הפניה לערכים עם Refs
כאשר אתה רוצה שרכיב “יזכור” מידע מסוים, אבל אתה לא רוצה שהמידע הזה תפעיל עיבודים חדשים, אתה יכול use ref.
You will learn
- כיצד להוסיף שופט לרכיב שלך
- כיצד לעדכן ערך של שופט
- איך השופטים שונים מ-state
- איך use שופטים בבטחה
הוספת ref לרכיב שלך
אתה יכול להוסיף רפר לרכיב שלך על ידי ייבוא ה-useRef Hook מ-React:
import { useRef } from 'react';בתוך הרכיב שלך, קרא ל-useRef Hook והעביר את הערך ההתחלתי שאליו אתה רוצה להתייחס כארגומנט היחיד. לדוגמה, הנה ר”פ לערך 0:
const ref = useRef(0);useRef מחזיר אובייקט בצורה הבאה:
{
current: 0 // The value you passed to useRef
}
Illustrated by Rachel Lee Nabors
אתה יכול לגשת לערך הנוכחי של אותו השופט דרך המאפיין ref.current. ערך זה ניתן לשינוי בכוונה, כלומר אתה יכול גם לקרוא וגם לכתוב אליו. זה כמו כיס סודי של הרכיב שלך שReact לא עוקב אחריו. (זה מה שהופך אותו ל”פתח מילוט” מזרימת הנתונים החד-כיוונית של React - עוד על כך בהמשך!)
כאן, כפתור יגדיל את ref.current בכל לחיצה:
import { useRef } from 'react'; export default function Counter() { let ref = useRef(0); function handleClick() { ref.current = ref.current + 1; alert('You clicked ' + ref.current + ' times!'); } return ( <button onClick={handleClick}> Click me! </button> ); }
ה-ref מצביע על מספר, אבל, כמו state, אתה יכול להצביע על כל דבר: מחרוזת, אובייקט או אפילו פונקציה. שלא כמו state, ref הוא אובייקט JavaScript רגיל עם המאפיין current שניתן לקרוא ולשנות.
שים לב שהרכיב לא מעבד מחדש עם כל תוספת. כמו state, השומרים נשמרים על ידי React בין העיבוד מחדש. עם זאת, הגדרה של state מעבדת מחדש רכיב. החלפת שופט לא!
דוגמה: בניית שעון עצר
אתה יכול לשלב refs ו-state ברכיב אחד. לדוגמה, בואו ניצור שעון עצר שה-user יכול להפעיל או לעצור על ידי לחיצה על כפתור. על מנת להציג כמה זמן עבר מאז שה-user לחץ על “התחל”, תצטרכו לעקוב מתי לחצו על כפתור התחל ומה השעה הנוכחית. מידע זה הוא used לעיבוד, כך שתשמור אותו ב-state:
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);כאשר ה-user לוחץ על “התחל”, תבצע use setInterval כדי לעדכן את השעה כל 10 אלפיות השנייה:
import { useState } from 'react'; export default function Stopwatch() { const [startTime, setStartTime] = useState(null); const [now, setNow] = useState(null); function handleStart() { // Start counting. setStartTime(Date.now()); setNow(Date.now()); setInterval(() => { // Update the current time every 10ms. setNow(Date.now()); }, 10); } let secondsPassed = 0; if (startTime != null && now != null) { secondsPassed = (now - startTime) / 1000; } return ( <> <h1>Time passed: {secondsPassed.toFixed(3)}</h1> <button onClick={handleStart}> Start </button> </> ); }
כאשר כפתור “עצור” נלחץ, עליך לבטל את המרווח הקיים כדי שיפסיק לעדכן את המשתנה now state. אתה יכול לעשות זאת על ידי קריאה ל-clearInterval, אבל אתה צריך לתת לו את מזהה המרווח שהוחזר בעבר על ידי שיחת setInterval כשה-user לחץ על Start. אתה צריך לשמור את מזהה המרווח איפשהו. מכיוון שמזהה המרווח אינו used לעיבוד, אתה יכול לשמור אותו ב-ref:
import { useState, useRef } from 'react'; export default function Stopwatch() { const [startTime, setStartTime] = useState(null); const [now, setNow] = useState(null); const intervalRef = useRef(null); function handleStart() { setStartTime(Date.now()); setNow(Date.now()); clearInterval(intervalRef.current); intervalRef.current = setInterval(() => { setNow(Date.now()); }, 10); } function handleStop() { clearInterval(intervalRef.current); } let secondsPassed = 0; if (startTime != null && now != null) { secondsPassed = (now - startTime) / 1000; } return ( <> <h1>Time passed: {secondsPassed.toFixed(3)}</h1> <button onClick={handleStart}> Start </button> <button onClick={handleStop}> Stop </button> </> ); }
כאשר פיסת מידע היא used לעיבוד, שמור אותו ב-state. כאשר פיסת מידע נחוצה רק על ידי מטפלי אירועים ושינויו אינו מצריך עיבוד מחדש, השימוש ב-ref עשוי להיות יעיל יותר.
הבדלים בין שופטים ל-state
אולי אתה חושב ששופטים נראים פחות “קפדניים” מ-state - אתה יכול לשנות אותם במקום תמיד להצטרך use פונקציית הגדרה של state, למשל. אבל ברוב המקרים, תרצה use state. Refs הם “פתח מילוט” שלא תצטרך לעתים קרובות. כך משווים בין state לבין השופטים:
| שופטים | state |
|---|---|
useRef(initialValue) מחזירה { current: initialValue } | useState(initialValue) מחזירה את הערך הנוכחי של משתנה state ופונקציית מגדיר state ([value, setValue]) |
| לא מפעיל עיבוד מחדש כשאתה משנה אותו. | מפעיל עיבוד מחדש כאשר אתה משנה אותו. |
ניתן לשינוי - אתה יכול לשנות ולעדכן את הערך של current מחוץ לתהליך העיבוד. | ”בלתי ניתן לשינוי” - עליך use את פונקציית ההגדרה state כדי לשנות משתנים של state כדי לעמוד בתור עיבוד מחדש. |
אין לקרוא (או לכתוב) את הערך current במהלך העיבוד. | אתה יכול לקרוא את state בכל עת. עם זאת, לכל עיבוד יש תמונת מצב משלו של state שאינו משתנה. |
הנה כפתור מונה שמיושם עם state:
import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return ( <button onClick={handleClick}> You clicked {count} times </button> ); }
מכיוון שהערך count מוצג, הגיוני להזין ערך state עבורו use. כאשר ערך המונה מוגדר עם setCount(), React מעבד מחדש את הרכיב והמסך מתעדכן כדי לשקף את הספירה החדשה.
אם ניסית ליישם את זה עם ref, React לעולם לא יעבד מחדש את הרכיב, כך שלעולם לא תראה את הספירה משתנה! ראה כיצד לחיצה על כפתור זה לא מעדכנת את הטקסט שלו:
import { useRef } from 'react'; export default function Counter() { let countRef = useRef(0); function handleClick() { // This doesn't re-render the component! countRef.current = countRef.current + 1; } return ( <button onClick={handleClick}> You clicked {countRef.current} times </button> ); }
זו הסיבה שקריאת ref.current במהלך העיבוד מובילה לקוד לא אמין. אם אתה צריך את זה, use state במקום זאת.
Deep Dive
למרות שגם useState וגם useRef מסופקים על ידי React, באופן עקרוני ניתן ליישם את useRef על גבי useState. אתה יכול לדמיין שבתוך React, useRef מיושם כך:
// Inside of React
function useRef(initialValue) {
const [ref, unused] = useState({ current: initialValue });
return ref;
}במהלך העיבוד הראשון, useRef מחזיר { current: initialValue }. אובייקט זה מאוחסן על ידי React, כך שבמהלך העיבוד הבא אותו אובייקט יוחזר. שימו לב כיצד מגדיר state אינוused בדוגמה זו. זה מיותר כי use useRef תמיד צריך להחזיר את אותו אובייקט!
React מספק גרסה מובנית של useRef כי use היא נפוצה מספיק בפועל. אבל אתה יכול לחשוב על זה כעל משתנה state רגיל ללא מגדיר. אם אתה מכיר תכנות מונחה עצמים, refs עשוי להזכיר לך שדות מופע - אבל במקום this.something אתה כותב somethingRef.current.
מתי use שופטים
בדרך כלל, אתה use ref כאשר הרכיב שלך צריך “לצאת החוצה” React ולתקשר עם APIs חיצוניים - לעתים קרובות דפדפן API שלא ישפיע על המראה של הרכיב. הנה כמה מהמצבים הנדירים האלה:
- אחסון מזהי זמן קצוב
- אחסון ומניפולציה של DOM אלמנטים, שאנו מכסים בעמוד הבא
- אחסון אובייקטים אחרים שאינם נחוצים לחישוב ה-JSX.
אם הרכיב שלך צריך לאחסן ערך מסוים, אבל זה לא משפיע על הלוגיקה של העיבוד, בחר refs.
שיטות עבודה מומלצות לשופטים
ביצוע העקרונות האלה יהפוך את הרכיבים שלך לצפויים יותר:
- תייחסו ל-Refs כאל פתח מילוט. Refs הם useמלאים כאשר אתם עובדים עם מערכות חיצוניות או דפדפן APIs. אם חלק גדול מהלוגיקת היישום ומזרימת הנתונים שלך מסתמכים על המלצות, אולי תרצה לחשוב מחדש על הגישה שלך.
- אל תקרא או תכתוב
ref.currentבמהלך העיבוד. אם יש צורך במידע כלשהו במהלך העיבוד, use state במקום זאת. מכיוון שReact אינו יודע מתיref.currentמשתנה, אפילו קריאתו תוך כדי רינדור מקשה על חיזוי ההתנהגות של הרכיב שלך. (החריג היחיד לכך הוא קוד כמוif (!ref.current) ref.current = new Thing()שקובע את השופט פעם אחת בלבד במהלך העיבוד הראשון.)
המגבלות של React state לא חלות על שופטים. לדוגמה, state פועל כמו תמונת מצב עבור כל עיבוד ולא מתעדכן באופן סינכרוני. אבל כאשר אתה משנה את הערך הנוכחי של:
ref.current = 5;
console.log(ref.current); // 5זה בגלל use השופט עצמו הוא אובייקט JavaScript רגיל, ולכן הוא מתנהג כמו אחד.
אתה גם לא צריך לדאוג לגבי הימנעות ממוטציה כשאתה עובד עם ר’. כל עוד האובייקט שאתה עושה מוטציה אינו used לעיבוד, ל-React לא אכפת מה אתה עושה עם ה-ref או התוכן שלו.
השופטים וה-DOM
אתה יכול להצביע על כל ערך. עם זאת, המקרה הנפוץ ביותר של use עבור ref הוא גישה לאלמנט DOM. לדוגמה, זה שימושי אם ברצונך למקד קלט באופן פרוגרמטי. כאשר אתה מעביר רפר למאפיין ref ב-JSX, כמו <div ref={myRef}>, React יכניס את האלמנט DOM התואם לתוך myRef.current. ברגע שהרכיב יוסר מה-DOM, React יעדכן את myRef.current להיות null. אתה יכול לקרוא עוד על זה ב-מניפולציה של DOM עם Refs.
Recap
- Refs הם פתח מילוט כדי להחזיק בערכים שאינם used לעיבוד. לא תזדקק להם לעתים קרובות.
- ref הוא אובייקט JavaScript רגיל עם מאפיין יחיד בשם
current, אותו ניתן לקרוא או להגדיר. - אתה יכול לבקש מ-React לתת לך שו”ת על ידי התקשרות ל-
useRefHook. - כמו state, שופטים מאפשרים לך לשמור מידע בין עיבוד מחדש של רכיב.
- שלא כמו state, הגדרת הערך
currentשל השופט לא מפעילה עיבוד מחדש. - אין לקרוא או לכתוב
ref.currentבמהלך העיבוד. זה הופך את הרכיב שלך לקשה לחזות.
Challenge 1 of 4: תקן קלט צ’אט שבור
הקלד הודעה ולחץ על “שלח”. תבחין שיש עיכוב של שלוש שניות לפני שתראה את ההודעה “נשלח!” עֵרָנִי. במהלך עיכוב זה, אתה יכול לראות כפתור “בטל”. לחץ עליו. כפתור ה”בטל” הזה אמור לעצור את ה”נשלח!” הודעה מלהופיע. הוא עושה זאת על ידי קריאה ל-clearTimeout עבור מזהה הזמן הקצוב שנשמר במהלך handleSend. עם זאת, גם לאחר לחיצה על “בטל”, ההודעה “נשלח!” עדיין מופיעה. מצא מדוע זה לא עובד ותקן את זה.
import { useState } from 'react'; export default function Chat() { const [text, setText] = useState(''); const [isSending, setIsSending] = useState(false); let timeoutID = null; function handleSend() { setIsSending(true); timeoutID = setTimeout(() => { alert('Sent!'); setIsSending(false); }, 3000); } function handleUndo() { setIsSending(false); clearTimeout(timeoutID); } return ( <> <input disabled={isSending} value={text} onChange={e => setText(e.target.value)} /> <button disabled={isSending} onClick={handleSend}> {isSending ? 'Sending...' : 'Send'} </button> {isSending && <button onClick={handleUndo}> Undo </button> } </> ); }