'컴포지션(Composition)'의 개념

Yerim Son·2023년 6월 27일
1

React

목록 보기
8/23

+ 지난 이야기...
App.js에서 Expenses를 사용하는데, 그 컴포넌트 안에서 ExpenseItem과 ExpenseDate를 사용한다.
작은 빌딩 블럭으로부터 사용자 인터페이스를 구축하는 이런 접근 방법을 일반적으로 합성(Composition)이라 부른다.


나는.. 가끔은.. props를 통해 모든 것을 설정하는 컴포넌트가 아닌... 열고 닫는 태그 사이에 있는 컨텐츠를 전달하구 싶다...

  • 현재 출력 모습

모든 expenses item을 감싸는 container가 있다.
회색 바탕에 모서리가 둥글고, 옅은 그림자가 있는 상자다.
또 전체 비용 목록을 감싸는 컨테이너의 모습도 비슷하다.

우리는 재사용한 빌딩 블럭을 가져야 하고, 코드 중복을 피해야 한다.
하지만 지금은 몇 스타일이 중복되고, 일부 html구조도 중복되고 있다.

그래서, ExpenseItem.js, Expenses.js에서 모두 갖고 있는 컨테이너 <div>와 공동으로 갖고 있는 스타일을 별도의 component로 추출할 수 있다.(ex. 둥근 모서리, 그림자).

그래서
1. Card.js에 Card Component를 만든다.
2. <div>를 반환하고, 공통으로 적용되는 Card.css를 import하여 Card모양 css를 Card component에 줄 것이다.

💡 Card Component: 일반적으로 Card는 보통 둥근 모서리에, 옅은 그림자 등과 같은 요소들이 있는 컨테이너 모양을 의미한다.



  • Card.js
import "./Card.css";

function Card() {
  return <div className="card"></div>;
}

export default Card;

💡 여기서 Card Container Component는 ExpenseItem, Expenses 컨텐츠를 둘러싼 shell같은 역할을 한다.

위와 같이 만들어진 사용자 지정 Card component로 내장된 <div>를 대체할 수 있다.

  • ExpenseItem.js
return (
    <Card className="card"> // div로 감싸져있던 부분을 Card component로 대체해서 감쌈.
      <ExpenseDate date={props.date} amount={props.amount} />
      <div className="expense-item__description">
        <h2>{props.title}</h2>
        <div className="expense-item__price">{props.amount}</div>
      </div>
    </Card>
  );

위와 같이 수정 후 저장하면 모든 비용item들이 화면에서 사라진다.
상자 밖에 있기 때문이다.

하얀 강아지

그것처럼 사용자 지정 컴포넌트를 컨텐츠를 감싸는 wrapper로 사용할 수 없다.

So it would be nice if we could also make it work for our custom components to build such reusable wrapper components like <h2>{props.title}</h2>, for example.

그리고 당연하게도 React는 솔루션이 있다!

Card.js에서 내가 설정한 적 없는! 리액트에 내장된 특별한 props를 사용할 것이다.

  • Card.js
function Card(props) {
  return <div className="card">{props.children}</div>;
}

props.children은 예약어이고, 언제나 사용자 지정 컴포넌트에 있는 열고 닫는 태그 사이에 있는 컨텐츠이다.

  • ExpenseItem.js
return (
    <Card className="card">
      <ExpenseDate date={props.date} amount={props.amount} />
      <div className="expense-item__description">
        <h2>{props.title}</h2>
        <div className="expense-item__price">{props.amount}</div>
      </div>
    </Card>
  );

그래서 위와 같이 <Card>사이에 있는 컨텐츠는 Card Component안에 있는 props.children에서 사용 가능하다.

💡 <Card className="card"> : Card는 내가 정의한 사용자 지정 컴포넌트기 때문에 className은 props를 설정한 것이다!!!!!!
(박스 밖의 모든 default Html component such as h1, h2...는 렌더링된 html요소들에게 CSS클래스를 추가하는 className을 지원하지만, 사용자정의 컴포넌트들은 내가 지원하라고 지시한 것만 지원한다.)




Card component에 className이 설정되길 원하고, 영향을 미치길 원한다면 Card component에 있는 코드를 약간 조정해야 한다.

  • Card.css
.card {
  border-radius: 12px;
  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.25);
}

(Expenses.css, ExpenseItem.css에 있던 속성들을 빼서 Card.css에 넣어준 것임)

  • Card.js
import "./Card.css";

function Card(props) {
  const classes = 'card ' + props.className;
  return <div className={classes}> {props.children}</div>;
}

export default Card;

이제 밖에서 className으로 받는 것들은 문자열로 추가된다.
그런 다음, 동적으로 이 클래스의 상수를 가리킬 수 있다.

추출이 가능하다는 것은, 수많은 코드 중복을 피할 수 있게 하주고 다른 컴포넌트를 깔끔하게 유지할 수 있게 해준다. 이것이 합성의 또다른 특징이다.

여기까지 하면 아래와 같은 출력이...

합성에서 중요한 부분은 props.childrend인데, 래퍼 컴포넌트를 생성하게 하며 특별한 컴포넌트라고 할 수 있다. 종종 필요하다.


복습

Component: 코드를 여러 개의 파일과 빌딩 블럭으로 나눔. 지금 하고 있는 프로젝트를 예로, 한 개 이상의 비용item을 원한다면 모든 코드를 여러 번 반복하는 대신에 사용자 지정 ExpenseItem component를 원하는 만큼 사용할 수 있다.

0개의 댓글