본문 바로가기

FrontEnd

[React] React.cloneElement 사용 사례

반응형

React.cloneElement를 어디다 사용하는지 알아봅니다.

해당 글의 번역입니다 : https://blog.logrocket.com/using-react-cloneelement-function/

 

Using the React.cloneElement() function to clone elements - LogRocket Blog

In this in-depth tutorial, learn how to clone and manipulate elements three different ways using the React.cloneElement function.

blog.logrocket.com

React.cloneElement()가 뭐죠?

React.cloneElement()는 엘리먼트를 조작하는 데 사용되는 React 최상위 API의 일부입니다.
첫 번째 인수를 시작점으로 사용하여 새 엘리먼트를 복제하고 반환합니다.
이 인수는 React 엘리먼트 또는 React 엘리먼트를 렌더링하는 컴포넌트가 될 수 있습니다.
새 엘리먼트는 다음과 같은 특성을 갖습니다.
  • 원래 엘리먼트의 props와 새 props가 얕게 병합됨
  • 새 child가 기존 child를 대체합니다.
  • 원래 엘리먼트의 keyref가 유지됩니다.

React.cloneElement()는 불필요한 중복 코드를 피하면서
상위 컴포넌트의 하위 요소를 추가하거나 수정하려는 경우에 유용합니다.

React.cloneElement()의 구문과 사용

React.cloneElement(element, [props], [...children])
  • element: 복제할 엘리먼트
  • [props]: 원본 엘리먼트에 추가하여 복제된 엘리먼트에 추가될 props
  • [...children]: 복제된 객체의 children입니다. 기존 객체의 자식은 복사되지 않습니다.

 

부모 컴포넌트의 정의 내에서 React.cloneElement()를 사용하여 다음 프로세스를 수행할 수 있습니다.
  • children props 수정
  • children props 추가
  • children 컴포넌트의 기능 확장

children props 수정

예제에서 수정된 props가 얕게 병합된다는 점에 유의하십시오. 
key와 ref는 유지됩니다.


1. 반복되는 문자열

다음 코드 블록에서 RepeatCharacters는 상위 컴포넌트이고
CreateTextWithProps는 하위 컴포넌트 입니다.

CreateTextWithProps에는 값이 유효한 ASCII 문자인 ASCIIChar라는 prop이 있습니다.
RepeatCharacters는 React.cloneElement()를 사용하여 times 속성에 지정된 횟수만큼 복제된 엘리먼트에서 이 문자를 반복합니다.

import React from "react";
​
const CreateTextWithProps = ({ text, ASCIIChar, ...props }) => {
 return (
   <span {...props}>
    {text}{ASCIIChar}
   </span>
)
};
​
const RepeatCharacters = ({ times, children }) => {
 return React.cloneElement(children, {
   // This will override the original ASCIIChar in the text.
   ASCIIChar: children.props.ASCIIChar.repeat(times),
})
};
​
function App() {
 return (
   <div>
     <RepeatCharacters times={3}>
       <CreateTextWithProps
         text="Habdul Hazeez"
         ASCIIChar='.'
         />
     </RepeatCharacters>
   </div>
)
}
​
export default App

2. 멋진 children 버튼

"Fancy button"이라는 텍스트가 있는 버튼을 보여주는 코드입니다.
ButtonContainer 컴포넌트는
React.cloneElement()를 사용하여 Button 컴포넌트에 의해 렌더링된 엘리먼트의 모양을 수정합니다.

import React from "react";

const ButtonContainer = (props) => {
 let newProp = {
   backgroundColor: "#1560bd",
   textColor: '#ffffff',
   border: '1px solid #cccccc',
   padding: '0.2em',
}

 return (
   <div>
    {React.Children.map(props.children, child => {
       return React.cloneElement(child, {newProp}, null)
    })}
   </div>
)
};
​
const Button = (props) => {
 return <button
   style={{
     color: props.newProp.textColor,
     border: props.newProp.border,
     padding: props.newProp.padding,
     backgroundColor: props.newProp.backgroundColor
  }}>Fancy Button</button>
}

function App() {
 return (
   <ButtonContainer>
     <Button />
   </ButtonContainer>
)
}

export default App

3. 라디오 버튼 속성 수정

HTML에서 라디오 버튼은 그룹화됩니다.
제공된 옵션 중 하나만 선택할 수 있으며

모든 컴포넌트에 항상 동일한 name prop이 첨부되어 있는 것 같습니다.

 

지금까지 얻은 지식으로 단일 상위 컴포넌트를 통해
이 name 속성을 여러 하위 컴포넌트에 동적으로 추가할 수 있습니다.

이 경우 자식 컴포넌트는 라디오 버튼이거나 라디오 버튼을 렌더링하는 컴포넌트여야 합니다.
import React from "react";
​
const RadioGroup = (props) => {
 const RenderChildren = () => (
   React.Children.map(props.children, child => {
     return React.cloneElement(child, {
       name: props.name,
    })
  })
)
​
 return (
   <div>
    {<RenderChildren />}
   </div>
)
}
​
const RadioButton = (props) => {
 return (
   <label>
     <input type="radio" value={props.value} name={props.name} />
    {props.children}
   </label>
)
}
​
function App() {
 return (
 <RadioGroup name="numbers">
   <RadioButton value="first">First</RadioButton>
   <RadioButton value="second">Second</RadioButton>
   <RadioButton value="third">Third</RadioButton>
 </RadioGroup>
)
}
​
export default App
브라우저의 엘리먼트 검사 기능을 사용하여 이를 확인할 수 있습니다.

4. 다른 React 요소를 Prop으로 복제

다른 웹 페이지에 대해 다른 텍스트로 웹 사이트 헤더를 만들어야 하는 상황에 처하게 될 것입니다.

헤더 텍스트를 변수에 저장하고,
이 변수를 컴포넌트에 프롭으로 전달하고,
텍스트를 렌더링하는 등 다양한 옵션을 사용할 수 있습니다.
그러면 이 헤더 텍스트가 필요한 모든 컴포넌트 내에서 이 작업을 수행할 수 있습니다.
 
이는 불필요한 코드 중복을 생성하고
소프트웨어 개발자로서 우리는 항상 단순함을 유지해야 합니다.

대신 React.cloneElement를 사용하여 동일한 목표를 더 쉽게 달성할 수 있습니다.
다음과 같이 재사용 가능한 세 가지 컴포넌트를 만듭니다.

  • Header
  • DefaultHeader
  • BigHeader

Header 컴포넌트는 컴포넌트를 prop으로 받습니다.
이 컴포넌트는 헤더 텍스트를 렌더링합니다.
DefaultHeader는 Header에 전달되는 기본 컴포넌트입니다. 

 

DefaultHeader는 기본 텍스트를 렌더링합니다.
기본 텍스트는 props 없이 Header가 호출될 때 렌더링됩니다.

 

한편 BigHeader 컴포넌트에는 값이 선택한 헤더 텍스트인 메시지 prop이 있습니다.
BigHeader를 Header에 전달할 때마다

BigHeader에 의해 렌더링되기 전에 이 메시지 prop의 값을 수정할 수 있습니다.

이 모든 것이 다음 코드 블록에 설명되어 있습니다.

import React from "react";
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';

const DefaultHeader = (color) => {
 return (
   <div style={{ color: "#1560bd" }}>
     <p>Website of Habdul Hazeez</p>
   </div>
)
}

const defaultMessage = 'Website of Habdul Hazeez';

const BigHeader = ({ color, message = defaultMessage }) => {
 return (
   <div style={{ color, fontSize: '2em' }}>
    {message}
   </div>
)
}

const Header = ({ hero = <DefaultHeader />}) => {
 return (
   <div>
    {React.cloneElement(hero, { color: "#1560bd"})}
   </div>
)
}

const HomePage = () => {
 return (

<Header hero={<BigHeader message="This is the home page" />} />
)
}

const AboutMe = () => {
 return (
   <Header hero={<BigHeader message="Information about me" />} />
)
}

const ContactPage = () => {
 return (
   <Header hero={<BigHeader message="This contains my contact information." />} />
)
}

function App() {
 return (
   <React.Fragment>
     <Router>
       <nav>
         <ul>
           <li>
             <Link to="/">Home</Link>
           </li>
           <li>
             <Link to="/contact-page">Contact</Link>
           </li>
           <li>
             <Link to="about-me">About</Link>
           </li>
         </ul>
       </nav>
       
       <Route exact path="/"><HomePage /></Route>
        <Route path="/contact-page"><ContactPage /></Route>
        <Route path="/about-me"><AboutMe /></Route>
      </Router>
    </React.Fragment>
)
}

export default App

Children에 프롭 추가하기

부모 컴포넌트를 통해 자식에 새로운 속성을 삽입한다는 의미입니다.
이 섹션에서 논의되는 다음 예제는 React.cloneElement()를 통해 props를 추가하는 방법을 설명합니다.
이전 섹션에서 언급했듯이 새 props가 병합되고 key와 ref가 유지되며 새 children이 생성되지 않습니다.

1. 두꺼운 텍스트

React.cloneElement() 함수를 사용하여 child에 추가되는 CSS 스타일을 정의합니다.

import React from "react";

const CreateTextWithProps = ({ text, ...props }) => {
  return (
    <span {...props}>
      {text}
    </span>
  )
};

const BoldText = ({ children }) => {
  return React.cloneElement(children, {
    style: {
      fontWeight: 'bold'
    },
  })
};

function App() {
  return (
    <div>
      <BoldText>
        <CreateTextWithProps
          text="Habdul Hazeez"
        />
      </BoldText>
    </div>
  )
}

export default App;

2. React.cloneElement()를 통해 prop으로 전달받은 Element에 prop 전달하기

React.cloneElement()를 사용하여 prop을 복제합니다.
이 prop은 <h1> 또는 <button>과 같은 유효한 React 엘리먼트여야 합니다.
프롭을 복제할 때 CSS 스타일 또는 이벤트 핸들러와 같은 추가 속성을 전달할 수 있습니다.

다음 코드 블록에서는 AlertOnClick에 의해 렌더링되는 프롭에 handleClick 함수를 전달합니다.
따라서 엘리먼트를 클릭할 때마다 handleClick 내의 코드가 실행됩니다.
import React from "react";
​
const AlertOnClick = (props) => {
 
 function handleClick () {
   alert("Hello World")
}
​
 const Trigger = props.trigger;
​
 return (
   <div>
    {React.cloneElement(Trigger, {
       onClick: handleClick
    })}
   </div>
)
}
​
function App() {
 
 return (
   <div>
    {
       <AlertOnClick trigger={<button>Click me</button>} />
    }
   </div>
)
}
​
export default App

자식 컴포넌트의 기능 확장하기

지금까지 children prop을 수정하고 추가했습니다.
마침내 새로운 children을 생성합니다.

복제된 요소의 마지막 특성을 보여줍니다!
이 새 자식은 React.cloneElement()의 세 번째 인수로 전달됩니다.

Alert on click

이 예에서 우리는 자식의 기능을 확장할 것입니다.

다음 코드 블록에서 Button 구성 요소의 React.cloneElement()를 통해 생성된

새 버튼 요소에는 onClick 이벤트 핸들러와 새 텍스트라는 두 가지 추가 기능이 있습니다.

import React from "react";
const Button = () => {
 return (
   <button type="button" style={{ padding: "10px" }}>
     This is a styled button
   </button>
)
}
​
function App() {
 return (
   <section>
    {
       React.cloneElement(
         Button(), // component to overwrite
        {
           
           onClick: () => { // additional props
             alert("You are making progress!!!")
          }
        },
         <>
           Styled button with onClick
         </>
      )
    }
   </section>
)
}
​
export default App

이 코드가 실행되면 버튼은 React.cloneElement() 함수를 통해 추가된 텍스트를 이용해 렌더링되며
클릭 시 얼럿을 표시합니다.

(button의 children을 대체하기 때문임.)

더 보기

https://blog.cristiana.tech/react-children-map-and-cloneelement-using-typescript

 

React Children Map and cloneElement using TypeScript

Mapping through React Compound Components and Conditionally Enriching Children Props

blog.cristiana.tech

https://stackoverflow.com/questions/58123398/when-to-use-jsx-element-vs-reactnode-vs-reactelement

 

When to use JSX.Element vs ReactNode vs ReactElement?

I am currently migrating a React application to TypeScript. So far, this works pretty well, but I have a problem with the return types of my render functions, specifically in my functional componen...

stackoverflow.com

https://stackoverflow.com/questions/42261783/how-to-assign-the-correct-typing-to-react-cloneelement-when-giving-properties-to

 

How to assign the correct typing to React.cloneElement when giving properties to children?

I am using React and Typescript. I have a react component that acts as a wrapper, and I wish to copy its properties to its children. I am following React's guide to using clone element: https://fac...

stackoverflow.com

 

반응형