Menu

forwardRef ב-React: העברת refs, ו-ref כ-prop ב-19

forwardRef מאפשר לקומפוננטה לקבל ref מההורה שלה ולחבר אותו לצומת DOM שבתוכה. ב-React 19 קומפוננטות פונקציה מקבלות ref כ-prop רגיל, כך שקוד חדש כבר לא צריך forwardRef. ראו את שתי הגרסאות רצות, יחד עם useImperativeHandle וטיפוסי TypeScript.

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

forwardRef מאפשר לקומפוננטת הורה להעביר ref דרך הקומפוננטה שלכם לאלמנט DOM שבתוכה, כך שההורה יכול לקרוא על האלמנט הזה ל-focus(), scrollIntoView() ולמתודות דומות. ב-React 19 כבר לא צריך אותו: קומפוננטות פונקציה מקבלות ref כ-prop רגיל. forwardRef עדיין עובד, ותראו אותו ברוב הקוד הקיים, ולכן הדף הזה מציג את שתי הדרכים.

forwardRef מקבל פונקציית רינדור עם שני ארגומנטים: props, וה-ref שההורה העביר. ref לא נמצא בתוך props; הוא מגיע בנפרד. לחצו על Focus והקונסול מאשר ש-inputRef.current הוא צומת ה-<input> האמיתי.

הדרך של React 19: ref הוא prop

מאז React 19 קומפוננטת פונקציה מקבלת ref ב-props שלה כמו כל prop אחר. בלי עטיפה, בלי ארגומנט שני:

הקלידו משהו ולחצו על Log value: ההורה קורא את הטקסט הנוכחי של השדה ישר מצומת ה-DOM. מחקו את ref={ref} מה-<input> ולחצו על Focus: הקונסול מציג שגיאה, כי inputRef.current הוא עכשיו null.

forwardRef (כל הגרסאות)ה-prop ref (React 19 ומעלה)
איך הקומפוננטה מקבלת את ה-refארגומנט שני, (props, ref)props.ref
צריך עטיפהכןלא
עובד עם useImperativeHandleכןכן
סטטוסעובד, מתוכנן להוצאה משימושהדרך לכתוב קוד חדש

ref שהילד מתעלם ממנו נשאר null

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

הקונסול מציג Forgetful ref: null ו-Careful ref: INPUT. זו הסיבה הנפוצה ביותר ל-"ref.current is null" על קומפוננטה מותאמת. לפני React 19, גם Careful היה null אלא אם היה עטוף ב-forwardRef, כי ה-ref אף פעם לא הועבר לפונקציה.

useImperativeHandle: חשיפת API קטן יותר

מתן כל צומת ה-DOM להורה אומר שהוא יכול לעשות לו כל דבר: לשנות סגנונות, להסיר ילדים, לקרוא ערכים שהתכוונתם לשמור פרטיים. useImperativeHandle מאפשר לכם להחליט מה ref.current מכיל במקום:

הכפתור האחרון רושם ["focus", "clear"]: ההורה מקבל את שתי המתודות האלה ולא שום דבר אחר, לא את צומת השדה. הוסיפו לאובייקט מתודת select() וכפתור שקורא לה, וההורה מקבל בדיוק יכולת אחת נוספת.

הילד שומר ref משלו, inputRef, לשדה האמיתי, וה-handle עוטף אותו. הארגומנט השלישי הוא מערך תלויות, כמו זה של useEffect: ה-handle נבנה מחדש כשהערכים האלה משתנים.

השתמשו בזה במשורה. רוב מה שהורה רוצה (לפתוח, לסגור, להציג שגיאה) עדיף כ-props, כמו isOpen או error. מתודות אימפרטיביות מיועדות לפעולות שאין להן prop טבעי: פוקוס, גלילה, ניגון וידאו, התחלת אנימציה.

שימוש ב-ref גם בתוך הילד

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

function AutoGrowTextarea({ ref, ...props }) {
    const localRef = useRef(null);

    function setRefs(node) {
        localRef.current = node;
        if (typeof ref === 'function') ref(node);
        else if (ref) ref.current = node;
    }

    return <textarea ref={setRefs} {...props} />;
}

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

העברת ref דרך כמה שכבות

ref עובר קומפוננטה אחת בכל פעם. אם Form מרנדרת Field, שמרנדרת TextInput, שמרנדרת את ה-<input>, כל אחת מהקומפוננטות האלה צריכה להעביר את ref הלאה. ב-React 19 זה עוד prop אחד להעביר (<TextInput ref={ref} />); עם forwardRef, כל שכבה הייתה צריכה להיעטף. spread של props ({...props}) לא מעביר אותו בגרסאות ישנות, כי לפני React 19 ref אף פעם לא היה חלק מ-props.

קומפוננטות של design system הן המקום הרגיל שבו זה חשוב: Button, Input או Select שעוטפים אלמנט נייטיב צריכים להעביר את ה-ref שלהם, כדי שהאפליקציה שמשתמשת בהם תוכל לתת להם פוקוס, למדוד אותם, או להעביר אותם לספרייה שממקמת popovers.

מעבר מ-forwardRef

השינוי מכני: מסירים את העטיפה וקוראים את ref מ-props.

// Before
const Button = forwardRef(function Button({ variant, ...props }, ref) {
    return <button ref={ref} className={variant} {...props} />;
});

// After (React 19)
function Button({ variant, ref, ...props }) {
    return <button ref={ref} className={variant} {...props} />;
}

אין לחץ. forwardRef ממשיך לעבוד ב-React 19, וספרייה שצריכה לתמוך ב-React 18 חייבת לשמור עליו, כי React 18 לא מעבירה ref כ-prop. קומפוננטות class לא מושפעות: ref על קומפוננטת class עדיין מצביע על המופע של הקומפוננטה.

TypeScript

עם forwardRef, ארגומנטי הטיפוס מגיעים בסדר הזה: טיפוס ה-ref, ואז ה-props:

import { forwardRef } from 'react';

type FancyInputProps = { label: string };

const FancyInput = forwardRef<HTMLInputElement, FancyInputProps>(
    function FancyInput({ label }, ref) {
        return <input ref={ref} aria-label={label} />;
    }
);

ב-React 19, תנו ל-ref טיפוס כמו לכל prop אחר. ComponentProps<'input'> כבר כולל אותו:

import { useImperativeHandle, useRef, type ComponentProps, type Ref } from 'react';

function FancyInput(props: ComponentProps<'input'>) {
    return <input {...props} />;
}

type SearchHandle = { focus: () => void; clear: () => void };

function SearchBox({ ref }: { ref?: Ref<SearchHandle> }) {
    const inputRef = useRef<HTMLInputElement>(null);
    useImperativeHandle(ref, () => ({
        focus: () => inputRef.current?.focus(),
        clear: () => {
            if (inputRef.current) inputRef.current.value = '';
        },
    }));
    return <input ref={inputRef} />;
}

// In the parent
const searchRef = useRef<SearchHandle>(null);

הדף על useRef מכסה את ה-refs עצמם: גישה ל-DOM, ערכים שנשמרים בלי רינדור, ו-ref callbacks. לטיפוסים של קומפוננטות באופן כללי, ראו React עם TypeScript.

שאלות נפוצות

מה forwardRef עושה ב-React?

הוא עוטף קומפוננטת פונקציה כך ש-ref שההורה נותן מגיע לקומפוננטה כארגומנט שני, (props, ref). הקומפוננטה שמה את ה-ref הזה על צומת DOM, כך שההורה יכול לקרוא עליו למתודות כמו focus().

האם forwardRef הוצא משימוש ב-React 19?

עדיין לא, והוא עדיין עובד. React 19 מעבירה ref לקומפוננטות פונקציה כ-prop רגיל, כך שקוד חדש לא צריך אותו, וצוות React אמר שהוא מתכנן להוציא את forwardRef משימוש בגרסה עתידית.

למה ה-ref שלי הוא null על קומפוננטה מותאמת?

הקומפוננטה קיבלה את ה-ref אבל לא שמה אותו על אף אלמנט. חברו אותו לצומת DOM בפנים: <input ref={ref} />. לפני React 19 ה-ref לא הועבר בכלל אלא אם הקומפוננטה נעטפה ב-forwardRef.

למה משמש useImperativeHandle?

הוא מחליף את מה שההורה רואה ב-ref.current. במקום כל צומת ה-DOM מחזירים אובייקט עם רק המתודות שבוחרים, כמו focus ו-clear.

האם קומפוננטות class מקבלות ref כ-prop ב-React 19?

לא. ref על קומפוננטת class עדיין מצביע על המופע של הקומפוננטה. השינוי חל רק על קומפוננטות פונקציה.

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

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

להתחיל