מחזור החיים של אפקטים תגובתיים

לאפקטים יש מחזור חיים. רכיבים רכיבים לעלות, לעד או לטעינה. אפקט יכול לעשות רק שני דברים: להתחיל לסנכרן משהו, ובהמשך להפסיק לסנכרן אותו. מחזור זה יכול לקרות מספר פעמים אם ההשפעה שלך תלויה בprops ובstates המשתנים עם הזמן. React מספק כלל linter כדי לבדוק את התלות של האפקט שלך בצורה נכונה. זה שומר על האפקט שלך מסונכרן עם הprops וstate העדכניים ביותר.

You will learn

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

מחזור החיים של אפקט

כל הרכיב של React עובר את אותו מחזור חיים:

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

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

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

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

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

גוף האפקט שלך מציין כיצד להתחיל לסנכרן:

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

פונקציית הניקוי המוחזרת מהאפקט שלך מציינת כיצד להפסיק את הסנכרון:

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

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

בואו נסתכל על למה זה הכרחי, מתי זה קורה, ו_איך_ אתה יכול לשלוט בהתנהגות הזו.

Note

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

מדוע ייתכן שהסנכרון צריך להתרחש יותר מפעם אחת

תא לעצמך שרכיב ChatRoom זה מקבל props roomId שהמשתמש בוחר בתפריט נפתח. נניח שבתחילה משתמש בוחר את החדר "כללי" בתור roomId. האפליקציה שלך מציגה את חדר הצ’אט "כללי":

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

function ChatRoom({ roomId /* "general" */ }) {
// ...
return <h1>Welcome to the {roomId} room!</h1>;
}

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

function ChatRoom({ roomId /* "general" */ }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Connects to the "general" room
connection.connect();
return () => {
connection.disconnect(); // Disconnects from the "general" room
};
}, [roomId]);
// ...

עד כאן, כל כך טוב.

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

function ChatRoom({ roomId /* "travel" */ }) {
// ...
return <h1>Welcome to the {roomId} room!</h1>;
}

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

בתוך זה, אתה רוצה ש-React תעשה שני דברים:

  1. הפסק לסנכרן עם roomId הישן (נתק מהחדר "כללי")
  2. התחל לסנכרן עם roomId החדש (התחבר לחדר "נסיעות")

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

איך מגיב מסנכרן מחדש את האפקט שלך

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

כדי להפסיק לסנכרן, תגובה תקרא לפונקציית הניקוי שהאפקט שלך החזיר לאחר התחברות לחדר "כללי". מה ש-roomId היה "כללי", פונקציית הניקוי מתנתקת מהחדר "כללי":

function ChatRoom({ roomId /* "general" */ }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Connects to the "general" room
connection.connect();
return () => {
connection.disconnect(); // Disconnects from the "general" room
};
// ...

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

function ChatRoom({ roomId /* "travel" */ }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Connects to the "travel" room
connection.connect();
// ...

הודות לכך, אתה מחובר כעת לאותו חדר שהמשתמש בחר בממשק המשתמש. נמנע אסון!

בכל פעם לאחר שהרכיב שלך יוצג מחדש עם roomId אחר, האפקט שלך יסונכרן מחדש. לדוגמה, נניח שהמשתמש משנה את roomId מ"travel" ל"music". תגיב שוב תפסיק לסנכרן את האפקט שלך על ידי קריאת פונקציית הניקוי שלו (תנתק אותך מחדר ה"נסיעות"). אז הוא יתחיל לסנכרן שוב על ידי הפעלת הגוף שלו עם props roomId החדש (חבר אותך לחדר `“מוזיקה”).

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

חשיבה מנקודת המבט של האפקט

בואו נסכם את כל מה שקרה מנקודת המבט של רכיב ה-ChatRoom:

  1. ChatRoom מותקן כאשר roomId מוגדר ל"כללי"
  2. ChatRoom עודכן עם roomId מוגדר ל"נסיעות"
  3. ChatRoom עודכן עם roomId מוגדר ל"מוזיקה"
  4. ’ChatRoom’ בוטה

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

  1. האפקט שלך מחובר לחדר "כללי"
  2. האפקט שלך התנתק מהחדר "כללי" והתחבר לחדר "נסיעות"
  3. האפקט שלך התנתק מחדר "נסיעות" והתחבר לחדר "מוזיקה"
  4. האפקט שלך מנותק מחדר "מוזיקה"

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

useEffect(() => {
// Your Effect connected to the room specified with roomId...
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
// ...until it disconnected
connection.disconnect();
};
}, [roomId]);

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

  1. האפקט שלך התחבר לחדר "כללי" (עד שהוא התנתק)
  2. האפקט שלך התחבר לחדר "נסיעות" (עד שהוא התנתק)
  3. האפקט שלך התחבר לחדר "מוזיקה" (עד שהוא התנתק)

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

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

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

How React מאמת שהאפקט שלך יכול להסתנכרן מחדש

הנה דוגמה חיה אתה צריך לשחק איתה. לחץ על “פתח צ’אט” כדי לטעון את הרכיב ChatRoom:

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

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

function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);
  return <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} />}
    </>
  );
}

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

  1. ✅ מתחבר לחדר "כללי" בכתובת https://localhost:1234... (לפיתוח בלבד)
  2. ❌ מנותק מהחדר "כללי" בכתובת https://localhost:1234. (לפיתוח בלבד)
  3. ✅ מתחבר לחדר "כללי" בכתובת https://localhost:1234...

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

הגיב מאמת שהאפקט שלך יכול להסתנכרן מחדש על ידי אילוץ לעשות זאת מיד בפיתוח. זה עשוי להזכיר לך לפתוח דלת ולסגור אותה פעם נוספת כדי לבדוק אם מנעול הדלת עובד. תגובה מתחילה ומפסיקה את האפקט שלך פעם נוספת בפיתוח כדי לבדוק [ישמת את הניקוי שלו היטב.](/למד/סנכרן-עם-אפקטים#איך- לטפל באפקט-ירי-פעמיים-בפיתוח)

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

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

How React יודע שהוא צריך לסנכרן מחדש את האפקט

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

function ChatRoom({ roomId }) { // The roomId prop may change over time
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // This Effect reads roomId
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]); // So you tell React that this Effect "depends on" roomId
// ...

הנה איך זה עובד:

  1. ידעת ש’roomId’ הוא props, מה שאומר שהוא יכול להשתנות עם הזמן.
  2. ידעת שהאפקט שלך קורא roomId (כך שהלוגיקה שלו תלויה בערך שעשוי להשתנות מאוחר יותר).
  3. אתה מחפש את זה בתור התלות של האפקט (כך שהוא יסונכרן מחדש כאשר roomId מתרוצץ).

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

לדוגמה, אם עברת את ["כללי"]בהמשך הרנדור הראשוני, ובהמשך עברת את ["נסיעות"]על הרינדור הבא, הגיבו ישווה את "כללי" ו"נסיעות". אלו הם ערכים שונים (בהשוואה ל-Object.is), אז React יסנכרן מחדש את האפקט שלך. מצד שני, אם הרכיב שלך מעבד מחדש אבל roomId לא השתנה, האפקט שלך יישאר מחובר לאותו החדר.

כל אפקט מייצג תהליך סנכרון נפרד

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

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

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

function ChatRoom({ roomId }) {
useEffect(() => {
logVisit(roomId);
}, [roomId]);

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

כל אפקט בקוד שלך צריך לייצג תהליך סנכרון נפרד ובלתי תלוי.

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

אפקטים “מגיבים” לערכים תגובתיים

האפקט שלך קורא שני משתנים (serverUrl ו-roomId), אך ציינת רק roomId כתלות:

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

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

למה serverUrl לא צריך להיות תלות?

למה היא הסיבה ש-‘serverUrl’ לעולם לא מדרג עקב עיבוד מחדש. זה תמיד אותו הדבר, לא משנה כמה פעמים הרכיב יוצג מחדש ולמה. זה ש-‘serverUrl’ לעולם לא משתנה, לא יהיה הגיוני לציין זאת כתלות. אחרי הכל, תלות עושה משהו רק כשהן משתנות עם הזמן!

מצד שני, roomId עשוי להיות שונה בעיבוד מחדש. props, מצב וערכים אחרים המוצהרים בתוך הרכיב הם reactive מה שהם מחושבים על העיבוד והמשתתפים בזרימת יום של React.

אם serverUrl היה משתנה מצב, הוא היה מגיב. ערכים תגובתיים חייבים להיכלל בתלות:

function ChatRoom({ roomId }) { // Props change over time
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // State may change over time

useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Your Effect reads props and state
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId, serverUrl]); // So you tell React that this Effect "depends on" on props and state
// ...
}

על ידי הכללת ‘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');
  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} />
    </>
  );
}

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

המשמעות של אפקט עם תלות ריקות

מה קורה אם אתה מעביר גם serverUrl וגם roomId מחוץ לרכיב?

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

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

כעת הקוד של האפקט שלך אינו משתמש בשום ערכים תגובתיים, ולכן התלות שלו יכולה להיות ריקות ([]).

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

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

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

function ChatRoom() {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, []);
  return <h1>Welcome to the {roomId} room!</h1>;
}

export default function App() {
  const [show, setShow] = useState(false);
  return (
    <>
      <button onClick={() => setShow(!show)}>
        {show ? 'Close chat' : 'Open chat'}
      </button>
      {show && <hr />}
      {show && <ChatRoom />}
    </>
  );
}

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

כל המשתנים המוצהרים בגוף הרכיב הם תגובתיים

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

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

function ChatRoom({ roomId, selectedServerUrl }) { // roomId is reactive
const settings = useContext(SettingsContext); // settings is reactive
const serverUrl = selectedServerUrl ?? settings.defaultServerUrl; // serverUrl is reactive
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Your Effect reads roomId and serverUrl
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId, serverUrl]); // So it needs to re-synchronize when either of them changes!
// ...
}

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

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

במילים אחרות, אפקטים “מגיבים” לכל הערכים מגוף הרכיב.

Deep Dive

האם ערכים גלובליים או ניתנים לשינוי יכולים להיות תלות?

ערכים הניתנים לשינוי (כולל משתנים גלובליים) אינם מגיבים.

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

ערך ניתן לשינוי כמו ref.current או שאתה קורא ממנו גם לא יכול להיות תלות. דברים רפר שמוחזר על ידי useRef עצמו יכול להיות תלות, אבל המאפיין current הוא יכול לשינוי בכוונה. זה יכול לך להפעיל רינדור מחדש.](/learn/referencing-values-with-refs)

כפי שתלמד להלן בדף זה, סרגל יבדוק בעיות אלו באופן אוטומטי.

React מאמת שציינת כל ערך תגובתי כתלות

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

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

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

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, []); // <-- Something's wrong here!

  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');
  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} />
    </>
  );
}

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

כדי לתקן את הבאג, עקוב אחר ההצעה של ה-linter כדי לציין roomId ו- serverUrl כתלות של האפקט שלך:

function ChatRoom({ roomId }) { // roomId is reactive
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl is reactive
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]); // ✅ All dependencies declared
// ...
}

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

Note

אפשרות, תגובה יודע שערך לעולם לא משתנה למרות שהוא מוצהר בתוך הרכיב. לדוגמה, הפונקציה set המוחזרת מuseState ואובייקט ref המוחזר על ידי useRef הם יציבים—מובטח שהם לא ישנו בעיבוד מחדש. ערכים יציבים אינם מגיבים, אז אתה יכול להשמיט אותם מהרשימה. לרבות אותם מותר: לא ישתנו, אז זה לא משנה.

מה לעשות כשאתה לא רוצה לסנכרן מחדש

בדוגמה הקודמת, תיקנת את שגיאת המוך על ידי רישום ‘roomId’ ו-‘serverUrl’ כתלות.

עם זאת, אתה יכול במקום זאת “להוכיח” ל-Linter שערכים אלה הם ערכים תגוב, כלומר שהם לא יכולים להשתנות כמו עיבוד מחדש. לדוגמה, אם serverUrl וroomId אינם תלויים בעיבוד ותמיד יש להם ערכים, אתה יכול להעביר אותם מחוץ לרכיב. עכשיו הם לא צריכים להיות תלות:

const serverUrl = 'https://localhost:1234'; // serverUrl is not reactive
const roomId = 'general'; // roomId is not reactive

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

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

function ChatRoom() {
useEffect(() => {
const serverUrl = 'https://localhost:1234'; // serverUrl is not reactive
const roomId = 'general'; // roomId is not reactive
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, []); // ✅ All dependencies declared
// ...
}

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

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

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

  • אם אתה רוצה לקרוא את הערך העדכני של props או מצב מצב “להגיב” אליו ולסנכרן מחדש את האפקט, אתה יכול לפצל את האפקט שלך לחלק תגובתי (שאותו תשמור באפקט) וחלק לא תגובתי (שאותו תחלץ למשהו שנקרא Effect Event). קרא על הפרדת אירועים מהאפקטים.

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

Pitfall

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

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

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

ב-הבא pages, תלמד כיצד לתקן את הקוד הזה לבד לשבור את הכללים. תמיד כדאי לתקן!

Recap

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

Challenge 1 of 5:
תיקון חיבור מחדש בכל הקשה

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

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

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

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

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

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

  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} />
    </>
  );
}