React Element 복제하기

시작하기

j쿼리의 경우 clone을 사용하여 간단하게 카피를 할 수 있습니다.
리액트에서는 어떻게 해야하는지 알아보도록 하겠습니다. Element를 복제해보도록 하겠습니다.

사전준비

복제를 하기위해 복제 대상인 element를 만들어 줍니다.

App.tsx
1
2
3
4
5
6
7
8
9
10
11
12
import React from 'react';

const App: React.FC = () => {
const el = React.useRef(null);
const cloneElement = () => {}
return (
<div className="App">
<div ref={el}>Clone Element</div>
<button onClick={() => cloneElement()}>Element 복제하기</button>
</div>
);
}

기본구성이 완료되었습니다.

기능구현

복제기능을 담당할 cloneElement 함수를 구현하겠습니다.

1
2
3
4
5
// ...
const cloneElement = () => {
const elementAssign: any = Object.assign({}, el.current);
}
// ...

Object.assign으로 element를 복사해줍니다.
Object.assign는 객체로부터 대상 객체로 속성을 복사할 때 사용합니다. 대상 객체를 반환합니다.
자세한 내용은 Object.assign 을 참고해주세요.

console.log(elementAssign)를 해보면 다음이미지와 같은 값이 반환됩니다.
console.log(elementAssign)
__reactEventHandlers$id, __reactInternalInstance$id 이렇게 두가지 값이 반환됩니다.
reactInternalInstance$id의 값이 FiberNode로 나옵니다.
하지만 저희는 __reactEventHandlers$id를 사용할 예정입니다.

참고
내부에있는 FiberNode에 도달하면 개인 필드를 통과했기 때문에 코드가 매우 비효율적임을 의미합니다.

1
2
3
4
5
6
// ...
const cloneElement = () => {
const elementAssign: any = Object.assign({}, el.current);
const reactHandlerKey = Object.keys(elementAssign).filter((item: string) => item.indexOf('__reactEventHandlers') >= 0);
}
// ...

__reactEventHandlers인값을 가져옵니다.

1
2
3
4
5
6
7
// ...
const cloneElement = () => {
const elementAssign: any = Object.assign({}, el.current);
const reactHandlerKey = Object.keys(elementAssign).filter((item: string) => item.indexOf('__reactEventHandlers') >= 0);
const reactHandler: { children: JSX.Element } = elementAssign[reactHandlerKey[0]];
}
// ...

elementAssign에서 reactHandler값만 가져오도록합니다.

1
2
3
4
5
6
7
8
// ...
const cloneElement = () => {
const elementAssign: any = Object.assign({}, el.current);
const reactHandlerKey = Object.keys(elementAssign).filter((item: string) => item.indexOf('__reactEventHandlers') >= 0);
const reactHandler: { children: JSX.Element } = elementAssign[reactHandlerKey[0]];
const cloneTargetElement: JSX.Element = React.cloneElement(reactHandler.children, {...reactHandler});
}
// ...

reactHandler.children값을 jsx파일로 복사해줍니다. 그리고 나머지 값(props, style…)등은 두번째인자로 넣어줍니다.

컴포넌트에 기능을 적용해보겠습니다.

App.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React, { useState } from 'react';

const App: React.FC = () => {
const el = React.useRef<HTMLDivElement>(null);
const [elements, setElements] = useState<JSX.Element[]>([]);
const cloneElement = () => {
const elementAssign: any = Object.assign({}, el.current);
const reactHandlerKey = Object.keys(elementAssign).filter((item: string) => item.indexOf('__reactEventHandlers') >= 0);
const reactHandler: { children: JSX.Element } = elementAssign[reactHandlerKey[0]];
const cloneTargetElement: JSX.Element = React.cloneElement(reactHandler.children, {...reactHandler});
setElements((prevState: JSX.Element[]) => [...prevState, cloneTargetElement])
}

return (
<div className="App">
<div ref={el}>
<div>Clone Element</div>
</div>
<button onClick={() => cloneElement()}>Element 복하기</button>
<div>Element:{elements.length > 0 && elements.map((element: JSX.Element, i: number) => <div
key={i}>{element}</div>)}</div>
</div>
);
}

export default App;

useElement에 element들을 저장하고 map으로 element을 렌더링해주도록 합니다.

결과물

이것으로 리액트에서 element를 복제하는방법에 대해서 알아보았습니다.
상태에 데이터를 저장하고 컴포넌트에 데이터를 넣어서 표현하는 방법이 더쉽고 효율적입니다.
이 방법은 element를 어쩔수 없이 복제해야 할때만 사용합니다.

Share