cloneElement
cloneElement
๋ฅผ ์ฌ์ฉํ๋ฉด element๋ฅผ ๊ธฐ์ค์ผ๋ก ์๋ก์ด React ์๋ฆฌ๋จผํธ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค.
const clonedElement = cloneElement(element, props, ...children)
๋ ํผ๋ฐ์ค
cloneElement(element, props, ...children)
์๋ก์ด React ์๋ฆฌ๋จผํธ๋ฅผ ๋ง๋ค๊ธฐ ์ํด element
๋ฅผ ๊ธฐ์ค์ผ๋ก ํ๊ณ , props
์ children
์ ๋ค๋ฅด๊ฒ ํ์ฌ cloneElement
๋ฅผ ํธ์ถํ์ธ์.
import { cloneElement } from 'react';
// ...
const clonedElement = cloneElement(
<Row title="Cabbage">
Hello
</Row>,
{ isHighlighted: true },
'Goodbye'
);
console.log(clonedElement); // <Row title="Cabbage">Goodbye</Row>
์๋์์ ๋ ๋ง์ ์์ ๋ฅผ ๋ณผ ์ ์์ต๋๋ค.
๋งค๊ฐ๋ณ์
-
element
:element
์ธ์๋ ์ ํจํ React ์๋ฆฌ๋จผํธ์ฌ์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด,<Something />
๊ณผ ๊ฐ์ JSX ๋ ธ๋,createElement
๋ก ํธ์ถํด ์ป์ ๊ฒฐ๊ณผ๋ฌผ ๋๋ ๋ค๋ฅธcloneElement
๋ก ํธ์ถํด ์ป์ ๊ฒฐ๊ณผ๋ฌผ์ด ๋ ์ ์์ต๋๋ค. -
props
:props
์ธ์๋ ๊ฐ์ฒด ๋๋null
์ด์ด์ผ ํฉ๋๋ค.null
์ ์ ๋ฌํ๋ฉด ๋ณต์ ๋ ์๋ฆฌ๋จผํธ๋ ์๋ณธelement.props
๋ฅผ ๋ชจ๋ ์ ์งํฉ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉดprops
๊ฐ์ฒด์ ๊ฐ prop์ ๋ํด ๋ฐํ๋ ์๋ฆฌ๋จผํธ๋element.props
์ ๊ฐ๋ณด๋คprops
์ ๊ฐ์ โ์ฐ์ โํฉ๋๋ค. ๋๋จธ์งprops
๋ ์๋ณธelement.props
์์ ์ฑ์์ง๋๋ค.props.key
๋๋props.ref
๋ฅผ ์ ๋ฌํ๋ฉด ์๋ณธ์ ๊ฒ์ ๋์ฒดํฉ๋๋ค. -
(์ ํ์ฌํญ)
...children
: 0๊ฐ ์ด์์ ์์ ๋ ธ๋๊ฐ ํ์ํฉ๋๋ค. React ์๋ฆฌ๋จผํธ, ๋ฌธ์์ด, ์ซ์, portals, ๋น ๋ ธ๋ (null
,undefined
,true
,false
) ๋ฐ React ๋ ธ๋ ๋ฐฐ์ด์ ํฌํจํ ๋ชจ๋ React ๋ ธ๋๊ฐ ํด๋นํ ์ ์์ต๋๋ค....children
์ธ์๋ฅผ ์ ๋ฌํ์ง ์์ผ๋ฉด ์๋ณธelement.props.children
์ด ์ ์ง๋ฉ๋๋ค.
๋ฐํ๊ฐ
cloneElement
๋ ๋ค์๊ณผ ๊ฐ์ ํ๋กํผํฐ๋ฅผ ๊ฐ์ง React ์๋ฆฌ๋จผํธ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค.
type
:element.type
๊ณผ ๋์ผํฉ๋๋ค.props
:element.props
์ ์ ๋ฌํprops
๋ฅผ ์๊ฒ ๋ณํฉํ ๊ฒฐ๊ณผ์ ๋๋ค.ref
:props.ref
์ ์ํด ์ฌ์ ์๋์ง ์์ ๊ฒฝ์ฐ ์๋ณธelement.ref
์ ๋๋ค.key
:props.key
์ ์ํด ์ฌ์ ์๋์ง ์์ ๊ฒฝ์ฐ ์๋ณธelement.key
์ ๋๋ค.
์ผ๋ฐ์ ์ผ๋ก ์ปดํฌ๋ํธ์์ ์๋ฆฌ๋จผํธ๋ฅผ ๋ฐํํ๊ฑฐ๋ ๋ค๋ฅธ ์๋ฆฌ๋จผํธ์ ์์์ผ๋ก ๋ง๋ญ๋๋ค. ์๋ฆฌ๋จผํธ์ ํ๋กํผํฐ๋ฅผ ์ฝ์ ์ ์์ง๋ง, ์์ฑ๋ ํ์๋ ๋ชจ๋ ์๋ฆฌ๋จผํธ์ ํ๋กํผํฐ๋ฅผ ์ฝ์ ์ ์๋ ๊ฒ์ฒ๋ผ ์ทจ๊ธํ๊ณ ๋ ๋๋งํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
์ฃผ์
-
์๋ฆฌ๋จผํธ๋ฅผ ๋ณต์ ํด๋ ์๋ณธ ์๋ฆฌ๋จผํธ๋ ์์ ๋์ง ์์ต๋๋ค.
-
์์์ด ๋ชจ๋ ์ ์ ์ธ ๊ฒฝ์ฐ์๋ง
cloneElement(element, null, child1, child2, child3)
์ ๊ฐ์ด ์์์ ์ฌ๋ฌ ๊ฐ์ ์ธ์๋ก ์ ๋ฌํด์ผ ํฉ๋๋ค. ์์์ด ๋์ ์ผ๋ก ์์ฑ๋์๋ค๋ฉดcloneElement(element, null, listItems)
์ ๊ฐ์ด ์ ์ฒด ๋ฐฐ์ด์ ์ธ ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌํด์ผ ํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด React๊ฐ ๋ชจ๋ ๋์ ๋ฆฌ์คํธ์ ๋ํด key๊ฐ ๋๋ฝ๋์๋ค๋ ๊ฒฝ๊ณ ๋ฅผ ๋ณด์ฌ์ค๋๋ค. ์ ์ ๋ฆฌ์คํธ์ ๊ฒฝ์ฐ๋ ์์๊ฐ ๋ณ๊ฒฝ๋์ง ์์ผ๋ฏ๋ก ์ด ์์ ์ ํ์ํ์ง ์์ต๋๋ค. -
cloneElement
๋ ๋ฐ์ดํฐ ํ๋ฆ์ ์ถ์ ํ๊ธฐ ์ด๋ ต๊ธฐ ๋๋ฌธ์ ๋ค์ ๋์์ ์ฌ์ฉํด ๋ณด์ธ์.
์ฌ์ฉ๋ฒ
์๋ฆฌ๋จผํธ์ props ์ฌ์ ์ํ๊ธฐ
์ผ๋ถ React ์๋ฆฌ๋จผํธ์ props๋ฅผ ์ฌ์ ์ํ๋ ค๋ฉด ์ฌ์ ์ํ๋ ค๋ props๋ฅผ cloneElement
์ ์ ๋ฌํ์ธ์.
import { cloneElement } from 'react';
// ...
const clonedElement = cloneElement(
<Row title="Cabbage" />,
{ isHighlighted: true }
);
clonedElement์ ๊ฒฐ๊ณผ๋ <Row title="Cabbage" isHighlighted={true} />
๊ฐ ๋ฉ๋๋ค.
์ด๋ค ๊ฒฝ์ฐ์ ์ ์ฉํ์ง ์์ ๋ฅผ ํตํด ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
children
์ ์ ํํ ์ ์๋ ํ ๋ชฉ๋ก์ผ๋ก ๋ ๋๋งํ๊ณ , ์ ํ๋ ํ์ ๋ณ๊ฒฝํ๋ โ๋ค์โ ๋ฒํผ์ด ์๋ List
์ปดํฌ๋ํธ๋ฅผ ์์ํด ๋ณด์ธ์. List
์ปดํฌ๋ํธ๋ ์ ํ๋ ํ์ ๋ค๋ฅด๊ฒ ๋ ๋๋งํด์ผ ํ๋ฏ๋ก ์ ๋ฌ๋ฐ์ ๋ชจ๋ <Row>
์์ ์์๋ฅผ ๋ณต์ ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ 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
์์ ์ ๋ฌ๋ฐ์ ์๋ณธ JSX๊ฐ ์๋ค๊ณ ๊ฐ์ ํฉ์๋ค.
<List>
<Row title="Cabbage" />
<Row title="Garlic" />
<Row title="Apple" />
</List>
์์ ์์๋ฅผ ๋ณต์ ํจ์ผ๋ก์จ List
๋ ๋ชจ๋ Row
์์ ์ถ๊ฐ์ ์ธ ์ ๋ณด๋ฅผ ์ ๋ฌํ ์ ์์ต๋๋ค. ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
<List>
<Row
title="Cabbage"
isHighlighted={true}
/>
<Row
title="Garlic"
isHighlighted={false}
/>
<Row
title="Apple"
isHighlighted={false}
/>
</List>
โ๋ค์โ ๋ฒํผ์ ๋๋ฅด๋ฉด List
์ state๊ฐ ์
๋ฐ์ดํธ๋๊ณ ๋ค๋ฅธ ํ์ด ํ์ด๋ผ์ดํธ ํ์๊ฐ ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
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) ); }}> ๋ค์ </button> </div> ); }
์์ฝํ์๋ฉด, List
๋ ์ ๋ฌ๋ฐ์ <Row />
์๋ฆฌ๋จผํธ๋ฅผ ๋ณต์ ํ๊ณ ์ถ๊ฐ๋ก ๋ค์ด์ค๋ prop ๋ํ ์ถ๊ฐํฉ๋๋ค.
๋์
render prop์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๊ธฐ
cloneElement
๋ฅผ ์ฌ์ฉํ๋ ๋์ ์ renderItem
๊ณผ ๊ฐ์ render prop์ ์ฌ์ฉํ๋ ๊ฒ์ ๊ณ ๋ คํด ๋ณด์ธ์. ๋ค์ ์์ ์ List
๋ renderItem
์ prop์ผ๋ก ๋ฐ์ต๋๋ค. List
๋ ๋ชจ๋ item์ ๋ํด renderItem
์ ํธ์ถํ๊ณ isHighlighted
๋ฅผ ์ธ์๋ก ์ ๋ฌํฉ๋๋ค.
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์ ๋ ๋๋ง ๋ฐฉ๋ฒ์ ์ง์ ํ๋ prop์ด๊ธฐ ๋๋ฌธ์ โrender propโ์ด๋ผ๊ณ ๋ถ๋ฆฝ๋๋ค. ์๋ฅผ ๋ค์ด, ์ฃผ์ด์ง isHighlighted
๊ฐ์ผ๋ก <Row>
๋ฅผ ๋ ๋๋งํ๋ renderItem
์ ์ ๋ฌํ ์ ์์ต๋๋ค.
<List
items={products}
renderItem={(product, isHighlighted) =>
<Row
key={product.id}
title={product.title}
isHighlighted={isHighlighted}
/>
}
/>
์ต์ข
์ ์ผ๋ก cloneElement
์ ๊ฐ์ ๊ฒฐ๊ณผ๊ฐ ๋ฉ๋๋ค.
<List>
<Row
title="Cabbage"
isHighlighted={true}
/>
<Row
title="Garlic"
isHighlighted={false}
/>
<Row
title="Apple"
isHighlighted={false}
/>
</List>
ํ์ง๋ง 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 ); }}> ๋ค์ </button> </div> ); }
์ด๋ฌํ ํจํด์ ๋ ๋ช
์์ ์ด๊ธฐ ๋๋ฌธ์ cloneElement
๋ณด๋ค ์ ํธ๋ฉ๋๋ค.
Context๋ฅผ ํตํด ๋ฐ์ดํฐ ์ ๋ฌํ๊ธฐ
cloneElement
์ ๋ ๋ค๋ฅธ ๋์์ผ๋ก๋ Context๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ ๊ฒ์
๋๋ค.
์๋ฅผ ๋ค์ด, createContext
๋ฅผ ํธ์ถํ์ฌ HighlightContext
๋ฅผ ์ ์ํ ์ ์์ต๋๋ค.
export const HighlightContext = createContext(false);
List
์ปดํฌ๋ํธ๋ ๋ ๋๋งํ๋ ๋ชจ๋ item์ HighlightContext.Provider
๋ก ๊ฐ์ ์ ์์ต๋๋ค.
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>
);
})}
์ด๋ฌํ ์ ๊ทผ ๋ฐฉ์์ผ๋ก ์ธํด Row
๋ isHighlighted
prop์ ๋ฐ์ ํ์๊ฐ ์์ด์ง๋๋ค. ๋์ context๋ฅผ ์ฝ์ต๋๋ค.
export default function Row({ title }) {
const isHighlighted = useContext(HighlightContext);
// ...
์ด์ ๋ฐ๋ผ isHighlighted
๋ฅผ <Row>
๋ก ์ ๋ฌํ๋ ๊ฒ์ ๋ํด ํธ์ถ๋ ์ปดํฌ๋ํธ๊ฐ ์๊ฑฐ๋ ๊ฑฑ์ ํ์ง ์์๋ ๋ฉ๋๋ค.
<List
items={products}
renderItem={product =>
<Row title={product.title} />
}
/>
๋์ ์ List
์ Row
๋ 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 ); }}> ๋ค์ </button> </div> ); }
context๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ ๊ฒ์ ๋ํ์ฌ ์์ธํ ์์๋ณด์ธ์.
Custom Hook์ผ๋ก ๋ก์ง ์ถ์ถํ๊ธฐ
๋ค๋ฅธ ์ ๊ทผ ๋ฐฉ์์ผ๋ก๋ ์์ฒด hook์ ํตํด โ๋น์๊ฐ์ ์ธโ ๋ก์ง์ ์ถ์ถํ๋ ๊ฒ์ ์๋ํด ๋ณผ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ hook์ ์ํด์ ๋ฐํ๋ ์ ๋ณด๋ฅผ ์ฌ์ฉํ์ฌ ๋ ๋๋งํ ๋ด์ฉ์ ์ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ด 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}>
๋ค์
</button>
</div>
);
}
๋ฐ์ดํฐ ํ๋ฆ์ ๋ช
์์ ์ด์ง๋ง state๋ ๋ชจ๋ ์ปดํฌ๋ํธ์์ ์ฌ์ฉํ ์ ์๋ 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}> ๋ค์ </button> </div> ); }
์ด๋ฌํ ์ ๊ทผ ๋ฐฉ์์ ๋ค๋ฅธ ์ปดํฌ๋ํธ ๊ฐ์ ํด๋น ๋ก์ง์ ์ฌ์ฌ์ฉํ๊ณ ์ถ์ ๋ ํนํ ์ ์ฉํฉ๋๋ค.