cloneElement
cloneElement
memungkinkan Anda untuk membuat elemen React baru dengan menggunakan elemen lain sebagai titik awal.
const clonedElement = cloneElement(element, props, ...children)
Referensi
cloneElement(element, props, ...children)
Panggil cloneElement
untuk membuat elemen React berdasarkan element
, tetapi dengan props
dan children
yang berbeda:
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>
Lihat lebih banyak contoh di bawah ini.
Parameter
-
element
: Argumenelement
harus merupakan elemen React yang valid. Misalnya, dapat berupa simpul JSX seperti<Something />
, hasil dari pemanggilancreateElement
, atau hasil dari pemanggilancloneElement
lainnya. -
props
: Argumenprops
harus berupa objek ataunull
. Jika Anda mengopernull
, elemen yang di-kloning akan mempertahankan semuaelement.props
yang orisinal. Sebaliknya, untuk setiap prop di objekprops
, elemen yang dikembalikan akan “memilih” nilai dariprops
daripada nilai darielement.props
. Sisa props lainnya akan diisi darielement.props
yang orisinal. Jika Anda mengoperprops.key
atauprops.ref
, mereka akan menggantikan yang orisinal. -
opsional
...children
: Nol atau lebih simpul anak. Bisa dari simpul React apa pun, termasuk elemen React, string, dan number. portal, simpul kosong (null
,undefined
,true
, danfalse
), dan senarai dari simpul-simpul React. Jika Anda tidak mengoper argumen...children
apa pun,element.props.children
yang orisinal akan tetap dipertahankan.
Kembalian
cloneElement
mengembalikan objek elemen React dengan beberapa properti:
type
: Sama sepertielement.type
.props
: Hasil dari penggabungan dangkal antaraelement.props
denganprops
yang Anda oper untuk menimpanya.ref
:element.ref
yang orisinal, kecuali telah ditimpa olehprops.ref
.key
:element.key
, yang orisinal, kecuali telah ditimpa olehprops.key
.
Biasanya, Anda akan mengembalikan elemen dari sebuah komponen atau membuatnya sebagai anak dari elemen lain. Meskipun Anda mungkin membaca properti elemen tersebut, sebaiknya Anda memperlakukan setiap elemen sebagai objek tersembunyi setelah dibuat, dan hanya me-render-nya.
Catatan penting
-
Mengkloning sebuah elemen tidak mengubah elemen yang orisinal.
-
Sebaiknya Anda hanya mengoper children sebagai beberapa argumen ke
cloneElement
jika semuanya diketahui secara statis, seperticloneElement(element, null, child1, child2, child3)
. Jika children Anda dinamis, oper seluruh senarai sebagai argumen ketiga:cloneElement(element, null, listItems)
. Ini memastikan bahwa React akan memperingatkan Anda tentangkey
yang hilang untuk setiap list dinamis. Untuk list statis hal tersebut tidak diperlukan karena tidak pernah diurutkan ulang. -
cloneElement
membuat pelacakan aliran data lebih sulit, jadi cobalah beberapa alternatif sebagai gantinya.
Penggunaan
Menimpa props dari suatu elemen
Untuk menimpa prop dari beberapa elemen React, oper ke cloneElement
dengan props yang ingin Anda timpa:
import { cloneElement } from 'react';
// ...
const clonedElement = cloneElement(
<Row title="Cabbage" />,
{ isHighlighted: true }
);
Hasil dari elemen yang dikloning akan menjadi <Row title="Cabbage" isHighlighted={true} />
.
Mari telusuri contoh untuk melihat kapan hal tersebut berguna.
Bayangkan komponen List
yang me-render children
nya sebagai daftar baris yang dapat dipilih dengan tombol “Next” yang dapat merubah baris mana yang dipilih. Komponen List
perlu me-render Row
yang dipilih secara terpisah, lalu mengkloning setiap anak <Row>
yang telah diterima, dan menambahkan prop isHighlighted: true
atau 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
})
)}
Katakanlah JSX asli yang diterima oleh List
terlihat seperti ini:
<List>
<Row title="Cabbage" />
<Row title="Garlic" />
<Row title="Apple" />
</List>
Dengan mengkloning anaknya, List
dapat meneruskan informasi tambahan ke setiap Row
di dalamnya. Hasilnya terlihat seperti ini:
<List>
<Row
title="Cabbage"
isHighlighted={true}
/>
<Row
title="Garlic"
isHighlighted={false}
/>
<Row
title="Apple"
isHighlighted={false}
/>
</List>
Perhatikan saat menekan “Next” akan memperbarui state dari List
, dan menyorot baris yang berbeda:
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> ); }
Ringkasnya, List
mengkloning elemen <Row />
yang diterimanya dan menambahkan prop tambahan ke dalamnya.
Alternatif
Mengoper data dengan render prop
Daripada menggunakan cloneElement
, pertimbangkan untuk menerima render prop seperti renderItem
. Di sini, List
menerima renderItem
sebagai prop. List
memanggil renderItem
untuk setiap item dan mengoper isHighlighted
sebagai argumen:
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);
})}
Prop renderItem
disebut “render prop” karena merupakan prop yang menentukan cara me-render sesuatu. Misalnya, Anda dapat mengoper renderItem
yang me-render <Row>
dengan nilai isHighlighted
yang diberikan:
<List
items={products}
renderItem={(product, isHighlighted) =>
<Row
key={product.id}
title={product.title}
isHighlighted={isHighlighted}
/>
}
/>
Hasil akhirnya sama dengan cloneElement
:
<List>
<Row
title="Cabbage"
isHighlighted={true}
/>
<Row
title="Garlic"
isHighlighted={false}
/>
<Row
title="Apple"
isHighlighted={false}
/>
</List>
Namun, Anda dapat dengan mudah melacak dari mana nilai isHighlighted
berasal.
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> ); }
Pola ini lebih anjurkan daripada cloneElement
karena lebih eksplisit.
Mengoper data melalui context
Alternatif lain untuk cloneElement
adalah mengoper data melalui context.
Sebagai contoh, Anda dapat memanggil createContext
untuk mendefinisikan HighlightContext
:
export const HighlightContext = createContext(false);
Komponen List
dapat menggabungkan setiap item yang di-render ke dalam provider 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>
);
})}
Dengan pendekatan ini, Row
tidak perlu menerima prop isHighlighted
sama sekali. Sebaliknya, dengan membaca context-nya:
export default function Row({ title }) {
const isHighlighted = useContext(HighlightContext);
// ...
Hal ini memungkinkan komponen pemanggil untuk tidak mengetahui atau peduli tentang pengoperan isHighlighted
ke <Row>
:
<List
items={products}
renderItem={product =>
<Row title={product.title} />
}
/>
Sebagai gantinya, List
dan Row
mengoordinasikan logika penyorotan melalui context.
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> ); }
Pelajari lebih lanjut tentang mengoper data melalui context.
Mengekstraksi logika ke dalam Hook kustom
Pendekatan lain yang dapat Anda coba adalah mengekstrak logika “non-visual” ke dalam Hook Anda sendiri, dan menggunakan informasi yang dikembalikan oleh Hook Anda untuk memutuskan apa yang akan di-render. Misalnya, Anda dapat menulis Hook kustom useList
seperti ini:
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];
}
Lalu Anda dapat menggunakannya seperti ini:
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>
);
}
Aliran datanya eksplisit, tetapi state ada di dalam Hook kustom useList
yang dapat Anda gunakan dari komponen apa pun:
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> ); }
Pendekatan ini sangat berguna jika Anda ingin menggunakan kembali logika ini di komponen yang berbeda.