[React] 합성 컴포넌트? 소 + 소 = 대!

hyeonbin·2023년 5월 1일

FE-log

목록 보기
2/9
post-thumbnail

📃 합성 컴포넌트

✨ 합성 컴포넌트란?

  • 작은 컴포넌트 + 작은 컴포넌트 = 큰 컴포넌트
  • 합성 컴포넌트를 사용하면 여러 컴포넌트에서 재사용 가능한 작은 컴포넌트를 만들 수 있다.
  • 컴포넌트 구조가 단순해져서 가독성과 유지보수성이 높아진다.

하단에 간단한 모달을 만드는 기본 예시를 확인해 보자!

기본 예시

[ App.js ]

import React, { useState } from 'react';
import Modal from './components/Modal2';
import styled from 'styled-components';

const App = () => {
  const [isModalOpen, setIsModalOpen] = useState(false);

  const handleOpenModal = () => {
    setIsModalOpen(true);
  };

  const handleCloseModal = () => {
    setIsModalOpen(false);
  };

  return (
    <Container>
      <h1>Welcome to my app!</h1>
      <button onClick={handleOpenModal}>Open Modal</button>
      {isModalOpen && (
        <Modal title="Hello 😉" content="Welcome Modal World" onClose={handleCloseModal} />
      )}
    </Container>
  );
};

export default App;

const Container = styled.div`
  width: 500px;
  margin: 30px auto;
  text-align: center;
`;

[ App 컴포넌트 ]
  1. const [isModalOpen, setIsModalOpen] = useState()
    를 이용해 모달의 열림/닫힘 버튼의 상태를 관리한다.
  1. handleOpenModal함수는 모달을 열기 위해,
    handleCloseModal 함수는 모달을 닫기 위해 호출된다.
  1. JSX를 이용해 화면에 컨테이너를 생성하고, 모달이 열려있을 때만
    제목(title) / 내용(content)등이 담긴 Modal 컴포넌트를 렌더링한다.


[ Modal.jsx ]

import React from 'react';

const Button = (props) => {
  const { text, onClick } = props;
  return <button onClick={onClick}>{text}</button>;
};

const Modal = (props) => {
  const { title, content, onClose } = props;
  return (
    <>
      <h2>{title}</h2>
      <p>{content}</p>
      <Button text="Close Modal" onClick={onClose} />
    </>
  );
};

export default Modal;

[ Modal 컴포넌트 ]
  1. const { title, content, onClose } = props 구조 분해 할당을 이용해 props 객체에서
    제목(title) / 내용(content) / 닫기 함수(onClose) 속성을 추출해 받아 화면에 렌더링한다.
  1. 'Close Modal' text를 가진 Button 컴포넌트를 렌더링하고,
    버튼을 클릭 시 onClose 함수가 호출되도록 설정되었다.

[ Button 컴포넌트 ]

  1. const { text, onClick } = props 구조 분해 할당을 이용해 props 객체에서
    텍스트(text) / 열기 함수(onClick) 속성을 추출해 받아 화면에 렌더링한다.
  1. JSX를 이용해 버튼을 생성하고, onClick 함수를 호출한다.

=> 이렇게 각자 기능을 하는 Button / Modal 컴포넌트를 만들어 다른 컴포넌트에서 버튼과 모달을 재사용할 수 있다.

그런데 만약, 여러 개의 비슷한 모달이 필요하다면?

  • 합성 컴포넌트를 사용하지 않으면 비슷한 컴포넌트를 여러 개 만들어야 한다.
  • 결국 코드가 중복되고, 재사용 할 수 있는 부분도 재사용 할 수 없게 된다.

이제 합성 컴포넌트를 적용해 보자!




✨ 합성 컴포넌트 적용하기

1. 모달창 2개 만들기

import React from 'react';
import styled from 'styled-components';
import { createGlobalStyle } from 'styled-components';
import { normalize } from 'styled-normalize';

const GlobalStyle = createGlobalStyle`
${normalize}

button {
  width: 70px;
  height: 25px;
  background: #FFD69D;
  border: 1px solid gray;
  border-radius: 5px;
  margin: 5px;
  font-size: 13px;
}
`;

const CardDiv = styled.div`
  padding: 20px;
  border-radius: 10px;
  border: 1px solid #c4c4c4;
  background-color: #fcffde;
  margin: 30px auto;
  width: 200px;
  text-align: center;
`;

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;
  background-color: #fcffde;
  margin: 30px auto;
  width: 500px;
  text-align: center;
`;

const CardTwo = (props) => {
  return (
    <>
      <GlobalStyle />
      <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>
          <button>삭제하기</button>
        </div>
      </CardDivTwo>
    </>
  );
};

function App() {
  return (
    <>
      <CardOne />
      <CardTwo />
    </>
  );
}

export default App;


2. 겹치는 코드 확인하기

재사용되는 부분 확인

// style - width를 제외하고 코드 동일 
const CardDiv = styled.div`
  padding: 20px;
  border-radius: 10px;
  border: 1px solid #c4c4c4;
  background-color: #fcffde;
  margin: 30px auto;
  width: 200px;
  text-align: center;
`;

// tag 동일
<CardDiv>
  <h3></h3>
  <hr />
</CardDiv>

합성 컴포넌트로 만들기

import React from 'react';
import styled from 'styled-components';
import { createGlobalStyle } from 'styled-components';
import { normalize } from 'styled-normalize';

const GlobalStyle = createGlobalStyle`
${normalize}

button {
  width: 70px;
  height: 25px;
  background: #FFD69D;
  border: 1px solid gray;
  border-radius: 5px;
  margin: 5px;
  font-size: 13px;
}
`;

const CardDiv = styled.div`
  padding: 20px;
  border-radius: 10px;
  border: 1px solid #c4c4c4;
  background-color: #fcffde;
  margin: 30px auto;
  width: ${(props) => (props.className === 'yourCard' ? '300px' : '500px')};
  text-align: center;
`;

const Card = (props) => {
  return (
    <>
      <CardDiv className={props.className}>
        <h3>{props.value}</h3>
        <hr />
      </CardDiv>
    </>
  );
};

const App = () => {
  return (
    <>
      <GlobalStyle />
      <Card className="yourCard" value="너의 카드" />
      <Card className="myCard" value="나의 카드" />
    </>
  );
}

export default App;

코드 설명
  1. Card 컴포넌트를 하나 만들고, className props로 넘겨준 값으로 styled-component
    props 조건문을 통해 className props 값에 따라 width가 달리진다.
  1. value props를 사용해 h3태그에 들어갈 텍스트를 설정한다.


3. 겹치는 코드 재사용하기

합성 컴포넌트 재사용하기

const YourCard = () => {
  return (
    <>
      <button>초기화</button>
      <button>저장하기</button>
    </>
  );
};

const MyCard = () => {
  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>
        <button>삭제하기</button>
      </div>
    </>
  );
};

코드 설명

<hr /> 태그 아래에 들어갈 내용들 YourCard, MyCard 각각 컴포넌트를 만든다.


컴포넌트 합치기

const CardDiv = styled.div`
  padding: 20px;
  border-radius: 10px;
  border: 1px solid #c4c4c4;
  background-color: #fcffde;
  margin: 30px auto;
  width: ${(props) => (props.className === 'yourCard' ? '300px' : '500px')};
  text-align: center;
`;

const Card = (props) => {
  return (
    <>
      <CardDiv className={props.className}>
        <h3>{props.value}</h3>
        <hr />
        <div>{props.children}</div>
      </CardDiv>
    </>
  );
};

코드 설명

props.children 속성을 통해 자식으로 사용되는 부분이 들어가야 하는 곳 <hr /> 태그 아래에 해당 코드를 넣는다.


최종 App 컴포넌트 코드

const App = () => {
  return (
    <>
      <GlobalStyle />
      <Card className="yourCard" value="너의 카드">
        <YourCard />
      </Card>
      <Card className="myCard" value="나의 카드">
        <MyCard />
      </Card>
    </>
  );
};

코드 설명

컴포넌트 사이에 들어가는 자식요소를 props.children으로 받아오게 되면 최종 App 컴포넌트 안에서 위와 같은 방식으로 사용할 수 있다.


4. 전체 코드

import React from 'react';
import styled from 'styled-components';
import { createGlobalStyle } from 'styled-components';
import { normalize } from 'styled-normalize';

const GlobalStyle = createGlobalStyle`
${normalize}

button {
  width: 70px;
  height: 25px;
  background: #FFD69D;
  border: 1px solid gray;
  border-radius: 5px;
  margin: 5px;
  font-size: 13px;
}
`;

const CardDiv = styled.div`
  padding: 20px;
  border-radius: 10px;
  border: 1px solid #c4c4c4;
  background-color: #fcffde;
  margin: 30px auto;
  width: ${(props) => (props.className === 'yourCard' ? '300px' : '500px')};
  text-align: center;
`;

const Card = (props) => {
  return (
    <>
      <CardDiv className={props.className}>
        <h3>{props.value}</h3>
        <hr />
        <div>{props.children}</div>
      </CardDiv>
    </>
  );
};

const YourCard = () => {
  return (
    <>
      <button>초기화</button>
      <button>저장하기</button>
    </>
  );
};

const MyCard = () => {
  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>
        <button>삭제하기</button>
      </div>
    </>
  );
};

const App = () => {
  return (
    <>
      <GlobalStyle />
      <Card className="yourCard" value="너의 카드">
        <YourCard />
      </Card>
      <Card className="myCard" value="나의 카드">
        <MyCard />
      </Card>
    </>
  );
};

export default App;
profile
할 수 있다고 믿는 사람은 결국 그렇게 된다 😄😊

0개의 댓글