cloneElement
cloneElement, başka bir elementi başlangıç noktası olarak kullanarak yeni bir React elementi oluşturmanıza olanak tanır.
const clonedElement = cloneElement(element, props, ...children)Referans
cloneElement(element, props, ...children)
element temel alınarak ancak farklı props ve children ile bir React elementi oluşturmak için cloneElement fonksiyonunu çağırın:
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>Daha fazla örneği aşağıda inceleyin.
Parametreler
-
element:elementargümanı geçerli bir React elementi olmalıdır. Örneğin<Something />gibi bir JSX node’u,createElementçağrısının sonucu veya başka bircloneElementçağrısının sonucu olabilir. -
props:propsargümanı bir obje veyanullolmalıdır. Eğernullgeçirirseniz, clone edilen element orijinalelement.propsdeğerlerini korur. Aksi takdirde,propsobjesindeki her prop için dönen element,element.propsyerinepropsiçindeki değeri “tercih eder”. Geri kalan prop’lar orijinalelement.props’tan doldurulur. Eğerprops.keyveyaprops.refgeçirirseniz, bunlar orijinal değerlerin yerini alır. -
opsiyonel
...children: Sıfır veya daha fazla child node. Bunlar React element’leri, string’ler, sayılar, portals, boş node’lar (null,undefined,true,false) ve React node dizileri dahil olmak üzere herhangi bir React node olabilir. Eğer herhangi bir...childrenargümanı geçmezseniz, orijinalelement.props.childrenkorunur.
Returns
cloneElement, birkaç özelliğe sahip bir React element objesi döndürür:
type:element.typeile aynıdır.props:element.propsile verdiğiniz overrideprops’un shallow merge edilmesi sonucu oluşur.ref:props.refile override edilmediyse, orijinalelement.ref.key:props.keyile override edilmediyse, orijinalelement.key.
Genellikle, bu elementi component’inizden döndürür veya başka bir elementin child’ı olarak kullanırsınız. Element’in özelliklerini okuyabilseniz de, oluşturulduktan sonra her elementi opaque (iç yapısı bilinmeyen) olarak ele almak ve sadece render etmek en iyisidir.
Uyarılar
-
Bir elementi clone etmek orijinal elementi değiştirmez.
-
children’larıcloneElement’e yalnızca tamamı statik olarak biliniyorsa çoklu argümanlar şeklinde geçmelisiniz, örneğincloneElement(element, null, child1, child2, child3). Eğerchildrendinamik ise, tüm diziyi üçüncü argüman olarak geçin:cloneElement(element, null, listItems). Bu, React’in dinamik listeler için eksikkeyuyarısı vermesini sağlar. Statik listeler için bu gerekli değildir çünkü yeniden sıralanmazlar. -
cloneElement, veri akışını takip etmeyi zorlaştırır, bu yüzden bunun yerine alternatifleri kullanmayı deneyin.
Kullanım
Bir öğenin özelliklerini geçersiz kılma
Bazı React element prop’larını override etmek için, onu cloneElement’e override etmek istediğiniz props ile geçirin:
import { cloneElement } from 'react';
// ...
const clonedElement = cloneElement(
<Row title="Cabbage" />,
{ isHighlighted: true }
);Burada, ortaya çıkan cloned element <Row title="Cabbage" isHighlighted={true} /> olacaktır.
Ne zaman kullanışlı olduğunu görmek için bir örnek üzerinden gidelim.
Bir List component’ini düşünün; bu component children öğelerini, seçilebilir satırlar listesi olarak render eder ve hangi satırın seçili olduğunu değiştiren bir “Next” butonu vardır. List component’i seçili Row’u farklı render etmesi gerektiğinden, aldığı her <Row> child’ını clone eder ve ekstra bir isHighlighted: true veya isHighlighted: false prop’u ekler:
export default function List({ children }) {
const [selectedIndex, setSelectedIndex] = useState(0);
return (
<div className="List">
{Children.map(children, (child, index) =>
cloneElement(child, {
isHighlighted: index === selectedIndex
})
)}Diyelim ki List’in aldığı orijinal JSX şöyle görünüyor:
<List>
<Row title="Cabbage" />
<Row title="Garlic" />
<Row title="Apple" />
</List>Children’larını clone ederek, List her bir Row’a ekstra bilgi geçirebilir. Sonuç şöyle görünür:
<List>
<Row
title="Cabbage"
isHighlighted={true}
/>
<Row
title="Garlic"
isHighlighted={false}
/>
<Row
title="Apple"
isHighlighted={false}
/>
</List>“Next” butonuna basıldığında List’in state’inin nasıl güncellendiğine ve farklı bir satırın highlight edildiğine dikkat edin:
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> ); }
Özetle, List aldığı <Row /> elementlerini clone etti ve bunlara ekstra bir prop ekledi.
Alternatifler
Render prop ile veri aktarımı
cloneElement kullanmak yerine, renderItem gibi bir render prop kabul etmeyi düşünebilirsiniz. Burada List, renderItem’ı bir prop olarak alır. List, her item için renderItem’ı çağırır ve isHighlighted’ı bir argüman olarak geçirir:
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 prop’una “render prop” denir çünkü bir şeyin nasıl render edileceğini belirten bir prop’tur. Örneğin, verilen isHighlighted değeri ile bir <Row> render eden bir renderItem implementasyonu geçebilirsiniz:
<List
items={products}
renderItem={(product, isHighlighted) =>
<Row
key={product.id}
title={product.title}
isHighlighted={isHighlighted}
/>
}
/>Sonuç, cloneElement kullanımıyla elde edilen ile aynıdır:
<List>
<Row
title="Cabbage"
isHighlighted={true}
/>
<Row
title="Garlic"
isHighlighted={false}
/>
<Row
title="Apple"
isHighlighted={false}
/>
</List>Ancak, isHighlighted değerinin nereden geldiğini açıkça takip edebilirsiniz.
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> ); }
Bu desen, cloneElement’e göre tercih edilir çünkü daha açıktır.
Veriyi context üzerinden geçirmek
cloneElement’e bir diğer alternatif ise veriyi context üzerinden geçirmektir.
Örneğin, bir HighlightContext tanımlamak için createContext çağırabilirsiniz:
export const HighlightContext = createContext(false);List bileşeniniz, render ettiği her bir öğeyi bir HighlightContext sağlayıcısı içine sarabilir:
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 key={item.id} value={isHighlighted}>
{renderItem(item)}
</HighlightContext>
);
})}Bu yaklaşımla, Row bileşeninin artık bir isHighlighted prop’u almasına gerek yoktur. Bunun yerine, context’i okur:
export default function Row({ title }) {
const isHighlighted = useContext(HighlightContext);
// ...Bu, çağıran bileşenin <Row> bileşenine isHighlighted prop’unu geçmek zorunda kalmamasını ve bununla ilgilenmemesini sağlar:
<List
items={products}
renderItem={product =>
<Row title={product.title} />
}
/>Bunun yerine, List ve Row, highlight (vurgulama) mantığını context üzerinden koordine eder:
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 key={item.id} value={isHighlighted} > {renderItem(item)} </HighlightContext> ); })} <hr /> <button onClick={() => { setSelectedIndex(i => (i + 1) % items.length ); }}> Next </button> </div> ); }
Context aracılığıyla veri aktarma hakkında daha fazla bilgi edinin.
Mantığı özel bir Hook içine çıkarma
Deneyebileceğiniz bir diğer yaklaşım, “görsel olmayan” mantığı kendi Hook’unuza çıkarmak ve Hook’unuzun döndürdüğü bilgilere göre neyin render edileceğine karar vermektir. Örneğin, aşağıdaki gibi bir useList özel Hook’u yazabilirsiniz:
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];
}Ardından bunu şu şekilde kullanabilirsiniz:
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>
);
}Veri akışı nettir (explicit), ancak state useList custom Hook’unun içindedir ve bunu herhangi bir bileşenden kullanabilirsiniz:
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> ); }
Bu yaklaşım, bu mantığı farklı bileşenler arasında yeniden kullanmak istediğiniz durumlarda özellikle faydalıdır.