cloneElement

Pitfall

cloneElement பயன்படுத்துவது அரிதானது; இது fragile code-க்கு வழிவகுக்கலாம். பொதுவான மாற்று வழிகளைப் பார்க்கவும்.

மற்றொரு element-ஐ starting point ஆகப் பயன்படுத்தி புதிய React element உருவாக்க cloneElement உதவுகிறது.

const clonedElement = cloneElement(element, props, ...children)

குறிப்பு

cloneElement(element, props, ...children)

element அடிப்படையில், ஆனால் வேறுபட்ட props மற்றும் children உடன் React element உருவாக்க cloneElement-ஐ call செய்யுங்கள்:

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>

மேலும் உதாரணங்களை கீழே பார்க்கவும்.

Parameters

  • element: element argument valid React element ஆக இருக்க வேண்டும். உதாரணமாக, அது <Something /> போன்ற JSX node, createElement call செய்த result, அல்லது மற்றொரு cloneElement call-ன் result ஆக இருக்கலாம்.

  • props: props argument object அல்லது null ஆக இருக்க வேண்டும். நீங்கள் null pass செய்தால், cloned element original element.props அனைத்தையும் retain செய்யும். இல்லையெனில், props object-இல் உள்ள ஒவ்வொரு prop-க்கும், return செய்யப்படும் element element.props-இல் உள்ள value-ஐ விட props-இல் உள்ள value-ஐ “prefer” செய்யும். மீதமுள்ள props original element.props-இலிருந்து நிரப்பப்படும். props.key அல்லது props.ref pass செய்தால், அவை original values-ஐ replace செய்யும்.

  • optional ...children: பூஜ்யம் அல்லது அதற்கு மேற்பட்ட child nodes. அவை React elements, strings, numbers, portals, empty nodes (null, undefined, true, மற்றும் false), மற்றும் React nodes-ன் arrays உட்பட எந்த React nodes ஆகவும் இருக்கலாம். ...children arguments எதையும் pass செய்யவில்லை என்றால், original element.props.children preserve செய்யப்படும்.

Returns

cloneElement சில properties கொண்ட React element object-ஐ return செய்கிறது:

  • type: element.type போலவே இருக்கும்.
  • props: நீங்கள் pass செய்த overriding props உடன் element.props-ஐ shallow-ஆக merge செய்த result.
  • ref: props.ref மூலம் override செய்யப்படாதவரை original element.ref.
  • key: props.key மூலம் override செய்யப்படாதவரை original element.key.

பொதுவாக, உங்கள் component-இலிருந்து element-ஐ return செய்வீர்கள் அல்லது அதை மற்றொரு element-ன் child ஆக்குவீர்கள். Element-ன் properties-ஐ read செய்யலாம் என்றாலும், element உருவாக்கப்பட்ட பிறகு அதை opaque ஆகக் கருதி render செய்வதே சிறந்தது.

Caveats

  • Element-ஐ clone செய்வது original element-ஐ modify செய்யாது.

  • எல்லா children-உம் statically known ஆக இருந்தால் மட்டுமே cloneElement(element, null, child1, child2, child3) போல cloneElement-க்கு children-ஐ பல arguments ஆக pass செய்யுங்கள். உங்கள் children dynamic என்றால், முழு array-ஐ மூன்றாவது argument ஆக pass செய்யுங்கள்: cloneElement(element, null, listItems). இதனால் dynamic lists-க்கு missing keys பற்றி React warning வழங்கும். Static lists-க்கு இது அவசியமில்லை; ஏனெனில் அவை reorder ஆகாது.

  • cloneElement data flow trace செய்வதை கடினமாக்குகிறது; எனவே அதற்கு பதிலாக மாற்று வழிகளை முயற்சிக்கவும்.


பயன்பாடு

Element-ன் props-ஐ override செய்தல்

ஒரு React element-ன் props-ஐ override செய்ய, override செய்ய வேண்டிய props உடன் அதை cloneElement-க்கு pass செய்யுங்கள்:

import { cloneElement } from 'react';

// ...
const clonedElement = cloneElement(
<Row title="Cabbage" />,
{ isHighlighted: true }
);

இங்கு resulting cloned element <Row title="Cabbage" isHighlighted={true} /> ஆக இருக்கும்.

இது எப்போது பயனுள்ளதாக இருக்கும் என்பதை ஒரு உதாரணத்தின் மூலம் பார்ப்போம்.

தேர்வு செய்யக்கூடிய rows பட்டியலாக தனது children-ஐ render செய்யும் List component-ஐ கற்பனை செய்யுங்கள்; எந்த row தேர்வு செய்யப்படுகிறது என்பதை மாற்றும் “Next” button உள்ளது. List component தேர்வு செய்யப்பட்ட Row-ஐ வேறுபட்டு render செய்ய வேண்டும்; ஆகவே அது பெற்ற ஒவ்வொரு <Row> child-ஐயும் clone செய்து, கூடுதல் isHighlighted: true அல்லது isHighlighted: false prop சேர்க்கிறது:

export default function List({ children }) {
const [selectedIndex, setSelectedIndex] = useState(0);
return (
<div className="List">
{Children.map(children, (child, index) =>
cloneElement(child, {
isHighlighted: index === selectedIndex
})
)}

List பெற்ற original JSX இவ்வாறு இருப்பதாக வைத்துக் கொள்வோம்:

<List>
<Row title="Cabbage" />
<Row title="Garlic" />
<Row title="Apple" />
</List>

தன் children-ஐ clone செய்வதன் மூலம், List உள்ளே உள்ள ஒவ்வொரு Row-க்கும் extra information pass செய்ய முடியும். Result இவ்வாறு இருக்கும்:

<List>
<Row
title="Cabbage"
isHighlighted={true}
/>
<Row
title="Garlic"
isHighlighted={false}
/>
<Row
title="Apple"
isHighlighted={false}
/>
</List>

“Next” press செய்தால் List-ன் state update ஆகி, வேறு row highlight ஆகிறது என்பதை கவனியுங்கள்:

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 /> elements-ஐ clone செய்து, அவற்றுக்கு extra prop சேர்த்தது.

Pitfall

Children-ஐ clone செய்வது, உங்கள் app-இல் data எப்படி flow ஆகிறது என்பதைப் புரிந்துகொள்வதை கடினமாக்கும். மாற்று வழிகளில் ஒன்றை முயற்சிக்கவும்.


மாற்று வழிகள்

Render prop மூலம் data pass செய்தல்

cloneElement பயன்படுத்துவதற்கு பதிலாக, renderItem போன்ற render prop ஏற்கலாம். இங்கு List renderItem-ஐ prop ஆகப் பெறுகிறது. ஒவ்வொரு item-க்கும் List renderItem call செய்து, isHighlighted-ஐ argument ஆக pass செய்கிறது:

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-ஐ “render prop” என்று அழைப்பது, ஏதாவது ஒன்றை எப்படி render செய்ய வேண்டும் என்பதை குறிப்பிடும் prop என்பதால். உதாரணமாக, கொடுக்கப்பட்ட isHighlighted value உடன் <Row> render செய்யும் renderItem implementation-ஐ pass செய்யலாம்:

<List
items={products}
renderItem={(product, isHighlighted) =>
<Row
key={product.id}
title={product.title}
isHighlighted={isHighlighted}
/>
}
/>

இறுதி result cloneElement பயன்படுத்தியதுடன் ஒரே மாதிரியாக இருக்கும்:

<List>
<Row
title="Cabbage"
isHighlighted={true}
/>
<Row
title="Garlic"
isHighlighted={false}
/>
<Row
title="Apple"
isHighlighted={false}
/>
</List>

ஆனால் isHighlighted value எங்கிருந்து வருகிறது என்பதை தெளிவாக trace செய்ய முடியும்.

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>
  );
}

இந்த pattern cloneElement-ஐ விட விரும்பத்தக்கது; ஏனெனில் இது இன்னும் explicit.


Context மூலம் data pass செய்தல்

cloneElement-க்கு மற்றொரு மாற்று வழி context மூலம் data pass செய்வது.

உதாரணமாக, HighlightContext define செய்ய createContext-ஐ call செய்யலாம்:

export const HighlightContext = createContext(false);

உங்கள் List component அது render செய்யும் ஒவ்வொரு item-ஐயும் HighlightContext provider-க்குள் wrap செய்யலாம்:

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>
);
})}

இந்த அணுகுமுறையில், Row isHighlighted prop பெறவே தேவையில்லை. அதற்கு பதிலாக அது context-ஐ read செய்கிறது:

export default function Row({ title }) {
const isHighlighted = useContext(HighlightContext);
// ...

இதனால் calling component <Row>-க்கு isHighlighted pass செய்வதைப் பற்றி அறியவோ கவலைப்படவோ வேண்டியதில்லை:

<List
items={products}
renderItem={product =>
<Row title={product.title} />
}
/>

அதற்கு பதிலாக, List மற்றும் Row highlighting logic-ஐ context மூலம் coordinate செய்கின்றன.

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 மூலம் data pass செய்வது பற்றி மேலும் அறிக.


Logic-ஐ custom Hook-க்குள் extract செய்தல்

நீங்கள் முயற்சிக்கக்கூடிய மற்றொரு அணுகுமுறை: “non-visual” logic-ஐ உங்கள் சொந்த Hook-க்குள் extract செய்து, உங்கள் Hook return செய்யும் information-ஐ வைத்து என்ன render செய்ய வேண்டும் என்பதை தீர்மானிப்பது. உதாரணமாக, useList custom 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];
}

பிறகு அதை இவ்வாறு பயன்படுத்தலாம்:

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>
);
}

Data flow explicit ஆக உள்ளது; ஆனால் state எந்த component-இலிருந்தும் பயன்படுத்தக்கூடிய useList custom Hook-க்குள் உள்ளது:

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>
  );
}

இந்த logic-ஐ வெவ்வேறு components இடையே reuse செய்ய விரும்பினால், இந்த அணுகுமுறை குறிப்பாக பயனுள்ளது.