העברת רפרנסים

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

העברת רפרנסים לקומפוננטות ב-DOM

נקח לדוגמא קומפוננטת כפתור FancyButton שמרנדרת אלמנט כפתור פשוט בתוך ה-DOM:

function FancyButton(props) {
  return (
    <button className="FancyButton">
      {props.children}
    </button>
  );
}

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

למרות שכימוס כזה רצויה בקומפוננטות ברמת האפליקציה כמו FeedStory או Comment, היא יכולה להפוך לפחות נוחה בזמן שימוש בקומפוננטות ״עלה״ שנועדות לשימוש חוזר כמו FancyButton או MyTextInput. השימוש בקומפוננטות האלה בדרך כלל חוזר על עצמו פעמים רבות באפליקציה, בדומה לשימוש ב- button ו- input, ולפעמים אין ברירה אלא לגשת ישירות לאלמנטי ה-DOM שלהן כדי לשנות פוקוס, בחירה או אנימציה.

העברת רפרנסים היא פיצ׳ר שמאפשר לקומפוננטות לקחת הפניה (רפרנס) שהם קיבלו ולהעביר אותה הלאה לקומפוננטת ילד

בדוגמא הנ״ל, הכפתור FancyButton משתמש ב-React.forwardRef כדי לקבל את הרפרנס שהועבר אליו, ואז מעביר אותו לאלמנט הכפתור שהוא מרנדר ב-DOM:

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// :DOM-עכשיו תוכלו לקבל רפרנס ישירות לכפתור ב
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

בצורה זו, קומפוננטות שמשתמשות ב- FancyButton יכולות לקבל רפרנס לצומת הכפתור עצמה ולהשתמש בה בעת הצורך - כאילו הן השתמשו בכפתור ב-DOM באופן ישיר.

להלן הסבר מפורט של מה שקורה בדוגמא לעיל:

  1. אנחנו יוצרים רפרנס של React ע״י שימוש ב- React.createRef ושמים אותו במשתנה ה-ref.
  2. אנחנו מעבירים את הרפרנס למטה ל- FancyButton בעזרת השימוש במאפיין ה-JSX: <FancyButton ref={ref}>
  3. React מעבירה את הרפרנס לפונקציית ה- (props, ref) => ... כפרמטר השני בתוך forwardRef.
  4. אנחנו מעבירים את ארגומנט הרפרנס למטה לכפתור על ידי שימוש במאפיין ה-JSX: <button ref={ref}>
  5. כשהרפרנס מחובר, ref.current יצביע לצומת הכפתור ב-DOM.

הערה

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

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

הערה למתחזקי ספריות קומפוננטות

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

השימוש המותנה ב- React.forwardRef כשהוא קיים גם כן לא מומלץ מאותה הסיבה: הוא משנה את התנהגות הספרייה ויכול לשבור את האפליקציות שמשתמשות בה כשהן משדגרות את גרסת ה-React עצמה.

העברת רפרנסים בקומפוננטות מסדר גבוה יותר

הטכניקה הזאת יכולה להיות שימושית ביותר בשימוש עם קומפוננטות מסדר גבוה יותר (שידועות גם כ-HOCs). נתחיל עם דוגמא של HOC שמתעדת props של קומפוננטות ל-console:

function logProps(WrappedComponent) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  }

  return LogProps;
}

ה-HOC “logProps” מעביר את כל ה-props דרכו לקומפוננטה שהוא עוטף, ולכן הפלט יהיה זהה. לדוגמא, אפשר להשתמש ב-HOC הזה כדי לתעד את כל ה-props שמועברים לקומפוננטת כפתור ה-”fancy button” שלנו:

class FancyButton extends React.Component {
  focus() {
    // ...
  }

  // ...
}

// LogProps-נייצא את ה,FancyButton-במקום לייצא את ה.
// FancyButton אבל הוא עדיין ירנדר
export default logProps(FancyButton);

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

זה אומר שרפרנסים שיועדו לקומפוננטת ה-FancyButton שלנו יהיו דווקא מקושרים לקומפוננטת ה- LogProps:

import FancyButton from './FancyButton';

const ref = React.createRef();

// .LogProps שיבאנו היא הקומפוננטה מסדר גבוה יותר FancyButton-קומפוננטת ה
// LogProps-למרות שהפלט ירונדר בצורה זהה, הרפרנס שלנו יצביא ל
// !הפנימית FancyButton-במקום לקומפוננטת ה
// ref.current.focus()-ולכן אנחנו לא יכולים לקרוא לדוגמא ל
<FancyButton
  label="Click Me"
  handleClick={handleClick}
  ref={ref}
/>;

למזלנו, אפשר להעביר את הרפרנס בצורה מפורשת לקומפוננטה הפנימית FancyButton בעזרת הממשק React.forwardRef. React.forwardRef מקבל פונקציית רינדור עם פרמטרים props ו- ref ומחזיר צומת React. לדוגמא:

function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      const {forwardedRef, ...rest} = this.props;

      // כרפרנס "forwardedRef" prop-ניישם את ה
      return <Component ref={forwardedRef} {...rest} />;
    }
  }

  // React.forwardRef סופק על ידי "ref" שימו לב שהפרמטר השני
  // רגיל prop כמו LogProps-אנחנו יכולים להעביר אותו הלאה ל
  // ולקשר אותו לקומפוננטה "forwardedRef" לדוגמא
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}

הצגת שם מותאים בכלי פיתוח

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

לדוגמא, הקומפוננטה הבאה תוצג כ: ”ForwardRef” בכלי הפיתוח של React:

const WrappedComponent = React.forwardRef((props, ref) => {
  return <LogProps {...props} forwardedRef={ref} />;
});

אם ניתן שם לפונקציית הרינדור, כלי הפיתוח יציגו גם אותו (לדוגמא ”ForwardRef(myFunction)”):

const WrappedComponent = React.forwardRef(
  function myFunction(props, ref) {
    return <LogProps {...props} forwardedRef={ref} />;
  }
);

אפשר לציין את ה-displayName של הפונקצייה בצורה שתכלול את הקומפוננטה העטופה:

function logProps(Component) {
  class LogProps extends React.Component {
    // ...
  }

  function forwardRef(props, ref) {
    return <LogProps {...props} forwardedRef={ref} />;
  }

  // ניתן לקומפוננטה שם יותר מועיל שיוצג בכלי הפיתוח
  // "ForwardRef(logProps(MyComponent))" לדוגמא
  const name = Component.displayName || Component.name;
  forwardRef.displayName = `logProps(${name})`;

  return React.forwardRef(forwardRef);
}