📝

10. Render Props - 합성 컴포넌트

 
이번 시간에는 컴포넌트를 합성해 봅시다! 일반적인 컴포넌트를 하나 만들고 그 안에 세부기능을 가지는 컴포넌트를 합성해서 사용할 수 있습니다. 컴포넌트를 합성함으로써 컴포넌트의 재사용성을 높입니다.
 
아래 사진은 기능이 다른 두 개의 모달입니다.
notion imagenotion image
notion imagenotion image
두 개의 모달 창을 만들 경우 두 개의 컴포넌트를 만들 수 있습니다. 이때 합성 컴포넌트를 사용하지 않는다면 비슷한 컴포넌트를 2개 만들어야 합니다. 그러면 코드가 중복되고 재사용을 할 수 있는 부분을 재사용하지 못하게 됩니다.
 
하지만 일반적인 컴포넌트를 만들어서 컴포넌트를 합성해서 사용한다면, 비슷한 일반적인 컴포넌트를 재사용하여 모달이 필요한 곳에 가서 합성하여 사용할 수 있습니다.
 

합성 컴포넌트 적용해보기

이번에는 create-react-app을 통해 새로운 프로젝트를 만들어 진행하겠습니다. styled-components를 사용하기 때문에 npm install styled-components를 설치합니다.
 

1. 기존 코드

두 개의 모달창을 만들어봅시다
위니브 - 모달 창 2개위니브 - 모달 창 2개
위니브 - 모달 창 2개
 
import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; ReactDOM.render(<App />, document.getElementById("root"));
src / index.js
import styled from "styled-components"; const CardDiv = styled.div` padding: 20px; border-radius: 10px; border: 1px solid #c4c4c4; margin-bottom: 20px; width: 200px; `; const CardOne = (props) => { return ( <> <CardDiv> <h3>챌린지 설정</h3> <hr /> <button>초기화</button> <button>저장하기</button> </CardDiv> </> ); }; const CardDivTwo = styled.div` padding: 20px; border-radius: 10px; border: 1px solid #c4c4c4; margin-bottom: 20px; width: 400px; `; const CardTwo = (props) => { return ( <> <CardDivTwo> <h3>서비스 공유하기</h3> <hr /> <p> Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque ut eveniet, laudantium, deleniti autem sequi molestias magni quia, aliquam et praesentium nostrum dolores culpa cupiditate unde doloremque labore beatae accusamus. </p> <div> <button>이미지 저장</button> <button>트위터</button> <button>페이스북</button> </div> </CardDivTwo> </> ); }; function App() { return ( <> <CardOne/> <CardTwo/> </> ); } export default App;
src / App.js
위에서 작성한 코드를 보면 겹치는 코드를 확인할 수 있습니다.
 

2. 합성컴포넌트 - 재사용되는 부분 확인하기

 
// 1.style - width를 제외하고 코드 동일 const CardDiv = styled.div` padding: 20px; border-radius: 10px; border: 1px solid #c4c4c4; margin-bottom: 20px; width: 400px; `; // 2. tag 동일 <CardDiv> <h3></h3> <hr /> </CardDiv>
 
해당부분에서 감싸는 태그가 비슷한 경우 위에서 설명한 것처럼 공통된 부분을 새로운 컴포넌트로 만들고 이 컴포넌트가 필요한 컴포넌트로 가져와서 사용하고 싶습니다. 모달을 하나 만들때마다 동일한 코드를 계속 작성하는 것이 아니라 겹치는 부분을 컴포넌트로 만들어서 재사용하는 것이죠
합성컴포넌트를 사용해서 작성해볼까요??
import styled from "styled-components"; const CardDiv = styled.div` padding: 20px; border-radius: 10px; border: 1px solid #c4c4c4; margin-bottom: 20px; width: ${(props) => (props.className === "setting" ? "200px" : "400px")}; `; const Card = (props) => { return ( <CardDiv className={props.className}> <h3>{props.value}</h3> <hr /> </CardDiv> ); }; function App() { return ( <> <Card className="setting" value="챌린지 설정"> </Card> <Card className="share" value="서비스 공유하기"> </Card> </> ); } export default App;
src/App.js에 새로 작성하기
먼저 <Card> 컴포넌트를 하나 만들어 주었습니다. Card 컴포넌트에 value props를 사용해서 h3태그에 들어갈 텍스트를 설정해줍니다. className props로 넘겨준 값으로는 styled-component의 props 조건문을 통해 className props의 값에 따라 width가 달라지게 합니다.
notion imagenotion image
 
 

3.합성컴포넌트 - 재사용해보기

다음은 <hr> 아래에 들어갈 내용들을 컴포넌트로 만들어볼게요.
챌린지 설정하기 카드 부분에서는 아래와 같은 부분들이 들어가겠네요.
 
notion imagenotion image
const SettingCard = () => { return ( <> <button>초기화</button> <button>저장하기</button> </> ); };
src / App.js에 코드 추가
그리고 서비스 공유하기에 들어갈 내용들입니다.
notion imagenotion image
const ShareCard = () => { return ( <> <p> Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque ut eveniet, laudantium, deleniti autem sequi molestias magni quia, aliquam et praesentium nostrum dolores culpa cupiditate unde doloremque labore beatae accusamus. </p> <div> <button>이미지 저장</button> <button>트위터</button> <button>페이스북</button> </div> </> ); };
src / App.js에 코드 추가
 
이제 ShareCard, SettingCard, Card 컴포넌트를 합쳐줍니다. 어떻게 하면될까요?
props.children이라는 속성을 통해 자식으로 사용되는 부분이 어디에 표현되야할지 입력할 수 있습니다. <hr /> 아래에 <div>{props.children}</div> 해당 코드를 넣어보도록할게요
const CardDiv = styled.div` padding: 20px; border-radius: 10px; border: 1px solid #c4c4c4; margin-bottom: 20px; width: ${(props) => (props.className === "setting" ? "200px" : "400px")}; `; const Card = (props) => { return ( <CardDiv className={props.className}> <h3>{props.value}</h3> <hr /> <div>{props.children}</div> </CardDiv> ); };
src / App.js 에 Card.jsx 코드 수정
위와 같이 작성하게되면 컴포넌트 사이에 들어가는 자식요소를 props.childrend받아오게됩니다. 아래와 같은 방식으로 사용 할 수있게됩니다.
//최종 App컴포넌트의 모습 const App = () => { return ( <> <Card className="setting" value="챌린지 설정"> <SettingCard /> </Card> <Card className="share" value="서비스 공유하기"> <ShareCard /> </Card> </> ); };
src / App.js에 return 부분 코드 수정
notion imagenotion image
 
최종코드
import styled from "styled-components"; const CardDiv = styled.div` padding: 20px; border-radius: 10px; border: 1px solid #c4c4c4; margin-bottom: 20px; width: ${(props) => (props.className === "setting" ? "200px" : "400px")}; `; const Card = (props) => { return ( <CardDiv className={props.className}> <h3>{props.value}</h3> <hr /> <div>{props.children}</div> </CardDiv> ); }; const SettingCard = () => { return ( <> <button>초기화</button> <button>저장하기</button> </> ); }; const ShareCard = () => { return ( <> <p> Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque ut eveniet, laudantium, deleniti autem sequi molestias magni quia, aliquam et praesentium nostrum dolores culpa cupiditate unde doloremque labore beatae accusamus. </p> <div> <button>이미지 저장</button> <button>트위터</button> <button>페이스북</button> </div> </> ); }; function App() { return ( <> <Card className="setting" value="챌린지 설정"> <SettingCard /> </Card> <Card className="share" value="서비스 공유하기"> <ShareCard /> </Card> </> ); } export default App;
shareCard와 settingCard는 Card 컴포넌트의 자식입니다. Card 컴포넌트의 입장에서 어떤 자식이 들어올지 미리 예상을 할 없습니다. 어떤 상황에는 버튼이 들어오고 어떤 상황에는 Div가 들어오고 상황마다 다 다르기 때문이죠. 자식으로 들어올 컴포넌트의 형태가 경우의 수가 많을 경우에 부모는 children prop을 사용하여 자식 엘리먼트를 받습니다. 그리고 아래와 같이 자식을 넣어주면 됩니다.
<Parent> <Child/> </Parent>