본문 바로가기

합성 컴포넌트

컴포넌트 합성은 작은 컴포넌트들을 결합하여 더 큰 컴포넌트를 만드는 것을 말합니다. 합성을 통해 컴포넌트의 재사용성을 높이고, 코드의 구조를 단순화하여 가독성과 유지보수성을 향상시킬 수 있습니다.

1. 합성 컴포넌트의 필요성

Vite를 통해 새로운 프로젝트를 만들어 진행하겠습니다. styled-components도 사용하기 때문에 npm install styled-components 명령어를 사용해 설치합니다.

npx my-react-vite-app [프로젝트 이름]
cd my-app
npm install
npm install styled-components

다음과 같이 유사하지만 세부 내용이 다른 두 개의 모달이 필요한 상황을 생각해봅시다.

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;

이러한 코드는 비슷한 구조가 반복되어 있어 효율적이지 않습니다. 이 때 합성 컴포넌트를 사용하면 공통된 부분을 재사용할 수 있습니다.

2. 합성 컴포넌트 만들기

2.1 공통 구조 분리하기

먼저 두 컴포넌트에서 공통으로 사용되는 부분을 찾아 기본 카드 컴포넌트를 만듭니다.

const CardDiv = styled.div`
  padding: 20px;
  border-radius: 10px;
  border: 1px solid #c4c4c4;
  margin-bottom: 20px;
`;

const Card = () => {
  return (
    <CardDiv>
      <h3></h3>
      <hr />
    </CardDiv>
  );
};

두 컴포넌트의 스타일은 너비를 제외하고 동일합니다. HTML 구조 또한 div 태그 내부에 h3 태그, hr 태그가 연이어 존재하고 그 아래 각자의 컨텐츠가 존재합니다. 이제 모달을 만들 때마다 동일한 코드를 반복해서 작성하지 않고 하나의 컴포넌트를 재사용할 수 있습니다.

2.2 세부 컴포넌트 만들기

위에서 만든 컴포넌트를 사용해서 코드를 작성해보겠습니다.

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;

컴포넌트에서 컴포넌트에 className과 value를 props로 넘겨고있습니다. 컴포넌트에서는 받아온 props의 value 값을 사용해 h3 태그에 들어갈 텍스트를 설정해줍니다. props.className으로는 조건문을 통해 className의 값에 따라 width값을 적용시켜줍니다.

이제 각각의 모달에 들어갈 고유한 내용을 별도의 컴포넌트로 만들어보겠습니다.

챌린지 설정하기 카드
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>
    </>
  );
};

2.3 합성하여 사용하기

이제 만든 컴포넌트들을 조합하여 사용할 수 있습니다. props.children 속성을 통해 자식으로 사용되는 부분이 어디에 표현되어야 하는지 설정할 수 있습니다.

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>
  );
};
function App() {
  return (
    <>
      <Card className="setting" value="챌린지 설정">
        <SettingCard />
      </Card>
      <Card className="share" value="서비스 공유하기">
        <ShareCard />
      </Card>
    </>
  );
}

이렇게 합성된 컴포넌트는 props.children을 통해 자식 컴포넌트를 받아올 수 있습니다. Card 컴포넌트는 어떤 내용이 들어올지 미리 알 필요 없이, 전달받은 자식 요소를 그대로 렌더링합니다.

2.4 최종 코드

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;

3. 실습: 상품 카드 만들기

위에서 배운 합성 컴포넌트의 개념을 활용하여 상품 카드를 만들어보겠습니다.

[기본 구조]

<ContCard>
  <ProductImage src="https://picsum.photos/id/237/200/300" alt="검은강아지" />
  <ProductTitle>검은 강아지를 입양하세요</ProductTitle>
  <ProductSubTitle>엄청 얌전하답니다. 우리개는 안물어요!</ProductSubTitle>
  <ProductDescription>
    Lorem ipsum dolor sit amet consectetur adipisicing elit. Alias distinctio
    sed molestias saepe reiciendis fugit illum enim et inventore, aliquam esse
    non nam consectetur minima atque consequuntur voluptates, eum quia.
  </ProductDescription>
  <ProductBreed breed="래브라도 리트리버" />
</ContCard>
합성 컴포넌트 - React 에센셜 | 위니버시티