cloneElement
cloneElement מאפשר לך ליצור אלמנט React חדש באמצעות אלמנט אחר כנקודת התחלה.
const clonedElement = cloneElement(element, props, ...children)הפניה
cloneElement(element, props, ...children)
התקשר ל-cloneElement כדי ליצור אלמנט React המבוסס על element, אך עם props וchildren שונים:
import { cloneElement } from 'react';
// ...
const clonedElement = cloneElement(
<Row title="Cabbage">
Hello
</Row>,
{ isHighlighted: true },
'Goodbye'
);
console.log(clonedElement); // <Row title="Cabbage" isHighlighted={true}>Goodbye</Row>פרמטרים
-
element: הארגומנטelementחייב להיות אלמנט React חוקי. לדוגמה, זה יכול להיות צומת JSX כמו<Something />, תוצאה של קריאה ל-createElement, או תוצאה של קריאה אחרת שלcloneElement. -
props: הארגומנטpropsחייב להיות אובייקט אוnull. אם תעבור אתnull, האלמנט המשובט ישמור על כל ה-element.propsהמקורי. אחרת, עבור כל אביזר באובייקטprops, האלמנט המוחזר “יעדיף” את הערך מ-propsעל פני הערך מ-element.props. שאר ה-props יתמלא מה-element.propsהמקורי. אם תעבור אתprops.keyאוprops.ref, הם יחליפו את המקוריים. -
אופציונלי
...children: אפס צמתים צאצאים או יותר. הם יכולים להיות כל צמתים React, כולל אלמנטים React, מחרוזות, מספרים, פורטלים, צמתים ריקים (null,undefined,trueו-false), ומערכים של nodes.9___ אם לא תעביר שום ארגומנט...children, ה-element.props.childrenהמקורי יישמר.
מחזירה
cloneElement מחזיר אובייקט אלמנט React עם כמה מאפיינים:
type: זהה ל-element.type.props: התוצאה של מיזוג רדוד שלelement.propsעם ה-propsהמכריע שעברת.ref: ה-element.refהמקורי, אלא אם כן הוא נדחק על ידיprops.ref.key: ה-element.keyהמקורי, אלא אם כן הוא נדחק על ידיprops.key.
בדרך כלל, אתה תחזיר את הרכיב מהרכיב שלך או תהפוך אותו לילד של אלמנט אחר. למרות שאתה יכול לקרוא את המאפיינים של האלמנט, עדיף להתייחס לכל אלמנט כאטום לאחר יצירתו, ולעבד אותו רק.
אזהרות
-
שיבוט של אלמנט אינו משנה את האלמנט המקורי.
-
עליך להעביר ילדים כארגומנטים מרובים ל-
cloneElementרק אם כולם ידועים סטטית, כמוcloneElement(element, null, child1, child2, child3). אם הילדים שלכם דינמיים, העבר את כל המערך כארגומנט השלישי:cloneElement(element, null, listItems). זה מבטיח ש-React תזהיר אותך על חסריםkeys עבור כל רשימות דינמיות. עבור רשימות סטטיות זה לא הכרחי כיuse הם אף פעם לא מסדרים מחדש. -
cloneElementמקשה על המעקב אחר זרימת הנתונים, אז נסה את חלופות במקום זאת.
שימוש
עקיפת props של אלמנט
כדי לעקוף את props של רכיב React כלשהו, העבר אותו ל-cloneElement עם props שברצונך לעקוף:
import { cloneElement } from 'react';
// ...
const clonedElement = cloneElement(
<Row title="Cabbage" />,
{ isHighlighted: true }
);כאן, הרכיב המשובט שנוצר יהיה <Row title="Cabbage" isHighlighted={true} />.
בואו נעבור על דוגמה כדי לראות מתי היא מלאה use.
תארו לעצמכם רכיב List שמעבד את children שלו כרשימה של שורות לבחירה עם כפתור “הבא” שמשנה איזו שורה נבחרה. הרכיב List צריך לעבד את ה-Row שנבחר בצורה שונה, אז הוא משכפל כל ילד <Row> שהוא קיבל, ומוסיף isHighlighted: true או isHighlighted: false תוספת נוספת:
export default function List({ children }) {
const [selectedIndex, setSelectedIndex] = useState(0);
return (
<div className="List">
{Children.map(children, (child, index) =>
cloneElement(child, {
isHighlighted: index === selectedIndex
})
)}נניח שה-JSX המקורי שהתקבל על-ידי List נראה כך:
<List>
<Row title="Cabbage" />
<Row title="Garlic" />
<Row title="Apple" />
</List>על ידי שיבוט ילדיו, ה-List יכול להעביר מידע נוסף לכל Row בפנים. התוצאה נראית כך:
<List>
<Row
title="Cabbage"
isHighlighted={true}
/>
<Row
title="Garlic"
isHighlighted={false}
/>
<Row
title="Apple"
isHighlighted={false}
/>
</List>שימו לב כיצד לחיצה על “הבא” מעדכנת את ה-state של ה-List, ומדגישה שורה אחרת:
import { Children, cloneElement, useState } from 'react'; export default function List({ children }) { const [selectedIndex, setSelectedIndex] = useState(0); return ( <div className="List"> {Children.map(children, (child, index) => cloneElement(child, { isHighlighted: index === selectedIndex }) )} <hr /> <button onClick={() => { setSelectedIndex(i => (i + 1) % Children.count(children) ); }}> Next </button> </div> ); }
לסיכום, ה-List שיבט את האלמנטים <Row /> שקיבל והוסיף להם אבזר נוסף.
חלופות
העברת נתונים עם אבזר עיבוד
במקום להשתמש ב-cloneElement, שקול לקבל מאפיין עיבוד כמו renderItem. כאן, List מקבל renderItem בתור אביזר. List קורא ל-renderItem עבור כל פריט ומעביר את isHighlighted כטיעון:
export default function List({ items, renderItem }) {
const [selectedIndex, setSelectedIndex] = useState(0);
return (
<div className="List">
{items.map((item, index) => {
const isHighlighted = index === selectedIndex;
return renderItem(item, isHighlighted);
})}הפרופס של renderItem נקרא “רנדור פרופס” מכיוון שuse זה אבזר שמציין כיצד לרנדר משהו. לדוגמה, אתה יכול להעביר יישום renderItem המציג <Row> עם הערך הנתון isHighlighted:
<List
items={products}
renderItem={(product, isHighlighted) =>
<Row
key={product.id}
title={product.title}
isHighlighted={isHighlighted}
/>
}
/>התוצאה הסופית זהה לזו של cloneElement:
<List>
<Row
title="Cabbage"
isHighlighted={true}
/>
<Row
title="Garlic"
isHighlighted={false}
/>
<Row
title="Apple"
isHighlighted={false}
/>
</List>עם זאת, אתה יכול לעקוב בבירור מאיפה מגיע הערך isHighlighted.
import { useState } from 'react'; export default function List({ items, renderItem }) { const [selectedIndex, setSelectedIndex] = useState(0); return ( <div className="List"> {items.map((item, index) => { const isHighlighted = index === selectedIndex; return renderItem(item, isHighlighted); })} <hr /> <button onClick={() => { setSelectedIndex(i => (i + 1) % items.length ); }}> Next </button> </div> ); }
תבנית זו מועדפת על cloneElement כי use היא מפורשת יותר.
העברת נתונים דרך ההקשר
חלופה נוספת ל-cloneElement היא להעביר נתונים דרך הקשר.
לדוגמה, אתה יכול לקרוא ל-createContext כדי להגדיר HighlightContext:
export const HighlightContext = createContext(false);רכיב List שלך יכול לעטוף כל פריט שהוא מעבד לספק HighlightContext:
export default function List({ items, renderItem }) {
const [selectedIndex, setSelectedIndex] = useState(0);
return (
<div className="List">
{items.map((item, index) => {
const isHighlighted = index === selectedIndex;
return (
<HighlightContext.Provider key={item.id} value={isHighlighted}>
{renderItem(item)}
</HighlightContext.Provider>
);
})}בגישה זו, Row אינו צריך לקבל אבזר isHighlighted כלל. במקום זאת, הוא קורא את ההקשר:
export default function Row({ title }) {
const isHighlighted = useContext(HighlightContext);
// ...זה מאפשר לרכיב הקורא לא לדעת או לדאוג לגבי העברת isHighlighted ל-<Row>:
<List
items={products}
renderItem={product =>
<Row title={product.title} />
}
/>במקום זאת, List ו-Row מתאמים את היגיון ההדגשה באמצעות הקשר.
import { useState } from 'react'; import { HighlightContext } from './HighlightContext.js'; export default function List({ items, renderItem }) { const [selectedIndex, setSelectedIndex] = useState(0); return ( <div className="List"> {items.map((item, index) => { const isHighlighted = index === selectedIndex; return ( <HighlightContext.Provider key={item.id} value={isHighlighted} > {renderItem(item)} </HighlightContext.Provider> ); })} <hr /> <button onClick={() => { setSelectedIndex(i => (i + 1) % items.length ); }}> Next </button> </div> ); }
למידע נוסף על העברת נתונים דרך הקשר.
חילוץ לוגיקה לתוך Hook מותאם אישית
גישה נוספת שאתה יכול לנסות היא לחלץ את ההיגיון ה”לא חזותי” לתוך Hook משלך, וuse את המידע המוחזר על ידי Hook שלך כדי להחליט מה לעבד. לדוגמה, אתה יכול לכתוב useList מותאם אישית Hook כך:
import { useState } from 'react';
export default function useList(items) {
const [selectedIndex, setSelectedIndex] = useState(0);
function onNext() {
setSelectedIndex(i =>
(i + 1) % items.length
);
}
const selected = items[selectedIndex];
return [selected, onNext];
}אז אתה יכול use את זה ככה:
export default function App() {
const [selected, onNext] = useList(products);
return (
<div className="List">
{products.map(product =>
<Row
key={product.id}
title={product.title}
isHighlighted={selected === product}
/>
)}
<hr />
<button onClick={onNext}>
Next
</button>
</div>
);
}זרימת הנתונים מפורשת, אבל ה-state נמצא בתוך useList המותאם אישית Hook שתוכלו use מכל רכיב:
import Row from './Row.js'; import useList from './useList.js'; import { products } from './data.js'; export default function App() { const [selected, onNext] = useList(products); return ( <div className="List"> {products.map(product => <Row key={product.id} title={product.title} isHighlighted={selected === product} /> )} <hr /> <button onClick={onNext}> Next </button> </div> ); }
גישה זו היא useמלאה במיוחד אם ברצונך לשנותuse את ההיגיון הזה בין רכיבים שונים.