Menu

Prop Drilling ב-React: מה זה ואיך להימנע מזה

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

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

prop drilling הוא העברת prop דרך קומפוננטות שלא משתמשות בו, רק כדי שקומפוננטה רחוקה יותר למטה בעץ תוכל להשתמש בו. כאן user עובר מ-App דרך Page ו-Sidebar כדי להגיע ל-Avatar, הקומפוננטה היחידה שקוראת אותו.

ארבע רמות, ורק לאחרונה אכפת מ-user. ה-Console מראה ש-Page ו-Sidebar רצות בשביל ערך שהן אף פעם לא מציגות. לחצו על הכפתור והן רושמות שוב.

למה זה כואב

שתיים או שלוש רמות זה בסדר. prop drilling הופך לבעיה כשהעץ גדל:

  • קומפוננטות הביניים נושאות props שהן לא משתמשות בהם. ל-Page ול-Sidebar יש עכשיו prop user בחתימה, ואי אפשר לקרוא אותן בלי לתהות בשביל מה הוא.
  • כל שינוי נוגע בכל שכבה. הוסיפו user.avatarUrl או ערך שני כמו onLogout, ותערכו כל קומפוננטה בדרך, לא רק את שני הקצוות.
  • הזזת קומפוננטה שוברת את השרשרת. הזיזו את Avatar לענף אחר ותצטרכו להשחיל את user במסלול חדש.
  • קל לאבד prop. שכחו להעביר אותו בשכבה אחת והקומפוננטה העמוקה מקבלת undefined, בלי שגיאה בשכבה שאיבדה אותו.

נסו: הוסיפו ב-App פונקציית onLogout ש-Avatar אמורה לקרוא לה. תערכו את כל ארבע הקומפוננטות.

פתרון 1: קומפוזיציה עם children

זה הפתרון שאנשים מדלגים עליו. במקום להעביר נתונים מטה כדי שקומפוננטה עמוקה תוכל לרנדר איתם, תנו לקומפוננטה שמחזיקה את הנתונים לרנדר בעצמה את הקומפוננטה העמוקה, והעבירו את האלמנט המוכן מטה בתור children (או כל prop אחר). שכבות הביניים מרנדרות slot ואף פעם לא רואות את user.

Page ו-Sidebar כבר לא יודעות שקיים משתמש. App יוצרת את Avatar במקום שבו user חי ומעבירה אותו מטה כאלמנט מוכן. קומפוננטות layout כאלה הופכות לניתנות לשימוש חוזר, והוספת onLogout היא עכשיו שינוי רק ב-App וב-Avatar. שתיהן עדיין רושמות בכל לחיצה, כי App מעבירה להן אלמנט חדש בכל פעם שהיא מתרנדרת: קומפוזיציה מפשטת את הקוד, לא את מספר הרינדורים. הדף על children מכסה עוד דפוסי slots.

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

פתרון 2: קונטקסט

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

Avatar ו-Greeting קוראות את המשתמש משני עומקים שונים, ו-Page ו-Sidebar לא מקבלות props. הוסיפו קורא שלישי בכל מקום מתחת ל-Page וזה עובד בלי לגעת בשאר. המחיר: זרימת הנתונים כבר לא גלויה ב-props, וכל קורא מתרנדר כשהערך משתנה. הדף על useContext מכסה providers, ערכי ברירת מחדל ואת הרינדורים האלה.

פתרון 3: ספריית state

ל-state גדול של אפליקציה שהרבה קומפוננטות קוראות ושמשתנה הרבה (עגלה, עורך מסמכים, נתונים חיים), ספריית state כמו Redux Toolkit, Zustand או Jotai מאפשרת לכל קומפוננטה להירשם רק לחלק שהיא משתמשת בו. זה חוסך גם את ה-drilling וגם את המחיר של "כל צרכן מתרנדר" בקונטקסט גדול אחד. זו גם עוד תלות ועוד סט של מושגים, אז פנו אליה כששני הפתרונות הראשונים מפסיקים להספיק, לא כדי לתקן prop שעובר שלוש רמות.

// Zustand, for comparison (not available in the editor here)
const useCart = create((set) => ({
    items: [],
    add: (item) => set((s) => ({ items: [...s.items, item] })),
}));

function CartCount() {
    const count = useCart((s) => s.items.length); // renders only when the count changes
    return <span>{count}</span>;
}

מתי prop drilling זה בסדר

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

שאלות נפוצות

מה זה prop drilling ב-React?

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

האם prop drilling זה רע?

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

איך נמנעים מ-prop drilling?

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

האם קונטקסט הוא הפתרון היחיד ל-prop drilling?

לא, ולעיתים קרובות הוא לא הכי טוב. קומפוזיציה עם children מבטלת את ה-drilling בלי להוסיף קונטקסט, והיא שומרת את זרימת הנתונים גלויה בקוד.

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

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

להתחיל