הפרדת אירועים מ-אפקטים
מטפלי אירועים פועלים מחדש רק כאשר אתה מבצע שוב את אותה אינטראקציה. שלא כמו מטפלים באירועים, אפקטים מסתנכרנים מחדש אם ערך כלשהו שהם קוראים, כמו אב או משתנה מצב, שונה ממה שהיה במהלך העיבוד האחרון. לפעמים, אתה גם רוצה שילוב של שתי ההתנהגויות: אפקט המופעל מחדש בתגובה לערכים מסוימים אך לא לאחרים. הדף הזה ילמד אותך איך לעשות זאת.
You will learn
- כיצד לבחור בין מטפל באירועים לבין אפקט
- מדוע אפקטים מגיבים, ומטפלי אירועים אינם
- מה לעשות כאשר אתה רוצה שחלק מהקוד של האפקט שלך לא יהיה תגובתי
- מהם אירועי אפקט וכיצד לחלץ אותם מהאפקטים שלך
- כיצד לקרוא את הprops וstate העדכניים ביותר מאפקטים באמצעות אפקט אירועים
בחירה בין מטפלי אירועים לאפקטים
ראשית, בואו נסכם את ההבדל בין מטפלי אירועים לאפקטים.
תאר לעצמך שאתה מיישם רכיב של חדר צ’אט. הדרישות שלך נראות כך:
- הרכיב שלך אמור להתחבר אוטומטית לחדר הצ’אט הנבחר.
- כאשר אתה לוחץ על כפתור “שלח”, זה אמור לשלוח הודעה לצ’אט.
נניח שכבר יישמת את הקוד עבורם, אבל אתה לא בטוח איפה לשים אותו. האם להשתמש במטפלי אירועים או אפקטים? בכל פעם שאתה צריך לענות על שאלה זו, שקול למה הקוד צריך לפעול.
מטפלי אירועים פועלים בתגובה לאינטראקציות ספציפיות
מנקודת המבט של המשתמש, שליחת הודעה צריכה להתרחש מכיוון שנלחצה על כפתור ה”שלח” המסוים. המשתמש יתעצבן למדי אם תשלח את ההודעה שלו בכל זמן אחר או מכל סיבה אחרת. זו הסיבה ששליחת הודעה צריכה להיות מטפל באירועים. מטפלי אירועים מאפשרים לך לטפל באינטראקציות ספציפיות:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
// ...
function handleSendClick() {
sendMessage(message);
}
// ...
return (
<>
<input value={message} onChange={e => setMessage(e.target.value)} />
<button onClick={handleSendClick}>Send</button>;
</>
);
}עם מטפל באירועים, אתה יכול להיות בטוח ש’sendMessage(message)’ יפעל רק אם המשתמש ילחץ על הכפתור.
אפקטים פועלים בכל פעם שיש צורך בסנכרון
זכור כי אתה גם צריך לשמור את הרכיב מחובר לחדר הצ’אט. לאן הולך הקוד הזה?
הסיבה להפעיל את הקוד הזה אינה אינטראקציה מסוימת. זה לא משנה למה או איך המשתמש נווט למסך חדר הצ’אט. כעת, כשהם מסתכלים עליו ויכולים ליצור איתו אינטראקציה, הרכיב צריך להישאר מחובר לשרת הצ’אט שנבחר. גם אם רכיב חדר הצ’אט היה המסך הראשוני של האפליקציה שלך, והמשתמש לא ביצע אינטראקציות כלל, תצטרך עדיין להתחבר. זו הסיבה שזה אפקט:
function ChatRoom({ roomId }) {
// ...
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]);
// ...
}עם קוד זה, אתה יכול להיות בטוח שתמיד יש חיבור פעיל לשרת הצ’אט שנבחר כעת, ללא קשר לאינטראקציות הספציפיות שמבצע המשתמש. בין אם המשתמש רק פתח את האפליקציה שלך, בחר חדר אחר או ניווט למסך אחר ובחזרה, האפקט שלך מבטיח שהרכיב יישאר מסונכרן עם החדר שנבחר כעת, ו[יתחבר מחדש בכל פעם שנדרש.](/learn/lifecycle-of-reactive-effects#why-synchronization-cany-reactive-to-han
import { useState, useEffect } from 'react'; import { createConnection, sendMessage } 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(); }, [roomId]); function handleSendClick() { sendMessage(message); } return ( <> <h1>Welcome to the {roomId} room!</h1> <input value={message} onChange={e => setMessage(e.target.value)} /> <button onClick={handleSendClick}>Send</button> </> ); } 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} />} </> ); }
ערכים ריאקטיביים והיגיון תגובתי
באופן אינטואיטיבי, אפשר לומר שמטפלי אירועים תמיד מופעלים “ידנית”, למשל על ידי לחיצה על כפתור. האפקטים, לעומת זאת, הם “אוטומטיים”: הם פועלים ומופעלים מחדש בתדירות הנדרשת כדי להישאר מסונכרנים.
יש דרך יותר מדויקת לחשוב על זה.
props, מצב ומשתנים המוצהרים בתוך גוף הרכיב שלך נקראים ערכים תגובתיים. בדוגמה זו, serverUrl אינו ערך תגובתי, אבל roomId ו-message כן. הם משתתפים בזרימת הנתונים בעיבוד:
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
// ...
}ערכים תגובתיים כמו אלה יכולים להשתנות עקב עיבוד מחדש. לדוגמה, המשתמש יכול לערוך את ה’הודעה’ או לבחור ‘roomId’ אחר בתפריט נפתח. מטפלי אירועים ואפקטים מגיבים לשינויים בצורה שונה:
- ההיגיון בתוך מטפלי אירועים הוא לא תגובתי. זה לא יפעל שוב אלא אם המשתמש יבצע שוב את אותה אינטראקציה (למשל, קליק). מטפלי אירועים יכולים לקרוא ערכים תגובתיים מבלי “להגיב” לשינויים שלהם.
- ההיגיון בתוך Effects הוא reactive. אם האפקט שלך קורא ערך תגובתי, עליך לציין אותו כתלות. לאחר מכן, אם עיבוד מחדש גורם לערך זה להשתנות, React יפעיל מחדש את הלוגיקה של האפקט החדש שלך.
בואו נחזור על הדוגמה הקודמת כדי להמחיש את ההבדל הזה.
ההיגיון בתוך מטפלי אירועים אינו תגובתי
תסתכל על שורת הקוד הזו. האם ההיגיון הזה צריך להיות תגובתי או לא?
// ...
sendMessage(message);
// ...מנקודת המבט של המשתמש, שינוי ב’הודעה’ לא אומר שהוא רוצה לשלוח הודעה. זה רק אומר שהמשתמש מקליד. במילים אחרות, ההיגיון ששולח הודעה לא צריך להיות תגובתי. זה לא אמור לפעול שוב רק בגלל שערך התגובה השתנה. לכן זה שייך למטפל באירועים:
function handleSendClick() {
sendMessage(message);
}מטפלי אירועים אינם ריאקטיביים, כך ש’sendMessage(message)’ יפעל רק כאשר המשתמש ילחץ על כפתור השליחה.
ההיגיון בתוך Effects הוא תגובתי
כעת נחזור לשורות אלו:
// ...
const connection = createConnection(serverUrl, roomId);
connection.connect();
// ...מנקודת המבט של המשתמש, שינוי ב-roomId משמעו שהם רוצים להתחבר לחדר אחר. במילים אחרות, ההיגיון לחיבור לחדר צריך להיות תגובתי. אתה רוצה ששורות הקוד האלה “יעמדו בקצב” עם הערך הריאקטיבי, ויפעלו שוב אם הערך הזה שונה. לכן זה שייך לאפקט:
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect()
};
}, [roomId]);האפקטים הם תגובתיים, אז createConnection(serverUrl, roomId) ו-connection.connect() יפעלו עבור כל ערך מובחן של roomId. האפקט שלך שומר על חיבור הצ’אט מסונכרן לחדר שנבחר כעת.
חילוץ לוגיקה לא-ריאקטיבית מתוך אפקטים
דברים נעשים מסובכים יותר כאשר אתה רוצה לערבב לוגיקה תגובתית עם לוגיקה לא תגובתית.
לדוגמה, דמיינו שאתם רוצים להציג התראה כאשר המשתמש מתחבר לצ’אט. אתה קורא את הנושא הנוכחי (כהה או בהיר) מprops כדי שתוכל להציג את ההודעה בצבע הנכון:
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Connected!', theme);
});
connection.connect();
// ...עם זאת, theme הוא ערך תגובתי (הוא יכול להשתנות כתוצאה מעיבוד מחדש), ו[כל ערך תגובתי הנקרא על ידי אפקט חייב להיות מוכרז כתלות שלו.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specificated-every-reactive-value-the-reactive-value-the-me-a-dependency: now
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Connected!', theme);
});
connection.connect();
return () => {
connection.disconnect()
};
}, [roomId, theme]); // ✅ All dependencies declared
// ...שחק עם הדוגמה הזו וראה אם אתה יכול לזהות את הבעיה בחוויית המשתמש הזו:
import { useState, useEffect } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId, theme }) { useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.on('connected', () => { showNotification('Connected!', theme); }); connection.connect(); return () => connection.disconnect(); }, [roomId, theme]); return <h1>Welcome to the {roomId} room!</h1> } export default function App() { const [roomId, setRoomId] = useState('general'); const [isDark, setIsDark] = 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> <label> <input type="checkbox" checked={isDark} onChange={e => setIsDark(e.target.checked)} /> Use dark theme </label> <hr /> <ChatRoom roomId={roomId} theme={isDark ? 'dark' : 'light'} /> </> ); }
כאשר ה-‘roomId’ משתנה, הצ’אט מתחבר מחדש כפי שהיית מצפה. אבל מכיוון ש’נושא’ הוא גם תלות, הצ’אט גם מתחבר מחדש בכל פעם שאתה מחליף בין הנושא הכהה והבהיר. זה לא נהדר!
במילים אחרות, אתה לא רוצה שהשורה הזו תהיה תגובתית, למרות שהיא בתוך אפקט (שהוא תגובתי):
// ...
showNotification('Connected!', theme);
// ...אתה צריך דרך להפריד את ההיגיון הלא תגובתי הזה מהאפקט התגובתי סביבו.
הכרזה על אירוע אפקט
השתמש בהוק מיוחד בשם useEffectEvent כדי לחלץ את ההיגיון הלא תגובתי הזה מהאפקט שלך:
import { useEffect, useEffectEvent } from 'react';
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
// ...כאן, onConnected נקרא Effect Event. זה חלק מהלוגיקת האפקט שלך, אבל הוא מתנהג הרבה יותר כמו מטפל באירועים. ההיגיון בתוכו אינו תגובתי, והוא תמיד “רואה” את הערכים העדכניים ביותר של הprops וstate שלך.
עכשיו אתה יכול לקרוא לאירוע ‘onConnected’ אפקט מתוך האפקט שלך:
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
onConnected();
});
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ All dependencies declared
// ...זה פותר את הבעיה. שים לב שהיית צריך להסיר את ‘onConnected’ מרשימת התלות של האפקט שלך. אירועי אפקט אינם ריאקטיביים ויש להשמיט אותם מהתלות.
ודא שההתנהגות החדשה פועלת כפי שהיית מצפה:
import { useState, useEffect } from 'react'; import { experimental_useEffectEvent as useEffectEvent } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId, theme }) { const onConnected = useEffectEvent(() => { showNotification('Connected!', theme); }); useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.on('connected', () => { onConnected(); }); connection.connect(); return () => connection.disconnect(); }, [roomId]); return <h1>Welcome to the {roomId} room!</h1> } export default function App() { const [roomId, setRoomId] = useState('general'); const [isDark, setIsDark] = 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> <label> <input type="checkbox" checked={isDark} onChange={e => setIsDark(e.target.checked)} /> Use dark theme </label> <hr /> <ChatRoom roomId={roomId} theme={isDark ? 'dark' : 'light'} /> </> ); }
אתה יכול לחשוב על אפקט אירועים כדומים מאוד למטפלי אירועים. ההבדל העיקרי הוא שמטפלי אירועים פועלים בתגובה לאינטראקציות של משתמש, בעוד שאירועי אפקט מופעלים על ידך מ- Effects. אפקט אירועים מאפשרים לך “לשבור את השרשרת” בין התגובתיות של אפקטים לקוד שלא אמור להיות תגובתי.
קריאת הprops האחרונים וstate עם אפקט אירועים
אירועי אפקט מאפשרים לך לתקן דפוסים רבים שבהם אתה עלול להתפתות לדכא את קו התלות.
לדוגמה, נניח שיש לך אפקט לרישום הביקורים בדף:
function Page() {
useEffect(() => {
logVisit();
}, []);
// ...
}מאוחר יותר, אתה מוסיף מספר מסלולים לאתר שלך. כעת רכיב ה’דף’ שלך מקבל props ‘כתובת אתר’ עם הנתיב הנוכחי. אתה רוצה להעביר את ה-URL כחלק משיחת ה-‘logVisit’ שלך, אבל קו התלות מתלונן:
function Page({ url }) {
useEffect(() => {
logVisit(url);
}, []); // 🔴 React Hook useEffect has a missing dependency: 'url'
// ...
}תחשוב מה אתה רוצה שהקוד יעשה. אתה רוצה לרשום ביקור נפרד עבור כתובות אתרים שונות מכיוון שכל כתובת אתר מייצגת דף אחר. במילים אחרות, קריאת logVisit זו צריכה להיות תגובתית ביחס לכתובת ה-URL. זו הסיבה, שבמקרה זה, הגיוני לעקוב אחר קו התלות ולהוסיף ‘כתובת אתר’ בתור תלות:
function Page({ url }) {
useEffect(() => {
logVisit(url);
}, [url]); // ✅ All dependencies declared
// ...
}כעת נניח שברצונך לכלול את מספר הפריטים בעגלת הקניות יחד עם כל ביקור בדף:
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
useEffect(() => {
logVisit(url, numberOfItems);
}, [url]); // 🔴 React Hook useEffect has a missing dependency: 'numberOfItems'
// ...
}השתמשת ב-‘numberOfItems’ בתוך האפקט, אז ה-linter מבקש ממך להוסיף אותו כתלות. עם זאת, אתה לא רוצה שהקריאה ‘logVisit’ תהיה תגובתית ביחס ל’numberOfItems’. אם המשתמש מכניס משהו לעגלת הקניות, ו-‘numberOfItems’ משתנה, זה לא אומר שהמשתמש ביקר שוב בעמוד. במילים אחרות, ביקור בדף הוא, במובן מסוים, “אירוע”. זה קורה ברגע מדויק בזמן.
פצל את הקוד לשני חלקים:
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, numberOfItems);
});
useEffect(() => {
onVisit(url);
}, [url]); // ✅ All dependencies declared
// ...
}כאן, ‘onVisit’ הוא אירוע אפקט. הקוד שבתוכו אינו מגיב. זו הסיבה שאתה יכול להשתמש ב-‘numberOfItems’ (או בכל ערך תגובתי אחר!) מבלי לדאוג שזה יגרום לקוד שמסביב להפעיל מחדש בשינויים.
מצד שני, האפקט עצמו נשאר תגובתי. הקוד בתוך האפקט משתמש בprops url, כך שהאפקט יפעל מחדש לאחר כל רינדור מחדש עם url אחר. זה, בתורו, יקרא את אירוע אפקט ‘onVisit’.
כתוצאה מכך, תתקשר ל-‘logVisit’ עבור כל שינוי ב-‘url’, ותמיד תקרא את ה-‘numberOfItems’ העדכני ביותר. עם זאת, אם ‘numberOfItems’ משתנה מעצמו, זה לא יגרום לאף אחד מהקודים להפעיל מחדש.
Deep Dive
בבסיסי הקוד הקיימים, לפעמים אתה עשוי לראות את כלל המוך מודחק כך:
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
useEffect(() => {
logVisit(url, numberOfItems);
// 🔴 Avoid suppressing the linter like this:
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url]);
// ...
}לאחר ש-‘useEffectEvent’ הופך לחלק יציב ב-React, אנו ממליצים לעולם לא לדכא את ה-linter.
החיסרון הראשון של דיכוי הכלל הוא ש-React כבר לא מזהיר אותך כאשר האפקט שלך צריך “להגיב” לתלות תגובתית חדשה שהכנסת לקוד שלך. בדוגמה הקודמת, הוספת ‘url’ לתלויות כי React הזכיר לך לעשות זאת. לא תקבל עוד תזכורות כאלה עבור כל עריכה עתידית של אפקט זה אם תשבית את ה-linter. זה מוביל לבאגים.
הנה דוגמה לבאג מבלבל שנגרם על ידי דיכוי ה-linter. בדוגמה זו, הפונקציה handleMove אמורה לקרוא את ערך משתנה הstate הנוכחי canMove על מנת להחליט אם הנקודה צריכה לעקוב אחר הסמן. עם זאת, canMove תמיד נכון בתוך handleMove.
אתה יכול לראות למה?
import { useState, useEffect } from 'react'; export default function App() { const [position, setPosition] = useState({ x: 0, y: 0 }); const [canMove, setCanMove] = useState(true); function handleMove(e) { if (canMove) { setPosition({ x: e.clientX, y: e.clientY }); } } useEffect(() => { window.addEventListener('pointermove', handleMove); return () => window.removeEventListener('pointermove', handleMove); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( <> <label> <input type="checkbox" checked={canMove} onChange={e => setCanMove(e.target.checked)} /> The dot is allowed to move </label> <hr /> <div style={{ position: 'absolute', backgroundColor: 'pink', borderRadius: '50%', opacity: 0.6, transform: `translate(${position.x}px, ${position.y}px)`, pointerEvents: 'none', left: -20, top: -20, width: 40, height: 40, }} /> </> ); }
הבעיה עם הקוד הזה היא בדיכוי ה-dependency linter. אם תסיר את הדיכוי, תראה שהאפקט הזה צריך להיות תלוי בפונקציה ‘handleMove’. זה הגיוני: handleMove מוצהר בתוך גוף הרכיב, מה שהופך אותו לערך תגובתי. כל ערך תגובתי חייב להיות מוגדר כתלות, אחרת הוא עלול להתייאש עם הזמן!
מחבר הקוד המקורי “שיקר” ל-React באומרו שהאפקט אינו תלוי ([]) בערכים תגובתיים כלשהם. זו הסיבה ש-React לא סינכרן מחדש את האפקט לאחר ש-‘canMove’ השתנה (ו-‘handleMove’ איתו). מכיוון ש-React לא סינכרן מחדש את האפקט, ה-‘handleMove’ המצורף כמאזין הוא הפונקציה ‘handleMove’ שנוצרה במהלך העיבוד הראשוני. במהלך העיבוד הראשוני, canMove היה נכון, וזו הסיבה שhandleMove מהרינדור הראשוני יראה לנצח את הערך הזה.
אם לעולם לא תדחיק את ה-linter, לעולם לא תראה בעיות עם ערכים מיושנים.
עם useEffectEvent, אין צורך “לשקר” ל-linter, והקוד עובד כפי שהיית מצפה:
import { useState, useEffect } from 'react'; import { experimental_useEffectEvent as useEffectEvent } from 'react'; export default function App() { const [position, setPosition] = useState({ x: 0, y: 0 }); const [canMove, setCanMove] = useState(true); const onMove = useEffectEvent(e => { if (canMove) { setPosition({ x: e.clientX, y: e.clientY }); } }); useEffect(() => { window.addEventListener('pointermove', onMove); return () => window.removeEventListener('pointermove', onMove); }, []); return ( <> <label> <input type="checkbox" checked={canMove} onChange={e => setCanMove(e.target.checked)} /> The dot is allowed to move </label> <hr /> <div style={{ position: 'absolute', backgroundColor: 'pink', borderRadius: '50%', opacity: 0.6, transform: `translate(${position.x}px, ${position.y}px)`, pointerEvents: 'none', left: -20, top: -20, width: 40, height: 40, }} /> </> ); }
זה לא אומר ש-‘useEffectEvent’ הוא תמיד הפתרון הנכון. עליך להחיל אותו רק על שורות הקוד שאינך רוצה שיהיו תגובתיים. בארגז החול שלמעלה, לא רצית שהקוד של האפקט יהיה תגובתי לגבי canMove. לכן היה הגיוני לחלץ אירוע אפקט.
קרא את הסרת תלות אפקט לקבלת חלופות נכונות אחרות לדיכוי ה-linter.
מגבלות של אירועי אפקט
אירועי אפקט מוגבלים מאוד איך אתה יכול להשתמש בהם:
- התקשר אליהם רק מתוך אפקטים.
- לעולם אל תעביר אותם לרכיבים אחרים או לווים.
לדוגמה, אל תצהיר ותעביר אירוע אפקט בצורה הבאה:
function Timer() {
const [count, setCount] = useState(0);
const onTick = useEffectEvent(() => {
setCount(count + 1);
});
useTimer(onTick, 1000); // 🔴 Avoid: Passing Effect Events
return <h1>{count}</h1>
}
function useTimer(callback, delay) {
useEffect(() => {
const id = setInterval(() => {
callback();
}, delay);
return () => {
clearInterval(id);
};
}, [delay, callback]); // Need to specify "callback" in dependencies
}במקום זאת, תמיד הכריז על אפקט אירועים ישירות ליד האפקטים המשתמשים בהם:
function Timer() {
const [count, setCount] = useState(0);
useTimer(() => {
setCount(count + 1);
}, 1000);
return <h1>{count}</h1>
}
function useTimer(callback, delay) {
const onTick = useEffectEvent(() => {
callback();
});
useEffect(() => {
const id = setInterval(() => {
onTick(); // ✅ Good: Only called locally inside an Effect
}, delay);
return () => {
clearInterval(id);
};
}, [delay]); // No need to specify "onTick" (an Effect Event) as a dependency
}אירועי אפקט הם “חלקים” לא תגובתיים של קוד האפקט שלך. הם צריכים להיות ליד האפקט המשתמש בהם.
Recap
- מטפלי אירועים פועלים בתגובה לאינטראקציות ספציפיות.
- אפקטים פועלים בכל פעם שיש צורך בסנכרון.
- ההיגיון בתוך מטפלי אירועים אינו תגובתי.
- ההיגיון בתוך אפקטים הוא תגובתי.
- אתה יכול להעביר לוגיקה לא תגובתית מאפקטים לאירועי אפקט.
- התקשר רק ל-Effect Events מתוך Effects.
- אל תעביר אירועי אפקט לרכיבים אחרים או הHooks.
Challenge 1 of 4: תקן משתנה שלא מתעדכן
רכיב ‘טיימר’ זה שומר על משתנה מצב ‘ספירה’ אשר גדל כל שנייה. הערך שבאמצעותו הוא גדל מאוחסן במשתנה הstate ‘increment’. אתה יכול לשלוט במשתנה ‘increment’ עם לחצני הפלוס והמינוס.
עם זאת, לא משנה כמה פעמים תלחץ על כפתור הפלוס, המונה עדיין גדל באחד בכל שנייה. מה רע בקוד הזה? מדוע increment תמיד שווה ל1 בתוך הקוד של האפקט? מצא את הטעות ותקן אותה.
import { useState, useEffect } from 'react'; export default function Timer() { const [count, setCount] = useState(0); const [increment, setIncrement] = useState(1); useEffect(() => { const id = setInterval(() => { setCount(c => c + increment); }, 1000); return () => { clearInterval(id); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( <> <h1> Counter: {count} <button onClick={() => setCount(0)}>Reset</button> </h1> <hr /> <p> Every second, increment by: <button disabled={increment === 0} onClick={() => { setIncrement(i => i - 1); }}>–</button> <b>{increment}</b> <button onClick={() => { setIncrement(i => i + 1); }}>+</button> </p> </> ); }