Skip to content

Latest commit

 

History

History
693 lines (558 loc) · 16.8 KB

File metadata and controls

693 lines (558 loc) · 16.8 KB
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)

Referans {/reference/}

cloneElement(element, props, ...children) {/cloneelement/}

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 {/parameters/}

  • element: element argü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 bir cloneElement çağrısının sonucu olabilir.

  • props: props argümanı bir obje veya null olmalıdır. Eğer null geçirirseniz, clone edilen element orijinal element.props değerlerini korur. Aksi takdirde, props objesindeki her prop için dönen element, element.props yerine props içindeki değeri "tercih eder". Geri kalan prop’lar orijinal element.props’tan doldurulur. Eğer props.key veya props.ref geç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 ...children argümanı geçmezseniz, orijinal element.props.children korunur.

Returns {/returns/}

cloneElement, birkaç özelliğe sahip bir React element objesi döndürür:

  • type: element.type ile aynıdır.
  • props: element.props ile verdiğiniz override props’un shallow merge edilmesi sonucu oluşur.
  • ref: props.ref ile override edilmediyse, orijinal element.ref.
  • key: props.key ile override edilmediyse, orijinal element.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 {/caveats/}

  • 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ğin cloneElement(element, null, child1, child2, child3). Eğer children dinamik ise, tüm diziyi üçüncü argüman olarak geçin: cloneElement(element, null, listItems). Bu, React’in dinamik listeler için eksik key uyarı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 {/usage/}

Bir öğenin özelliklerini geçersiz kılma {/overriding-props-of-an-element/}

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.


Alternatifler {/alternatives/}

Render prop ile veri aktarımı {/passing-data-with-a-render-prop/}

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.


Veriyi context üzerinden geçirmek {/passing-data-through-context/}

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.


Mantığı özel bir Hook içine çıkarma {/extracting-logic-into-a-custom-hook/}

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.