| title | cloneElement |
|---|
cloneElement kullanımı yaygın değildir ve kırılgan kodlara yol açabilir. Yaygın alternatiflere göz atın.
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)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.
-
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.
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.
-
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.
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 List from './List.js';
import Row from './Row.js';
import { products } from './data.js';
export default function App() {
return (
<List>
{products.map(product =>
<Row
key={product.id}
title={product.title}
/>
)}
</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>
);
}export default function Row({ title, isHighlighted }) {
return (
<div className={[
'Row',
isHighlighted ? 'RowHighlighted' : ''
].join(' ')}>
{title}
</div>
);
}export const products = [
{ title: 'Cabbage', id: 1 },
{ title: 'Garlic', id: 2 },
{ title: 'Apple', id: 3 },
];.List {
display: flex;
flex-direction: column;
border: 2px solid grey;
padding: 5px;
}
.Row {
border: 2px dashed black;
padding: 5px;
margin: 5px;
}
.RowHighlighted {
background: #ffa;
}
button {
height: 40px;
font-size: 20px;
}Özetle, List aldığı <Row /> elementlerini clone etti ve bunlara ekstra bir prop ekledi.
Children’ları clone etmek, veri akışının uygulamanızda nasıl ilerlediğini anlamayı zorlaştırır. Alternatiflerden birini denemeyi deneyin.
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 List from './List.js';
import Row from './Row.js';
import { products } from './data.js';
export default function App() {
return (
<List
items={products}
renderItem={(product, isHighlighted) =>
<Row
key={product.id}
title={product.title}
isHighlighted={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>
);
}export default function Row({ title, isHighlighted }) {
return (
<div className={[
'Row',
isHighlighted ? 'RowHighlighted' : ''
].join(' ')}>
{title}
</div>
);
}export const products = [
{ title: 'Cabbage', id: 1 },
{ title: 'Garlic', id: 2 },
{ title: 'Apple', id: 3 },
];.List {
display: flex;
flex-direction: column;
border: 2px solid grey;
padding: 5px;
}
.Row {
border: 2px dashed black;
padding: 5px;
margin: 5px;
}
.RowHighlighted {
background: #ffa;
}
button {
height: 40px;
font-size: 20px;
}Bu desen, cloneElement’e göre tercih edilir çünkü daha açıktır.
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 List from './List.js';
import Row from './Row.js';
import { products } from './data.js';
export default function App() {
return (
<List
items={products}
renderItem={(product) =>
<Row title={product.title} />
}
/>
);
}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>
);
}import { useContext } from 'react';
import { HighlightContext } from './HighlightContext.js';
export default function Row({ title }) {
const isHighlighted = useContext(HighlightContext);
return (
<div className={[
'Row',
isHighlighted ? 'RowHighlighted' : ''
].join(' ')}>
{title}
</div>
);
}import { createContext } from 'react';
export const HighlightContext = createContext(false);export const products = [
{ title: 'Cabbage', id: 1 },
{ title: 'Garlic', id: 2 },
{ title: 'Apple', id: 3 },
];.List {
display: flex;
flex-direction: column;
border: 2px solid grey;
padding: 5px;
}
.Row {
border: 2px dashed black;
padding: 5px;
margin: 5px;
}
.RowHighlighted {
background: #ffa;
}
button {
height: 40px;
font-size: 20px;
}Context aracılığıyla veri aktarma hakkında daha fazla bilgi edinin.
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>
);
}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];
}export default function Row({ title, isHighlighted }) {
return (
<div className={[
'Row',
isHighlighted ? 'RowHighlighted' : ''
].join(' ')}>
{title}
</div>
);
}export const products = [
{ title: 'Cabbage', id: 1 },
{ title: 'Garlic', id: 2 },
{ title: 'Apple', id: 3 },
];.List {
display: flex;
flex-direction: column;
border: 2px solid grey;
padding: 5px;
}
.Row {
border: 2px dashed black;
padding: 5px;
margin: 5px;
}
.RowHighlighted {
background: #ffa;
}
button {
height: 40px;
font-size: 20px;
}Bu yaklaşım, bu mantığı farklı bileşenler arasında yeniden kullanmak istediğiniz durumlarda özellikle faydalıdır.