Menu

הרמת state למעלה ב-React: שיתוף state בין קומפוננטות

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

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

הרמת state למעלה (lifting state up) פירושה להוציא state מהקומפוננטות שצריכות לשתף אותו ולהעביר אותו להורה המשותף הקרוב ביותר שלהן. ההורה מחזיק את הערך, מעביר אותו מטה כ-prop, ומעביר פונקציה שהילדים קוראים לה כדי לשנות אותו, כך שכל ילד מתרנדר מעותק אחד של ה-state.

לחצו על "Show" באחד הפאנלים: הוא נפתח והפאנל שהיה פתוח נסגר. רק פאנל אחד יכול להיות פתוח בכל רגע כי הפאנלים לא מחליטים על זה בעצמם. App מחזיקה את openIndex, וכל Panel מקבל רק את isOpen ופונקציית onOpen.

הבעיה: state שצריך להסכים

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

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

הרמת state בשלושה צעדים

הפיכת הדוגמה השנייה לראשונה דורשת שלוש עריכות.

  1. הסירו את ה-state מהילד. מחקו את useState ב-Panel וקראו את isOpen מ-props במקום. הילד כבר לא מחליט אם הוא פתוח.
  2. העבירו מההורה את הערך ודרך לשנות אותו. Panel מקבל את isOpen ו-callback בשם onOpen. הילד קורא ל-onOpen() כשלוחצים על הכפתור שלו; הוא לא יודע מה ההורה עושה עם זה.
  3. הוסיפו את ה-state להורה המשותף. App מצהירה על openIndex והופכת אותו ל-props לכל פאנל: isOpen={openIndex === 1} ו-onOpen={() => setOpenIndex(1)}.

ההורה המשותף הקרוב ביותר הוא הקומפוננטה הנמוכה ביותר שמרנדרת את כל הקומפוננטות שצריכות את ה-state. כאן זו App. אם הפאנלים היו יושבים בתוך קומפוננטת Faq, ה-state היה הולך ל-Faq, לא גבוה יותר.

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

מקור אמת יחיד

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

הקלידו באחד השדות והשני עוקב. ה-state הוא עובדה אחת: המספר שהמשתמש הקליד אחרון ובאיזו סקאלה. השדה השני מחושב ממנו בזמן הרינדור, כך שהשדה שבו אתם מקלידים תמיד שומר בדיוק את מה שהקלדתם. שנו את ה-state ההתחלתי ל-{ value: '212', scale: 'f' } וההודעה מתחת לשדות מתחלפת ל-"Water boils.".

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

העברת ה-setter מטה

ילד יכול לשנות את ה-state של ההורה רק דרך פונקציה שההורה נותן לו. אפשר להעביר את ה-setter עצמו (onSelect={setColor}) או פונקציה שעושה יותר (onOpen={() => setOpenIndex(1)}). מתן שם בסגנון אירוע ל-prop, כמו onSelect או onChange, במקום setSelected, משאיר את הילד לא מודע לאופן שבו ההורה שומר את הערך, כך שההורה יכול לשנות את זה אחר כך בלי לגעת בילד.

ColorPicker ו-Preview אף פעם לא מדברות זו עם זו. הבורר מדווח למעלה על בחירה, App שומרת אותה, והערך החדש זורם מטה לשתיהן.

מתי לא להרים state

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

הקלידו בתיבה ושימו לב לקונסול: רק SearchBox מתרנדרת בכל הקשה. עכשיו העבירו את query למעלה ל-App והעבירו אותו ל-SearchBox כ-props. אז כל הקשה רושמת גם את ProductList, למרות שהרשימה לא משתמשת בשאילתה. אם הרשימה הייתה מסננת לפי השאילתה, ההרמה הייתה ההחלטה הנכונה, כי אז שתי הקומפוננטות היו תלויות באותו ערך.

השאלה שצריך לשאול היא "מי צריך לקרוא את הערך הזה?". אם התשובה היא קומפוננטה אחת, ה-state נשאר שם. אם כמה, הוא הולך להורה המשותף הקרוב ביותר שלהן.

כשההרמה מגזימה

לפעמים ההורה המשותף הקרוב ביותר נמצא גבוה בעץ, והערך צריך לעבור דרך כמה קומפוננטות שרק מעבירות אותו הלאה. זה prop drilling. כמה שכבות של props זה בסדר וקל לעקוב. כשאותו ערך עובר דרך הרבה שכבות, או שכמעט כל קומפוננטה צריכה אותו (המשתמש המחובר, ערכת הנושא, השפה), קראו אותו עם useContext במקום להעביר אותו ידנית. קונטקסט משנה את הדרך שבה הערך מגיע לילדים; ה-state עצמו עדיין חי בהורה אחד, כך שהוא עדיין מורם.

שאלות נפוצות

מה פירוש הרמת state למעלה ב-React?

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

איך שתי קומפוננטות אחיות משתפות state ב-React?

אחים לא יכולים לקרוא את ה-state זה של זה. שימו את ה-state בהורה המשותף שלהם, העבירו את הערך לשניהם, והעבירו setter (או handler כמו onChange) לזה שמשנה אותו. אז שני האחים מתרנדרים מאותו ערך.

איך קומפוננטת ילד מעדכנת את ה-state של ההורה?

ההורה מעביר פונקציה כ-prop, למשל onSelect={setSelected} או onSelect={(id) => setSelected(id)}, והילד קורא לה. ה-state נשאר בהורה; הילד רק מבקש את השינוי.

מתי לא כדאי להרים state למעלה?

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

מה החלופה להרמת state רחוק מדי?

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

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

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

להתחיל