cloneElement
lets you create a new React element using another element as a starting point.
cloneElement
를 사용하면 다른 엘리먼트를 시작점으로 사용하여 새로운 React 엘리먼트를 생성할 수 있습니다.
const clonedElement = cloneElement(element, props, ...children)
Reference참조
cloneElement(element, props, ...children)
Call cloneElement
to create a React element based on the element
, but with different props
and children
:
cloneElement
를 호출하면 element
를 기반으로 별도의 props
및 children
를 갖는 React 엘리먼트를 생성합니다:
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>
See more examples below. 아래에서 더 많은 예시를 확인하세요.
Parameters매개변수
-
element
: Theelement
argument must be a valid React element. For example, it could be a JSX node like<Something />
, the result of callingcreateElement
, or the result of anothercloneElement
call.element
:element
인수는 유효한 React 엘리먼트여야 합니다. 예를 들어,<Something />
과 같은 JSX 노드,createElement
를 호출한 결과, 또는 다른cloneElement
호출의 결과일 수 있습니다. -
props
: Theprops
argument must either be an object ornull
. If you passnull
, the cloned element will retain all of the originalelement.props
. Otherwise, for every prop in theprops
object, the returned element will “prefer” the value fromprops
over the value fromelement.props
. The rest of the props will be filled from the originalelement.props
. If you passprops.key
orprops.ref
, they will replace the original ones.props
: props 인수는 객체이거나null
이어야 합니다.null
을 전달하면 복제된 엘리먼트는 원본element.props
를 모두 유지합니다. 반대로null
이 아닐 경우에는, 반환된 엘리먼트는props
객체의 모든 prop에 대해element.props
의 값보다props
의 값을 “우선”합니다. 나머지 prop들은 원본element.props
에서 채워집니다.props.key
또는props.ref
를 전달하면 원래 값을 대체합니다. -
optional
...children
: Zero or more child nodes. They can be any React nodes, including React elements, strings, numbers, portals, empty nodes (null
,undefined
,true
, andfalse
), and arrays of React nodes. If you don’t pass any...children
arguments, the originalelement.props.children
will be preserved. 선택적...children
: 0개 이상의 자식 노드. 자식 노드는 React 엘리먼트, 문자열, 숫자, portals, 빈 노드(null
,undefined
,true
,false
), React 노드의 배열을 포함한 모든 React 노드가 될 수 있습니다....children
인수를 전달하지 않으면 원래의element.props.children
이 보존됩니다.
Returns반환값
cloneElement
returns a React element object with a few properties:
cloneElement
는 몇 가지 속성을 가진 React 엘리먼트 객체를 반환합니다:
-
type
: Same aselement.type
.type
:element.type
과 동일합니다. -
props
: The result of shallowly mergingelement.props
with the overridingprops
you have passed.props
: 재정의되어 전달된props
와element.props
를 얕게 병합한 결과입니다. -
ref
: The originalelement.ref
, unless it was overridden byprops.ref
.ref
:props.ref
로 재정의되지 않은 경우 원본element.ref
. -
key
: The originalelement.key
, unless it was overridden byprops.key
.key
:props.key
로 재정의되지 않은 경우 원본element.key
.
Usually, you’ll return the element from your component or make it a child of another element. Although you may read the element’s properties, it’s best to treat every element as opaque after it’s created, and only render it. 일반적으로 컴포넌트에서 엘리먼트를 반환하거나 다른 엘리먼트의 자식으로 만듭니다. 엘리먼트의 프로퍼티를 읽을 수는 있지만, 생성된 후에는 모든 엘리먼트를 불명확하게 처리하고 렌더링만 하는 것이 가장 좋습니다.
Caveats주의사항
-
Cloning an element does not modify the original element. 엘리먼트를 복제해도 원본 엘리먼트는 수정되지 않습니다.
-
You should only pass children as multiple arguments to
cloneElement
if they are all statically known, likecloneElement(element, null, child1, child2, child3)
. If your children are dynamic, pass the entire array as the third argument:cloneElement(element, null, listItems)
. This ensures that React will warn you about missingkey
s for any dynamic lists. For static lists this is not necessary because they never reorder. 자식은cloneElement(element, null, child1, child2, child3)
와 같이 모두 정적으로 알려진 경우에 한해서만 여러 개의 인수로cloneElement
에 전달해야 합니다. 자식들이 동적인 경우, 다음과 같이 세 번째 인자로 전체 배열을 전달하세요:cloneElement(element, null, listItems)
. 이렇게 하면 React는 동적 리스트를 상정하여 누락된key
에 대해 경고합니다. 정적 리스트의 경우 순서가 바뀌지 않으므로key
는 필요하지 않습니다. -
cloneElement
makes it harder to trace the data flow, so try the alternatives instead.cloneElement
를 사용하면 데이터 흐름을 추적하기가 더 어려워지므로 대신 다른 방법을 사용해 보세요.
Usage사용법
Overriding props of an element엘리먼트의 props 재정의
To override the props of some React element, pass it to cloneElement
with the props you want to override:
일부 React 엘리먼트의 props를 재정의하려면, 재정의하려는 props와 함께 cloneElement
에 전달하세요:
import { cloneElement } from 'react';
// ...
const clonedElement = cloneElement(
<Row title="Cabbage" />,
{ isHighlighted: true }
);
Here, the resulting cloned element will be <Row title="Cabbage" isHighlighted={true} />
.
여기서 복제된 엘리먼트의 결과는 <Row title="Cabbage" isHighlighted={true} />
입니다.
Let’s walk through an example to see when it’s useful. 언제 이것이 유용한지 예제를 통해 살펴보겠습니다.
Imagine a List
component that renders its children
as a list of selectable rows with a “Next” button that changes which row is selected. The List
component needs to render the selected Row
differently, so it clones every <Row>
child that it has received, and adds an extra isHighlighted: true
or isHighlighted: false
prop:
선택된 행을 변경하는 “Next” 버튼이 있는 행의 목록을 자식으로 렌더링하는 List
컴포넌트를 상상해 보세요. List
컴포넌트는 선택된 Row
를 다르게 렌더링해야 하므로 전달된 모든 <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
})
)}
Let’s say the original JSX received by List
looks like this:
List
가 전달받은 원본 JSX가 다음과 같다고 가정해 보겠습니다:
<List>
<Row title="Cabbage" />
<Row title="Garlic" />
<Row title="Apple" />
</List>
By cloning its children, the List
can pass extra information to every Row
inside. The result looks like this:
List
는 자식을 복제하여 내부의 모든 Row
에 추가 정보를 전달할 수 있습니다. 결과는 다음과 같습니다:
<List>
<Row
title="Cabbage"
isHighlighted={true}
/>
<Row
title="Garlic"
isHighlighted={false}
/>
<Row
title="Apple"
isHighlighted={false}
/>
</List>
Notice how pressing “Next” updates the state of the List
, and highlights a different row:
“다음”을 누르면 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) ); }}> Next </button> </div> ); }
To summarize, the List
cloned the <Row />
elements it received and added an extra prop to them.
요약하자면, List
는 수신한 <Row />
엘리먼트를 복제하고 여기에 별도의 prop을 추가했습니다.
Alternatives대안
Passing data with a render prop렌더링 props로 데이터 전달하기
Instead of using cloneElement
, consider accepting a render prop like renderItem
. Here, List
receives renderItem
as a prop. List
calls renderItem
for every item and passes isHighlighted
as an argument:
cloneElement
를 사용하는 대신 renderItem
과 같은 렌더링 prop를 받아들이는 것을 고려해 보세요. 여기서 List
는 렌더링 prop로 renderItem
을 받습니다. List
는 모든 항목에 대해 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);
})}
The renderItem
prop is called a “render prop” because it’s a prop that specifies how to render something. For example, you can pass a renderItem
implementation that renders a <Row>
with the given isHighlighted
value:
renderItem
prop은 렌더링 방법을 지정하는 prop이기 때문에 “render prop”라고 부릅니다. 예를 들어, 주어진 isHighlighted
값으로 <Row>
를 렌더링하는 renderItem
구현을 전달할 수 있습니다:
<List
items={products}
renderItem={(product, isHighlighted) =>
<Row
key={product.id}
title={product.title}
isHighlighted={isHighlighted}
/>
}
/>
The end result is the same as with cloneElement
:
최종 결과는 cloneElement
와 동일합니다:
<List>
<Row
title="Cabbage"
isHighlighted={true}
/>
<Row
title="Garlic"
isHighlighted={false}
/>
<Row
title="Apple"
isHighlighted={false}
/>
</List>
However, you can clearly trace where the isHighlighted
value is coming from.
하지만, cloneElement
와 달리 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> ); }
This pattern is preferred to cloneElement
because it is more explicit.
이 패턴은 더 명시적이므로 cloneElement
보다 선호됩니다.
Passing data through contextContext를 통해 데이터 전달하기
Another alternative to cloneElement
is to pass data through context.
cloneElement
의 또 다른 대안은 context를 통해 데이터를 전달하는 것입니다.
For example, you can call createContext
to define a HighlightContext
:
예를 들어, createContext
를 호출하여 HighlightContext
를 정의할 수 있습니다:
export const HighlightContext = createContext(false);
Your List
component can wrap every item it renders into a HighlightContext
provider:
List
컴포넌트는 렌더링하는 모든 항목을 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>
);
})}
With this approach, Row
does not need to receive an isHighlighted
prop at all. Instead, it reads the context:
이 방법을 사용하면 Row
는 isHighlighted
prop를 전혀 전달받을 필요가 없습니다. 대신 context를 읽습니다:
export default function Row({ title }) {
const isHighlighted = useContext(HighlightContext);
// ...
This allows the calling component to not know or worry about passing isHighlighted
to <Row>
:
이렇게 하면 호출 컴포넌트가 <Row>
에 isHighlighted
를 전달하는 것에 대해 알 필요도 없고, 걱정하지 않아도 됩니다:
<List
items={products}
renderItem={product =>
<Row title={product.title} />
}
/>
Instead, List
and Row
coordinate the highlighting logic through context.
대신 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 ); }}> Next </button> </div> ); }
Learn more about passing data through context. context를 통한 데이터 전달에 대해 자세히 알아보세요.
Extracting logic into a custom Hook로직을 커스텀 훅으로 추출하기
Another approach you can try is to extract the “non-visual” logic into your own Hook, and use the information returned by your Hook to decide what to render. For example, you could write a useList
custom Hook like this:
시도해 볼 수 있는 또 다른 접근 방식은 “비시각적” 로직을 자신만의 Hook으로 추출하고, Hook에서 반환된 정보를 사용하여 렌더링할 내용을 결정하는 것입니다. 예를 들어, 다음과 같이 커스텀 훅 useList
를 작성할 수 있습니다:
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];
}
Then you could use it like this: 그러면 다음과 같이 사용할 수 있습니다:
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>
);
}
The data flow is explicit, but the state is inside the useList
custom Hook that you can use from any component:
데이터 흐름은 명시적이지만 state는 모든 컴포넌트에서 사용할 수 있는 커스텀 훅 useList
안에 있습니다:
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> ); }
This approach is particularly useful if you want to reuse this logic between different components. 이 접근 방식은 다른 컴포넌트 간에 이 로직을 재사용하려는 경우에 특히 유용합니다.