Menu

ההוק useRef ב-React: refs ל-DOM וערכים שנשמרים

useRef נותן לקומפוננטה קופסה, { current }, ששומרת את התוכן שלה בין רינדורים בלי לגרום לרינדור כשמשנים אותה. השתמשו בו כדי להגיע לצומת DOM (פוקוס, גלילה, מדידה) או כדי לשמור מזהה טיימר או כל ערך שהמסך לא מציג.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

useRef מחזיר אובייקט, { current: initialValue }, ש-React שומרת לאורך כל חיי הקומפוננטה. שינוי של ref.current לא מרנדר שום דבר מחדש. זה הופך את ה-refs למתאימים לשתי עבודות: החזקת צומת DOM כדי לקרוא עליו למתודות כמו focus(), וזכירת ערך בין רינדורים שהמסך לא צריך להציג.

העברת אובייקט ה-ref ל-ref={inputRef} אומרת ל-React לשים את צומת ה-DOM של ה-<input> ב-inputRef.current. שנו את focus() ל-select(), הקלידו משהו ולחצו שוב: הטקסט מסומן.

התחביר

const myRef = useRef(initialValue);

myRef.current;          // read
myRef.current = value;  // write, no re-render
  • initialValue משמש רק ברינדור הראשון. ב-refs ל-DOM הוא null.
  • React מחזירה את אותו אובייקט בכל רינדור. רק current משתנה.
  • קריאה וכתיבה של current הן JavaScript רגיל. React לא עוקבת אחריהן.

ערך שלא גורם לרינדורים

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

לחצו על Ref + 1 כמה פעמים. הקונסול מראה ש-ref.current עולה, אבל בדף עדיין כתוב 0 ולא נרשם רינדור. עכשיו לחצו על State + 1: הקומפוננטה מתרנדרת, ומונה ה-ref על המסך קופץ למספר שה-ref כבר החזיק. ה-ref שמר את הערך שלו כל הזמן; ל-React פשוט לא הייתה סיבה לצייר אותו.

refs מול state

useStateuseRef
מחזיר[value, setValue]{ current: value }
שינוי שלומרנדר את הקומפוננטה שובלא עושה שום דבר גלוי
מתי אפשר לקרוא את הערך החדשברינדור הבאמיד
משנים אותו עםה-setterהשמה, ref.current = x
משמש לכל מה שהמסך מציגמזהי טיימרים, צמתי DOM, ערכים קודמים, דגלים

אם אתם לא בטוחים, שאלו אם ה-JSX קורא את הערך. אם כן, הוא שייך ל-state.

שמירת מזהה טיימר

setInterval מחזיר מזהה שצריך אחר כך כדי לעצור את הטיימר. זה מתאים מצוין ל-ref: ה-handler של Stop צריך אותו, אבל המסך אף פעם לא מציג אותו, ושמירה שלו במשתנה רגיל הייתה מאבדת אותו ברינדור הבא.

החליפו את useRef(null) ב-let intervalRef = { current: null }; ונסו Start, חכו שתי שניות, ואז Stop: הטיימר ממשיך. כל tick מרנדר את הקומפוננטה, מה שיוצר אובייקט חדש, ולכן Stop לא מנקה כלום.

זכירת הערך הקודם

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

לחצו על green, ואז על blue: הקונסול רושם changed from red to green ו-changed from green to blue. לחיצה על הצבע שכבר נבחר לא רושמת כלום, כי האפקט רץ רק כש-color משתנה.

עבודה עם ה-DOM

refs הם הדרך לקרוא ל-APIs של הדפדפן שאין להם prop ב-React: focus(), select(), scrollIntoView(), play() על וידאו, או קריאת גודל עם getBoundingClientRect().

const lastItemRef = useRef(null);

function scrollToLast() {
    lastItemRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}

<li ref={lastItemRef}>Last item</li>

ref.current הוא null בזמן הרינדור הראשון, כי React עוד לא יצרה את צומת ה-DOM, ושוב אחרי שהאלמנט מוסר. קראו אותו ב-event handlers ובאפקטים, שבהם הצומת קיים. כדי למדוד אלמנט ולמקם משהו לפני שהדפדפן מצייר, קראו את ה-ref ב-useLayoutEffect.

אל תקראו ואל תכתבו את ref.current בזמן הרינדור

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

function Bad() {
    const count = useRef(0);
    count.current++;              // writing during render
    return <p>{count.current}</p>; // reading during render
}

קראו וכתבו refs ב-event handlers ובאפקטים. החריג היחיד הוא אתחול עצל, שבו ממלאים את ה-ref פעם אחת ברינדור הראשון:

const playerRef = useRef(null);
if (playerRef.current === null) {
    playerRef.current = new VideoPlayer(); // runs once
}

(דמו מונה ה-ref שלמעלה קורא את clicks.current ב-JSX בכוונה, כדי להראות שהמסך לא עוקב אחריו. אל תעתיקו את זה לקוד אמיתי.)

ref callbacks

במקום אובייקט ref, אפשר להעביר פונקציה ל-ref. React קוראת לה עם צומת ה-DOM כשהאלמנט נוסף. ב-React 19 הפונקציה יכולה להחזיר cleanup, ש-React קוראת לו כשהאלמנט מוסר. זה שימושי ברשימה, שבה אי אפשר לקרוא ל-useRef פעם אחת לכל פריט:

בטעינה הקונסול רושם שורת attached אחת לכל פריט. לחצו על Remove last ומופיע רק detached Cheese. trackNode מוגדרת מחוץ לקומפוננטה, ולכן היא אותה פונקציה בכל רינדור ו-React משאירה את שאר הפריטים בשקט. כתבו אותה inline בתור ref={(node) => { ... }} במקום, וכל לחיצה מנתקת ומחברת מחדש כל פריט, כי כל רינדור מעביר פונקציה חדשה.

העברת ref לקומפוננטה משלכם

ב-React 19, ref הוא prop רגיל בקומפוננטות פונקציה. ההורה יוצר את ה-ref, והילד שם אותו על אלמנט ה-DOM שהוא רוצה לחשוף:

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

טעויות נפוצות

הצגת ערך של ref ב-JSX. המסך לא עוקב אחריו. אם המשתמש צריך לראות את הערך, השתמשו ב-state.

שימוש ב-ref.current כתלות של אפקט. [ref.current] קורא את הערך בזמן הרינדור ו-React לא יודעת מתי הוא משתנה, ולכן האפקט לא רץ מחדש כשזה קורה. השתמשו ב-ref callback כדי להגיב לחיבור של צומת.

קריאת ref של DOM בזמן הרינדור הראשון. הוא null עד ש-React יצרה את הצומת. קראו אותו באפקט או ב-handler.

יצירת אובייקט חדש במקום להשתמש בהוק. const ref = { current: null } בגוף הקומפוננטה הוא אובייקט חדש בכל רינדור. רק useRef נותן לכם את אותו אובייקט בכל פעם.

שאלות נפוצות

מה useRef מחזיר?

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

מה ההבדל בין useRef ל-useState?

שינוי state מרנדר את הקומפוננטה שוב; שינוי ref.current לא. השתמשו ב-state לערכים שהמסך מציג, וב-ref לערכים שרק ה-event handlers והאפקטים שלכם צריכים, כמו מזהה טיימר או צומת DOM.

למה ref.current הוא null?

React קובעת את ref.current לצומת ה-DOM אחרי שהיא יוצרת את הצומת, בשלב ה-commit. בזמן הרינדור הראשון הוא עדיין null, והוא שוב null אחרי שהאלמנט מוסר. קראו אותו ב-event handler או באפקט.

האם שינוי של ref.current גורם לרינדור מחדש?

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

איך מעבירים ref לקומפוננטת ילד?

ב-React 19 קומפוננטת פונקציה מקבלת ref כ-prop רגיל, אז מעבירים <MyInput ref={inputRef} /> והילד שם אותו על ה-<input> שלו. גרסאות ישנות יותר דרשו forwardRef.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל