Menu

ה-prop children ב-React: קומפוננטות עוטפות ו-slots

children הוא ה-prop שמחזיק את כל מה שמכניסים בין תגית הפתיחה לתגית הסגירה של קומפוננטה. למדו איך בונים איתו קומפוננטות עוטפות ו-layout, מעבירים כמה slots כ-props עם שם, משתמשים ב-children כפונקציה, ומתי ה-Children API הוא הכלי הלא נכון.

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

ב-React, children הוא ה-prop שמחזיק את כל מה שכותבים בין תגית הפתיחה לתגית הסגירה של קומפוננטה. קומפוננטה שמרנדרת {children} איפשהו בפלט שלה הופכת לעטיפה: היא מספקת את המסגרת, ומי שמשתמש בה מספק את התוכן.

לשני הכרטיסים יש אותה מסגרת, אותו padding ואותו סגנון כותרת, אבל כל אחד מחזיק תוכן שונה לגמרי. הוסיפו <Card title="Help"> שלישי עם קישור בפנים, והוא יקבל את אותה מסגרת בחינם.

איך children מגיע לקומפוננטה

JSX הופך תוכן מקונן ל-prop. שתי השורות האלה מייצרות את אותו אלמנט:

<Card title="Profile"><p>Ada</p></Card>

<Card title="Profile" children={<p>Ada</p>} />

כך ש-children הוא prop רגיל עם דרך מיוחדת להעביר אותו. עושים לו destructuring כמו לכל prop אחר (function Card({ children })) או קוראים אותו בתור props.children. הדף על props מכסה props באופן כללי.

מה שמגיע ב-children תלוי במה שכתבתם בין התגיות. ילד אחד מגיע כמו שהוא. כמה ילדים מגיעים כמערך. טקסט מגיע כמחרוזת, וכלום מגיע כ-undefined.

הקונסול מציג object, array of 2, string ו-undefined. לרוב לא צריך לדאוג לזה: רינדור של {children} מטפל בכל המקרים האלה, ו-null, undefined, true ו-false לא מרנדרים כלום. הצורה חשובה רק אם מנסים לבדוק או לשנות את children, ומפני זה בדיוק מזהיר הסעיף האחרון.

קומפוננטות layout

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

function PageLayout({ children }) {
    return (
        <div className="page">
            <Header />
            <main className="page-content">{children}</main>
            <Footer />
        </div>
    );
}

function AboutPage() {
    return (
        <PageLayout>
            <h1>About us</h1>
            <p>We teach people to code.</p>
        </PageLayout>
    );
}

ל-PageLayout לא אכפת מה יש בדף "אודות". ההפרדה הזו היא כל הרעיון: משנים את הכותרת פעם אחת וכל דף שעטוף ב-layout מקבל את השינוי.

כמה slots עם props בעלי שם

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

מעטפת המודאל שולטת בגבולות, ב-padding ובסדר של שלושת האזורים, ומי שמשתמש בה ממלא כל אחד מהם. frameworks אחרים קוראים לזה slots; ב-React אלה פשוט props. ה-<>...</> שמסביב לשני הכפתורים הוא fragment, שמקבץ אותם בלי להוסיף אלמנט נוסף. דיאלוג אמיתי גם היה מתרנדר לתוך document.body עם portal כדי לשבת מעל הדף.

העבירו את ה-<p> לתוך ה-prop title וטקסט הגוף יופיע בכותרת: הקומפוננטה מחליטה לאן כל prop הולך, ומי שמשתמש בה מחליט רק מה נכנס אליו.

children כפונקציה

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

Toggle אחד מצייר כפתור והשני checkbox, מאותה לוגיקה. render props היו הדרך העיקרית לשתף לוגיקה עם state לפני ההוקים. היום הוק מותאם אישית כמו useToggle() בדרך כלל קריא יותר, אבל עדיין תראו render props בספריות של רשימות, טפסים ואנימציה.

ה-Children API ו-cloneElement

React מייצאת אובייקט Children עם פונקציות עזר למעבר על children: Children.map, Children.forEach, Children.count, Children.toArray ו-Children.only. יחד עם cloneElement, שמעתיק אלמנט עם props חדשים, הם מאפשרים להורה לבדוק ולשנות את מה שקיבל. צוות React מגדיר אותם כ-API מסוג legacy: הם עדיין עובדים, אבל קוד חדש צריך להימנע מהם.

הסיבה היא שהם רואים רק את האלמנטים שנכתבו ישירות בין התגיות. הם לא יכולים לראות מה האלמנטים האלה מרנדרים.

הרשימה מציגה ארבעה פריטים, אבל הקונסול אומר Children.count = 3, כי TwoMore הוא ילד אחד לא משנה כמה פריטים הוא מרנדר. cloneElement גם מעביר את ה-prop style ל-TwoMore, שמתעלם ממנו, ולכן הפריטים השלישי והרביעי לא מפוספסים. כל מי שעושה refactor לכמה אלמנטי <li> לתוך קומפוננטה שובר את ההורה בלי לגעת בו.

הפתרון הוא להפסיק לחטט ב-children ולהעביר נתונים במקום:

function NumberedList({ items }) {
    return (
        <ol>
            {items.map((item, i) => (
                <li key={item.id} style={{ color: i % 2 ? 'gray' : 'black' }}>
                    {item.label}
                </li>
            ))}
        </ol>
    );
}

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

טיפוסים ל-children ב-TypeScript

ב-TypeScript, תנו ל-children את הטיפוס React.ReactNode. הוא מכסה את כל מה ש-React יודעת לרנדר: אלמנטים, מחרוזות, מספרים, מערכים שלהם, null, undefined ובוליאנים.

import type { ReactNode } from 'react';

type CardProps = {
    title: string;
    children: ReactNode;
};

function Card({ title, children }: CardProps) {
    return (
        <section>
            <h3>{title}</h3>
            {children}
        </section>
    );
}

ל-render prop, תנו את הטיפוס של הפונקציה שהוא: children: (on: boolean, toggle: () => void) => ReactNode. הפכו את children לאופציונלי (children?: ReactNode) כשהקומפוננטה הגיונית גם כשהיא ריקה.

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

שוכחים לרנדר את children. אם קומפוננטה מקבלת children אבל אף פעם לא שמה {children} בפלט שלה, התוכן פשוט נעלם. שום דבר לא מזהיר אתכם.

קוראים ל-children כמו לפונקציה כשהוא לא פונקציה. children() עובד רק כשמי שמשתמש בקומפוננטה העביר פונקציה. אם קומפוננטה מצפה ל-render prop, ציינו את זה בשם שלה או בתיעוד, או קבלו prop עם שם כמו render כדי שהכוונה תהיה ברורה.

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

שאלות נפוצות

מה זה props.children ב-React?

זה התוכן שנכתב בין תגית הפתיחה לתגית הסגירה של קומפוננטה. ב-<Card><p>Hi</p></Card>, הפונקציה Card מקבלת את ה-<p> בתור props.children ומחליטה איפה לרנדר אותו.

האם children הוא prop מיוחד?

רק באופן שבו מעבירים אותו. JSX מכניס את התוכן המקונן ל-prop בשם children, אבל בתוך הקומפוננטה זה prop רגיל. אפשר גם להעביר אותו במפורש, כמו ב-<Card children={<p>Hi</p>} />, וזה עובד אותו דבר.

איך מעבירים לקומפוננטה יותר מבלוק תוכן אחד?

משתמשים ב-props עם שם בשביל הבלוקים הנוספים. prop יכול להחזיק JSX בדיוק כמו children, כך ש-<Modal title={<h2>Delete?</h2>} footer={<button>OK</button>}>Body</Modal> נותן לקומפוננטה שלושה slots.

כדאי להשתמש ב-Children.map וב-cloneElement?

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

מה הטיפוס של children ב-TypeScript?

השתמשו ב-React.ReactNode. הוא מכסה את כל מה ש-React יודעת לרנדר: אלמנטים, מחרוזות, מספרים, מערכים, null, undefined ובוליאנים.

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

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

להתחיל