useEffect הוא React Hook המאפשר לך לסנכרן רכיב עם מערכת חיצונית.

useEffect(setup, dependencies?)

הפניה

useEffect(setup, dependencies?)

התקשר ל-useEffect ברמה העליונה של הרכיב שלך כדי להכריז על אפקט:

import { useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}

ראה דוגמאות נוספות למטה.

פרמטרים

  • setup: הפונקציה עם ההיגיון של האפקט שלך. פונקציית ההגדרה שלך עשויה גם להחזיר פונקציית ניקוי. כאשר הרכיב שלך יתווסף ל-DOM, React יפעיל את פונקציית ההגדרה שלך. לאחר כל רינדור מחדש עם שינויים תלויים, React יריץ תחילה את פונקציית הניקוי (אם סיפקת אותה) עם הערכים הישנים, ולאחר מכן יריץ את פונקציית ההתקנה שלך עם הערכים החדשים. לאחר הסרת הרכיב שלך מה-DOM, React יפעיל את פונקציית הניקוי שלך.

  • אופציונלי dependencies: רשימת כל הערכים התגובתיים שאליהם מתייחסים בתוך הקוד setup. הערכים Reactive כוללים את props, state, ואת כל המשתנים והפונקציות המוצהרות ישירות בתוך גוף הרכיב שלך. אם ה-linter שלך הוא מוגדר עבור React, הוא יוודא שכל ערך תגובתי צוין כהלכה כתלות. רשימת התלות חייבת לכלול מספר קבוע של פריטים ולהיכתב בשורה כמו [dep1, dep2, dep3]. React ישווה כל תלות עם הערך הקודם שלה באמצעות ההשוואה Object.is. אם תשמיט ארגומנט זה, האפקט שלך יופעל מחדש לאחר כל רינדור מחדש של הרכיב. ראה את ההבדל בין העברת מערך של תלות, מערך ריק וללא תלות כלל.examples.

מחזירה

useEffect מחזירה undefined.

אזהרות

  • useEffect הוא Hook, אז אתה יכול לקרוא לו רק ברמה העליונה של הרכיב שלך או Hooks משלך. אתה לא יכול לקרוא לזה בתוך לולאות או תנאים. אם אתה צריך את זה, חלץ רכיב חדש והעביר את ה-state לתוכו.

  • אם אתה לא מנסה לסנכרן עם מערכת חיצונית כלשהי, כנראה לא צריך אפקט.

  • כאשר מצב קפדני מופעל, React יריץ הגדרה אחת נוספת לפיתוח בלבד+מחזור ניקוי לפני ההגדרה האמיתית הראשונה. זהו מבחן מאמץ המבטיח שהלוגיקת הניקוי שלך “משקפת” את היגיון ההתקנה שלך ושהוא עוצר או מבטל את כל מה שההגדרה עושה. אם זה use הוא בעיה, הטמיע את פונקציית הניקוי.

  • אם חלק מהתלות שלך הם אובייקטים או פונקציות שהוגדרו בתוך הרכיב, קיים סיכון שהם cause יפעלו מחדש את האפקט לעתים קרובות יותר מהנדרש. כדי לתקן זאת, הסר תלות מיותרות object ו-[function](#removing-unnecessary-dependencies-dependencies. אתה יכול גם לחלץ עדכונים של state ו-היגיון לא תגובתי מחוץ לאפקט שלך.

  • אם האפקט שלך לא היה caused על ידי אינטראקציה (כמו קליק), React בדרך כלל יאפשר לדפדפן לצבוע את המסך המעודכן תחילה לפני הפעלת האפקט שלך. אם האפקט שלך עושה משהו ויזואלי (לדוגמה, מיקום הסבר כלים), והעיכוב מורגש (לדוגמה, הוא מהבהב), החלף את useEffect ב-useEffect useLayoutEffect.

  • גם אם האפקט שלך היה caused על ידי אינטראקציה (כמו קליק), הדפדפן עשוי לצבוע מחדש את המסך לפני עיבוד עדכוני state בתוך האפקט שלך. בדרך כלל, זה מה שאתה רוצה. עם זאת, אם עליך לחסום את הדפדפן מלצבוע מחדש את המסך, עליך להחליף את useEffect ב-useLayoutEffect.

  • אפקטים פועלים רק על הלקוח. הם לא פועלים במהלך עיבוד השרת.


שימוש

התחברות למערכת חיצונית

חלק מהרכיבים צריכים להישאר מחוברים לרשת, חלק מהדפדפן API, או ספריית צד שלישי, בזמן שהם מוצגים בדף. מערכות אלו אינן נשלטות על ידי React, ולכן הן נקראות חיצוניות.

כדי לחבר את הרכיב שלך למערכת חיצונית כלשהי, התקשר ל-useEffect ברמה העליונה של הרכיב שלך:

import { useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}

עליך להעביר שני טיעונים ל-useEffect:

  1. פונקציית הגדרה עם קוד הגדרה שמתחברת לאותה מערכת.
    • היא אמורה להחזיר פונקציית ניקוי עם קוד ניקוי שמתנתקת ממערכת זו.
  2. רשימת תלות הכוללת כל ערך מהרכיב used שלך בתוך הפונקציות הללו.

React קורא לפונקציות ההגדרה והניקוי שלך בכל פעם שנדרש, מה שעשוי לקרות מספר פעמים:

  1. קוד ההגדרה שלך פועל כאשר הרכיב שלך מתווסף לדף (mounts).
  2. לאחר כל עיבוד מחדש של הרכיב שלך שבו השתנו התלות:
    • ראשית, קוד הניקוי שלך פועל עם props וstate הישנים.
    • לאחר מכן, קוד ההגדרה שלך פועל עם props וstate החדשים.
  3. קוד הניקוי שלך פועל פעם אחת אחרונה לאחר הסרת הרכיב שלך מהדף (מבוטל).

בואו נמחיש את הרצף הזה עבור הדוגמה שלמעלה.

כאשר הרכיב ChatRoom למעלה יתווסף לדף, הוא יתחבר לחדר הצ’אט עם ה-serverUrl וה-roomId הראשוניים. אם serverUrl או roomId משתנים כתוצאה מעיבוד מחדש (נניח, אם ה-user בוחר חדר צ’אט אחר בתפריט נפתח), האפקט שלך יתנתק מהחדר הקודם, ויתחבר לחדר הבא. כאשר הרכיב ChatRoom יוסר מהדף, האפקט שלך יתנתק פעם אחרונה.

כדי לעזור לך למצוא באגים, בפיתוח React מפעיל את setup ו-cleanup פעם נוספת אחת לפני step=1>setup. זהו מבחן מאמץ המוודא שהלוגיקה של האפקט שלך מיושמת כהלכה. אם יש בעיות גלויות לעין, פונקציית הניקוי שלך חסרה היגיון מסוים. פונקציית הניקוי צריכה להפסיק או לבטל את כל מה שפונקציית ההגדרה עשתה. כלל האצבע הוא שה-user לא אמור להבחין בין ההתקנה שנקראת פעם אחת (כמו בייצור) לבין רצף התקנהניקויהתקנה (כמו בפיתוח). ראה פתרונות נפוצים.

**נסה לכתוב כל אפקט כתהליך עצמאי ולחשוב על מחזור הגדרה/ניקוי יחיד בכל פעם.’ הרכיב שלך מותקן, מתעדכן או מבטל. כאשר היגיון הניקוי שלך “משקף” את היגיון ההגדרה בצורה נכונה, האפקט שלך עמיד להפעלת הגדרות וניקוי בתדירות הנדרשת.

Note

אפקט מאפשר לך לשמור על הרכיב שלך מסונכרן עם מערכת חיצונית כלשהי (כמו שירות צ’אט). כאן, מערכת חיצונית פירושה כל פיסת קוד שאינה נשלטת על ידי React, כגון:

אם אינך מתחבר לשום מערכת חיצונית, כנראה אינך זקוק לאפקט.

Examples of connecting to an external system

Example 1 of 5:
מתחבר לשרת צ’אט

בדוגמה זו, הרכיב ChatRoom use הוא אפקט כדי להישאר מחובר למערכת חיצונית המוגדרת ב-chat.js. לחץ על “פתח צ’אט” כדי לגרום לרכיב ChatRoom להופיע. ארגז החול הזה פועל במצב פיתוח, כך שיש מחזור חיבור וניתוק נוסף, כפי הסבר כאן. נסה לשנות את roomId וserverUrl באמצעות התפריט הנפתח והקלט, וראה כיצד האפקט מתחבר מחדש לצ’אט. לחץ על “סגור צ’אט” כדי לראות את האפקט מתנתק בפעם האחרונה.

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [roomId, serverUrl]);

  return (
    <>
      <label>
        Server URL:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Welcome to the {roomId} room!</h1>
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Close chat' : 'Open chat'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}


אפקטי גלישה ב-Hooks מותאם אישית

אפקטים הם “פתח בריחה”: אתה use אותם כאשר אתה צריך “לצאת החוצה React” וכאשר אין פתרון מובנה טוב יותר עבור מקרה use שלך. אם אתה מוצא את עצמך לעתים קרובות צריך לכתוב אפקטים באופן ידני, זה בדרך כלל סימן שאתה צריך לחלץ כמה custom Hooks להתנהגויות נפוצות שהרכיבים שלך מסתמכים עליהן.

לדוגמה, useChatRoom מותאם אישית Hook זה “מסתיר” את ההיגיון של האפקט שלך מאחורי API הצהרתי יותר:

function useChatRoom({ serverUrl, roomId }) {
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]);
}

אז אתה יכול use אותו מכל רכיב כמו זה:

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useChatRoom({
roomId: roomId,
serverUrl: serverUrl
});
// ...

יש גם הרבה Hooks מותאמים אישית מצוינים לכל מטרה הזמינים במערכת האקולוגית React.

למידע נוסף על גלישת אפקטים ב-Hooks מותאם אישית.

Examples of wrapping Effects in custom Hooks

Example 1 of 3:
מותאם אישית useChatRoom Hook

דוגמה זו זהה לאחת הדוגמאות המוקדמות יותר, אך ההיגיון חולץ ל-Hook מותאם אישית.

import { useState } from 'react';
import { useChatRoom } from './useChatRoom.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useChatRoom({
    roomId: roomId,
    serverUrl: serverUrl
  });

  return (
    <>
      <label>
        Server URL:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Welcome to the {roomId} room!</h1>
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Close chat' : 'Open chat'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}


שליטה בווידג’ט שאינו React

לפעמים, אתה רוצה לשמור על מערכת חיצונית מסונכרנת לאביזר כלשהו או state של הרכיב שלך.

לדוגמה, אם יש לך ווידג’ט מפה של צד שלישי או רכיב נגן וידאו שנכתב ללא React, אתה יכול use אפקט להתקשר לשיטות עליו שגורמות ל-state שלו להתאים ל-state הנוכחי של רכיב React שלך. אפקט זה יוצר מופע של מחלקה MapWidget המוגדרת ב-map-widget.js. כאשר אתה משנה את הפרופס של zoomLevel של הרכיב Map, האפקט קורא ל-setZoom() במופע המחלקה כדי לשמור אותו מסונכרן:

import { useRef, useEffect } from 'react';
import { MapWidget } from './map-widget.js';

export default function Map({ zoomLevel }) {
  const containerRef = useRef(null);
  const mapRef = useRef(null);

  useEffect(() => {
    if (mapRef.current === null) {
      mapRef.current = new MapWidget(containerRef.current);
    }

    const map = mapRef.current;
    map.setZoom(zoomLevel);
  }, [zoomLevel]);

  return (
    <div
      style={{ width: 200, height: 200 }}
      ref={containerRef}
    />
  );
}

בדוגמה זו, אין צורך בפונקציית ניקוי מכיוון שuse המחלקה MapWidget מנהלת רק את הצומת DOM שהועבר אליה. לאחר הסרת הרכיב Map React מהעץ, גם הצומת DOM וגם מופע המחלקה MapWidget ייאספו אוטומטית אשפה על ידי מנוע JavaScript של הדפדפן.


שליפת נתונים עם Effects

אתה יכול use אפקט כדי להביא נתונים עבור הרכיב שלך. שים לב שאם אתה use מסגרת, שימוש במנגנון אחזור הנתונים של המסגרת שלך יהיה הרבה יותר יעיל מאשר כתיבת אפקטים באופן ידני.

אם אתה רוצה להביא נתונים מאפקט באופן ידני, הקוד שלך עשוי להיראות כך:

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
const [person, setPerson] = useState('Alice');
const [bio, setBio] = useState(null);

useEffect(() => {
let ignore = false;
setBio(null);
fetchBio(person).then(result => {
if (!ignore) {
setBio(result);
}
});
return () => {
ignore = true;
};
}, [person]);

// ...

שימו לב למשתנה ignore שמאוחל ל-false, ומוגדר ל-true במהלך הניקוי. זה מבטיח הקוד שלך לא סובל מ”תנאי מירוץ”: תגובות הרשת עשויות להגיע בסדר שונה ממה ששלחת להן.

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);
  useEffect(() => {
    let ignore = false;
    setBio(null);
    fetchBio(person).then(result => {
      if (!ignore) {
        setBio(result);
      }
    });
    return () => {
      ignore = true;
    }
  }, [person]);

  return (
    <>
      <select value={person} onChange={e => {
        setPerson(e.target.value);
      }}>
        <option value="Alice">Alice</option>
        <option value="Bob">Bob</option>
        <option value="Taylor">Taylor</option>
      </select>
      <hr />
      <p><i>{bio ?? 'Loading...'}</i></p>
    </>
  );
}

אתה יכול גם לכתוב מחדש באמצעות async / await תחביר, אך עדיין עליך לספק פונקציית ניקוי:

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);
  useEffect(() => {
    async function startFetching() {
      setBio(null);
      const result = await fetchBio(person);
      if (!ignore) {
        setBio(result);
      }
    }

    let ignore = false;
    startFetching();
    return () => {
      ignore = true;
    }
  }, [person]);

  return (
    <>
      <select value={person} onChange={e => {
        setPerson(e.target.value);
      }}>
        <option value="Alice">Alice</option>
        <option value="Bob">Bob</option>
        <option value="Taylor">Taylor</option>
      </select>
      <hr />
      <p><i>{bio ?? 'Loading...'}</i></p>
    </>
  );
}

כתיבת אחזור נתונים ישירות באפקט חוזרת על עצמה ומקשה על הוספת אופטימיזציות כמו שמירה במטמון ועיבוד שרת מאוחר יותר. קל יותר use Hook מותאם אישית—בעצמך או שמתוחזק על ידי הקהילה.

Deep Dive

מהן חלופות טובות לאיסוף נתונים ב- Effects?

כתיבת fetch שיחות בתוך Effects היא דרך פופולרית להביא נתונים, במיוחד באפליקציות צד לקוח לחלוטין. עם זאת, זוהי גישה מאוד ידנית ויש לה חסרונות משמעותיים:

  • השפעות אינן פועלות על השרת. משמעות הדבר היא שהשרת הראשוני שניתנו HTML יכלול רק טעינה state ללא נתונים. מחשב הלקוח יצטרך להוריד את כל JavaScript ולעבד את האפליקציה שלך רק כדי לגלות שעכשיו הוא צריך לטעון את הנתונים. זה לא מאוד יעיל.
  • שליפה ישירות באפקטים מקלה על יצירת “מפלי רשת”. אתה מעבד את רכיב האב, הוא שואב נתונים מסוימים, מעבד את רכיבי הצאצא, ואז הם מתחילים לאחזר את הנתונים שלהם. אם הרשת לא מהירה במיוחד, זה איטי משמעותית מאשר שליפת כל הנתונים במקביל.
  • שליפה ישירה באפקטים משמעה בדרך כלל שאינך טוען מראש או מאחסן נתונים. לדוגמה, אם הרכיב מתנתק ואז נטען שוב, הוא יצטרך לאחזר את הנתונים שוב.
  • זה לא מאוד ארגונומי. יש לא מעט קוד בוילפלייס מעורב בעת כתיבת קריאות fetch בצורה שאינה סובלת מבאגים כמו תנאי מירוץ.

רשימה זו של חסרונות אינה ספציפית ל-React. זה חל על שליפת נתונים ב-mount עם כל ספרייה. כמו בניתוב, אחזור נתונים אינו טריוויאלי כדי לעשות זאת בצורה טובה, ולכן אנו ממליצים על הגישות הבאות:

  • אם אתה use framework, use מנגנון אחזור הנתונים המובנה שלו. במסגרות React מודרניות יש מנגנוני שליפת נתונים משולבים שהם יעילים ואינם סובלים מהמגבלה שלעיל.
  • אחרת, שקול להשתמש או לבנות מטמון בצד הלקוח. פתרונות קוד פתוח פופולריים כוללים React שאילתה, useSWR, ו-React נתב 6.4+. אתה יכול לבנות פתרון משלך גם כן, ובמקרה כזה אתה תוסיף __TK_ic עבור ____ ביטול כפילויות של בקשות, שמירת תגובות במטמון והימנעות ממפלי מים ברשת (על ידי טעינת נתונים מראש או הנפת דרישות נתונים למסלולים).

אתה יכול להמשיך להביא נתונים ישירות ב- Effects אם אף אחת מהגישות הללו לא מתאימה לך.


ציון תלות תגובתית

שים לב שאינך יכול “לבחור” את התלות של האפקט שלך. כל ערך תגובתי used לפי הקוד של האפקט שלך חייב להיות מוכרז כתלות. רשימת התלות של האפקט שלך נקבעת על ידי הקוד שמסביב:

function ChatRoom({ roomId }) { // This is a reactive value
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // This is a reactive value too

useEffect(() => {
const connection = createConnection(serverUrl, roomId); // This Effect reads these reactive values
connection.connect();
return () => connection.disconnect();
}, [serverUrl, roomId]); // ✅ So you must specify them as dependencies of your Effect
// ...
}

אם serverUrl או roomId ישתנו, האפקט שלך יתחבר מחדש לצ’אט באמצעות הערכים החדשים.

Reactive values כוללים את props ואת כל המשתנים והפונקציות המוצהרות ישירות בתוך הרכיב שלך. מכיוון שroomId וserverUrl הם ערכים תגובתיים, אינך יכול להסיר אותם מהתלות. אם תנסה להשמיט אותם וה-linter שלך מוגדר כהלכה עבור React, ה-linter יסמן זאת כטעות שעליך לתקן:

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // 🔴 React Hook useEffect has missing dependencies: 'roomId' and 'serverUrl'
// ...
}

כדי להסיר תלות, אתה צריך “להוכיח” ל-linter שהיא לא צריכה להיות תלות. לדוגמה, אתה יכול להעביר את serverUrl מהרכיב שלך כדי להוכיח שהוא לא משתנה ב-Reactive and ישתנה:

const serverUrl = 'https://localhost:1234'; // Not a reactive value anymore

function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ All dependencies declared
// ...
}

כעת, כאשר serverUrl אינו ערך תגובתי (ולא יכול להשתנות בעיבוד מחדש), זה לא צריך להיות תלות. אם הקוד של האפקט שלך אינו use ערכים תגובתיים כלשהם, רשימת התלות שלו צריכה להיות ריקה ([]):

const serverUrl = 'https://localhost:1234'; // Not a reactive value anymore
const roomId = 'music'; // Not a reactive value anymore

function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // ✅ All dependencies declared
// ...
}

אפקט עם תלות ריקות אינו פועל מחדש כאשר כל אחד מהרכיבים props או state משתנה.

Pitfall

אם יש לך בסיס קוד קיים, אולי יהיו לך כמה אפקטים שמדכאים את ה-linter כך:

useEffect(() => {
// ...
// 🔴 Avoid suppressing the linter like this:
// eslint-ignore-next-line react-hooks/exhaustive-deps
}, []);

כאשר התלות אינן תואמות לקוד, קיים סיכון גבוה להחדרת באגים. על ידי דיכוי ה-linter, אתה “משקר” ל-React לגבי הערכים שהאפקט שלך תלוי בהם. במקום זאת, הוכח שהם מיותרים.

Examples of passing reactive dependencies

Example 1 of 3:
העברת מערך תלות

אם תציין את התלות, האפקט שלך יפעל לאחר הרינדור הראשוני ו לאחר רינדור מחדש עם התלות שהשתנו.

useEffect(() => {
// ...
}, [a, b]); // Runs again if a or b are different

בדוגמה למטה, serverUrl ו-roomId הם ערכים תגובתיים, ולכן יש לציין את שניהם כתלות. כתוצאה מכך, בחירת חדר אחר בתפריט הנפתח או עריכת קלט כתובת ה-URL של השרת מאפשרת לצ’אט להתחבר מחדש. עם זאת, מכיוון שmessage אינו used באפקט (ולכן זה לא תלות), עריכת ההודעה לא מתחברת מחדש לצ’אט.

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');
  const [message, setMessage] = useState('');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [serverUrl, roomId]);

  return (
    <>
      <label>
        Server URL:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Welcome to the {roomId} room!</h1>
      <label>
        Your message:{' '}
        <input value={message} onChange={e => setMessage(e.target.value)} />
      </label>
    </>
  );
}

export default function App() {
  const [show, setShow] = useState(false);
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
        <button onClick={() => setShow(!show)}>
          {show ? 'Close chat' : 'Open chat'}
        </button>
      </label>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId}/>}
    </>
  );
}


עדכון state בהתבסס על state קודם מאפקט

כאשר אתה רוצה לעדכן את state בהתבסס על state קודם מאפקט, אתה עלול להיתקל בבעיה:

function Counter() {
const [count, setCount] = useState(0);

useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1); // You want to increment the counter every second...
}, 1000)
return () => clearInterval(intervalId);
}, [count]); // 🚩 ... but specifying `count` as a dependency always resets the interval.
// ...
}

מכיוון שcount הוא ערך תגובתי, יש לציין אותו ברשימת התלות. עם זאת, זה נותן את האפקט לניקוי והגדרה מחדש בכל פעם שה-count משתנה. זה לא אידיאלי.

כדי לתקן זאת, העבירו את עדכון c => c + 1 state אל setCount:

import { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(c => c + 1); // ✅ Pass a state updater
    }, 1000);
    return () => clearInterval(intervalId);
  }, []); // ✅ Now count is not a dependency

  return <h1>{count}</h1>;
}

כעת, כשאתה מעביר את c => c + 1 במקום count + 1, [האפקט שלך כבר לא צריך להיות תלוי ב-count.](/learn/removing-effect-dependencies#are-you-reading-some-state-לחשב-הבא-count. זה זכה שוב לתיקון והגדר את התיקון הזה. בכל פעם שה-count משתנה.


הסרת תלות מיותרת באובייקט

אם האפקט שלך תלוי באובייקט או בפונקציה שנוצרה במהלך העיבוד, הוא עלול לפעול לעתים קרובות מדי. לדוגמה, אפקט זה מתחבר מחדש לאחר כל רינדור מכיוון שהאובייקט options הוא שונה עבור כל רינדור:

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

const options = { // 🚩 This object is created from scratch on every re-render
serverUrl: serverUrl,
roomId: roomId
};

useEffect(() => {
const connection = createConnection(options); // It's used inside the Effect
connection.connect();
return () => connection.disconnect();
}, [options]); // 🚩 As a result, these dependencies are always different on a re-render
// ...

הימנע משימוש באובייקט שנוצר במהלך העיבוד כתלות. במקום זאת, צור את האובייקט בתוך האפקט:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <>
      <h1>Welcome to the {roomId} room!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

עכשיו שאתה יוצר את האובייקט options בתוך האפקט, האפקט עצמו תלוי רק במחרוזת roomId.

עם התיקון הזה, הקלדה בקלט לא מחברת מחדש את הצ’אט. שלא כמו אובייקט שנוצר מחדש, מחרוזת כמו roomId לא משתנה אלא אם כן תגדיר אותה לערך אחר. קרא עוד על הסרת תלות.


הסרת תלות מיותרת בפונקציות

אם האפקט שלך תלוי באובייקט או בפונקציה שנוצרה במהלך העיבוד, הוא עלול לפעול לעתים קרובות מדי. לדוגמה, אפקט זה מתחבר מחדש לאחר כל רינדור מכיוון שהפונקציה createOptions היא שונה עבור כל רינדור:

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

function createOptions() { // 🚩 This function is created from scratch on every re-render
return {
serverUrl: serverUrl,
roomId: roomId
};
}

useEffect(() => {
const options = createOptions(); // It's used inside the Effect
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // 🚩 As a result, these dependencies are always different on a re-render
// ...

כשלעצמה, יצירת פונקציה מאפס בכל עיבוד מחדש אינה בעיה. אתה לא צריך לייעל את זה. עם זאת, אם אתה use זה כתלות של האפקט שלך, זה use האפקט שלך יפעל מחדש לאחר כל עיבוד מחדש.

הימנע משימוש בפונקציה שנוצרה במהלך העיבוד כתלות. במקום זאת, הכריז על זה בתוך האפקט:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    function createOptions() {
      return {
        serverUrl: serverUrl,
        roomId: roomId
      };
    }

    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <>
      <h1>Welcome to the {roomId} room!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

עכשיו כשאתה מגדיר את הפונקציה createOptions בתוך האפקט, האפקט עצמו תלוי רק במחרוזת roomId. עם התיקון הזה, הקלדה בקלט לא מחברת מחדש את הצ’אט. שלא כמו פונקציה שנוצרת מחדש, מחרוזת כמו roomId לא משתנה אלא אם כן תגדיר אותה לערך אחר. קרא עוד על הסרת תלות.


קריאת ה-props וה-state העדכניים מאפקט

Under Construction

סעיף זה מתאר ניסוי API שעדיין לא שוחרר בגרסה יציבה של React.

כברירת מחדל, כאשר אתה קורא ערך תגובתי מאפקט, עליך להוסיף אותו כתלות. זה מבטיח שהאפקט שלך “מגיב” לכל שינוי של הערך הזה. עבור רוב התלות, זו ההתנהגות שאתה רוצה.

עם זאת, לפעמים תרצו לקרוא את האחרונות props וstate מתוך אפקט מבלי “להגיב” אליהם. לדוגמה, דמיינו שאתם רוצים לרשום את מספר הפריטים בעגלת הקניות עבור כל ביקור בדף:

function Page({ url, shoppingCart }) {
useEffect(() => {
logVisit(url, shoppingCart.length);
}, [url, shoppingCart]); // ✅ All dependencies declared
// ...
}

מה אם אתה רוצה לרשום ביקור חדש בדף אחרי כל שינוי url, אבל לא אם רק ה-shoppingCart משתנה? אינך יכול לכלול את shoppingCart מתלות מבלי לשבור את כללי התגובה. עם זאת, אתה יכול להביע שאתה *לא רוצה לשנות את הקוד ל”אפקט” אפילו ל”אפקט”. הכרז על אירוע אפקט עם ה-useEffectEvent Hook, והזז בתוכו את הקוד שקורא shoppingCart:

function Page({ url, shoppingCart }) {
const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, shoppingCart.length)
});

useEffect(() => {
onVisit(url);
}, [url]); // ✅ All dependencies declared
// ...
}

אירועי אפקט אינם תגובתיים ויש להשמיט אותם תמיד מהתלות של האפקט שלך. זה מה שמאפשר לך לשים קוד לא תגובתי (שם תוכל לקרוא את הערך האחרון של כמה props וstate) בתוכם. על ידי קריאת shoppingCart בתוך onVisit, אתה מבטיח שshoppingCart לא יפעיל מחדש את האפקט שלך.

קרא עוד על האופן שבו אפקט אירועים מאפשרים לך להפריד בין קוד תגובתי ולא תגובתי.


הצגת תוכן שונה בשרת ובלקוח

אם האפליקציה שלך use עיבוד שרת (או ישיר או באמצעות framework), הרכיב שלך יוצג בשתי סביבות שונות. בשרת, הוא יעבד כדי לייצר את ה-HTML הראשוני. בלקוח, React יריץ שוב את קוד העיבוד כדי שיוכל לצרף את מטפלי האירועים שלך לאותו HTML. זו הסיבה שכדי ש-hydration יעבוד, פלט העיבוד הראשוני שלך חייב להיות זהה בלקוח ובשרת.

במקרים נדירים, ייתכן שיהיה עליך להציג תוכן שונה בלקוח. לדוגמה, אם האפליקציה שלך קוראת כמה נתונים מ-localStorage, היא לא יכולה לעשות זאת בשרת. כך תוכל ליישם זאת:

function MyComponent() {
const [didMount, setDidMount] = useState(false);

useEffect(() => {
setDidMount(true);
}, []);

if (didMount) {
// ... return client-only JSX ...
} else {
// ... return initial JSX ...
}
}

בזמן שהאפליקציה נטענת, ה-user יראה את פלט העיבוד הראשוני. לאחר מכן, כאשר הוא נטען ומיועד לחות, האפקט שלך יפעל ויגדיר את didMount ל-true, ויפעיל עיבוד מחדש. פעולה זו תעבור לפלט העיבוד ללקוח בלבד. אפקטים לא פועלים על השרת, אז זו הסיבה שdidMount היה false במהלך העיבוד הראשוני של השרת.

השתמש בתבנית זו במשורה. זכור ש-users עם חיבור איטי יראו את התוכן הראשוני במשך לא מעט זמן - פוטנציאלי, שניות רבות - כך שאינך רוצה לבצע שינויים צורמים במראה הרכיב שלך. במקרים רבים, אתה יכול להימנע מהצורך בכך על ידי הצגה מותנית של דברים שונים עם CSS.


פתרון בעיות

My Effect פועל פעמיים כאשר הרכיב נטען

כאשר מצב קפדני מופעל, בפיתוח, React מריץ את ההגדרה והניקוי פעם נוספת לפני ההגדרה בפועל.

זהו מבחן מאמץ המוודא שהלוגיקה של האפקט שלך מיושמת כהלכה. אם יש בעיות גלויות לעין, פונקציית הניקוי שלך חסרה היגיון מסוים. פונקציית הניקוי צריכה להפסיק או לבטל את כל מה שפונקציית ההגדרה עשתה. כלל האצבע הוא שה-user לא אמור להיות מסוגל להבחין בין ההגדרה שנקראת פעם אחת (כמו בייצור) לבין הגדרה → ניקוי → רצף התקנה (כמו בפיתוח).

קרא עוד על איך זה עוזר למצוא באגים ואיך לתקן את ההיגיון שלך.


האפקט שלי פועל לאחר כל עיבוד מחדש

ראשית, בדוק שלא שכחת לציין את מערך התלות:

useEffect(() => {
// ...
}); // 🚩 No dependency array: re-runs after every render!

אם ציינת את מערך התלות אבל האפקט שלך עדיין פועל מחדש בלולאה, זה בגלל שאחת מהתלות שלך שונה בכל רינדור מחדש.

אתה יכול לנפות באגים בבעיה זו על ידי רישום ידני של התלות שלך למסוף:

useEffect(() => {
// ..
}, [serverUrl, roomId]);

console.log([serverUrl, roomId]);

לאחר מכן תוכל ללחוץ לחיצה ימנית על המערכים מעיבודים חוזרים שונים בקונסולה ולבחור “אחסן כמשתנה גלובלי” עבור שניהם. בהנחה שהראשון נשמר כ-temp1 והשני נשמר כ-temp2, לאחר מכן תוכל use למסוף הדפדפן כדי לבדוק אם כל תלות בשני המערכים זהה:

Object.is(temp1[0], temp2[0]); // Is the first dependency the same between the arrays?
Object.is(temp1[1], temp2[1]); // Is the second dependency the same between the arrays?
Object.is(temp1[2], temp2[2]); // ... and so on for every dependency ...

כאשר אתה מוצא את התלות השונה בכל עיבוד מחדש, אתה יכול בדרך כלל לתקן אותה באחת מהדרכים הבאות:

כמוצא אחרון (אם שיטות אלו לא עזרו), עטפו את יצירתו ב-useMemo או useCallback-foo).


האפקט שלי ממשיך לפעול מחדש במחזור אינסופי

אם האפקט שלך פועל במחזור אינסופי, שני הדברים האלה חייבים להיות נכונים:

  • האפקט שלך מעדכן כמה state.
  • state זה מוביל לעיבוד מחדש, אשר גורם לשינוי התלות של האפקט.

לפני שתתחיל לתקן את הבעיה, שאל את עצמך אם האפקט שלך מתחבר למערכת חיצונית כלשהי (כמו DOM, רשת, ווידג’ט של צד שלישי וכן הלאה). מדוע האפקט שלך צריך להגדיר state? האם זה מסתנכרן עם אותה מערכת חיצונית? או שאתה מנסה לנהל איתו את זרימת הנתונים של האפליקציה שלך?

אם אין מערכת חיצונית, שקול אם הסרת האפקט לגמרי תפשט את ההיגיון שלך.

אם אתה באמת מסתנכרן עם מערכת חיצונית כלשהי, חשב למה ובאילו תנאים האפקט שלך צריך לעדכן את ה-state. האם השתנה משהו שמשפיע על הפלט החזותי של הרכיב שלך? אם אתה צריך לעקוב אחר נתונים מסוימים שאינם used על ידי רינדור, ref (שאינו מפעיל עיבוד מחדש) עשוי להיות מתאים יותר. ודא שהאפקט שלך לא מעדכן את state (ומפעיל עיבוד מחדש) יותר מהנדרש.

לבסוף, אם האפקט שלך מעדכן את state בזמן הנכון, אבל עדיין יש לולאה, זה בגלל שuse עדכון state מוביל לשינוי אחד מהתלות של האפקט. קרא כיצד לנפות באגים בשינויי תלות.


לוגיקת הניקוי שלי פועלת למרות שהרכיב שלי לא ביטל את הטעינה

פונקציית הניקוי פועלת לא רק במהלך ביטול הרכבה, אלא לפני כל רינדור מחדש עם השתנות תלויות. בנוסף, בפיתוח, React מריץ את ההתקנה+ניקוי פעם נוספת מיד לאחר הרכבה של הרכיב.

אם יש לך קוד ניקוי ללא קוד הגדרה מתאים, זה בדרך כלל ריח של קוד:

useEffect(() => {
// 🔴 Avoid: Cleanup logic without corresponding setup logic
return () => {
doSomething();
};
}, []);

היגיון הניקוי שלך צריך להיות “סימטרי” ללוגיקת ההגדרה, ועליו לעצור או לבטל את כל מה שההגדרה עשתה:

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);

למד כיצד מחזור החיים של אפקט שונה ממחזור החיים של הרכיב.


האפקט שלי עושה משהו ויזואלי, ואני רואה הבהוב לפני שהוא מריץ

אם האפקט שלך חייב לחסום את הדפדפן מצביעת המסך, החלף את useEffect ב-useLayoutEffect. שים לב שזה לא אמור להיות נחוץ עבור הרוב המכריע של האפקטים. תזדקק לזה רק אם זה חיוני להפעיל את האפקט שלך לפני ציור הדפדפן: למשל, למדוד ולמקם הסבר כלים לפני שה-user יראה אותו.