[React] 컴포넌트 Composition(합성): props.children

summereuna🐥·2023년 3월 2일
0

React JS

목록 보기
24/69

컴포넌트는 JSX코드를 결합한 사용자 정의 html + 스타일링 + 조금의 JS 로직이다.

사용자 인터페이스를 구축하기 위해 모든 컴포넌트를 생성한다.
작은 빌딩 블럭으로 사용자 인터페이스를 구축하는 접근 방법을 일반적으로 합성(Composition)이라고 한다.

구체적인 목적이 있는 컴포넌트들은 props(상속)를 통해 설정한다.
예를 들어 현재 ExpenseDate 컴포넌트는 비용을 지출한 날짜를 캘린더 모양으로 렌더링하는 컴포넌트이다. 또한 ExpenseItem은 ExpenseDate를 포함하여 비용 지출한 목록을 렌더링하는 컴포넌트이다.

하지만 props를 통해 모든 것을 설정하는 컴포넌트가 아닌, 컴포넌트의 열고 닫는 태그 사이에 있는 컨텐츠를 전달하기 위한 컴포넌트를 만들고 싶다면 어떻게 해야 할까? 이런 컴포넌트는 구체적인 목적이 있기 보다는 스타일링 등을 위해 그냥 감싸는 용도 등이다.
이럴 때 합성(Composition)을 사용한다.
예를 들어 Expenses 컴포넌트에는 전체를 감싸는 둥근 박스 모양 스타일링이 있고, ExpenseItem 컴포넌트도 각각 둥근 박스 모양의 스타일링으로 감싸져 있다. 또한 둘다 div로 나머지 코드를 감싸고 있다.

📌 컴포넌트는 재사용가능한 빌딩 블럭을 가지는 동시에 코드 중복을 피하기 위해 존재한다.

현재 동일한 스타일링이 중복되어 있는데 이를 해결하기 위해 둥근 박스 형태를 지니는 컴포넌트를 하나 만들고 그 컴포넌트로 Expenses 컴포넌트와 ExpenseItem 컴포넌트를 감싸보자.

보통 웹 개발 시, 둥근 모서리에 옅은 그림자를 가진 컨테이너 모양을 "Card"라고 한다. 코드 중복을 피하면서 같은 스타일링을 적용할 수 있도록 Card.js 컴포넌트를 만들어 보자.


1. Card 컴포넌트 만들고 스타일 주기

📍 Card.css

//둥근 박스 형태 스타일
.card {
  border-radius: 12px;
  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.25);
}

📍 Card.js

//둥근 박스 스타일로 다른 컴포넌트를 감싸는 용도의 Card 컴포넌트
import "./Card.css";

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

export default Card;

{props.children}
사실 사용자 지정 컴포넌트는 일종의 컨텐츠를 감싸는 html의 div 같은 wrapper로 사용할 수 없다. 하지만 리액트에서 제공하는 props.children을 사용하면 사용자 지정 컴포넌트로도 다른 컨텐츠를 감쌀 수 있다.

따라서 Card 컴포넌트를 감싸는 컴포넌트(wrapper component)로 사용하기 위해 Card 컴포넌트의 매개변수로 props을 받고, root 요소의 값으로 {props.children}을 넣는다.

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


2. ExpenseItem 컴포넌트와 Expenses 컴포넌트의 JSX 부분을 Card 컴포넌트로 감싸기

📍 ExpenseItem.js

import Card from "./Card";
import ExpenseDate from "./ExpenseDate";
import "./ExpenseItem.css";

function ExpenseItem(props) {
  return (
    //Card는 Card.js 컴포넌트에서 지정된 스타일을 함께 가진다.
    <Card className="expense-item" id={props.id}>
      //---------------------------------------------
      //Card 컴포넌트 사이에 있는 이 코드가 바로 Card 컴포넌트의 props.children가 가리키는 것이다.
      <ExpenseDate date={props.date} />
      <div className="expense-item__description">
        <h2>{props.title}</h2>
        <div className="expense-item__price">${props.amount}</div>
      </div>
      //---------------------------------------------
    </Card>
  );
}

export default ExpenseItem;

📍Expenses.js

import "./Expenses.css";
import ExpenseItem from "./ExpenseItem";
import Card from "./Card";

function Expenses(props) {
  return (
    //Card는 Card.js 컴포넌트에서 지정된 스타일을 함께 가진다.
    <Card className="expenses">
      //--------------------------------------------------
      //Card 컴포넌트 사이에 있는 이 코드가 바로 Card 컴포넌트의 props.children가 가리키는 것이다.
      <ExpenseItem
        id={props.items[0].id}
        title={props.items[0].title}
        amount={props.items[0].amount}
        date={props.items[0].date}
      />
      <ExpenseItem
        id={props.items[1].id}
        title={props.items[1].title}
        amount={props.items[1].amount}
        date={props.items[1].date}
      />
      <ExpenseItem
        id={props.items[2].id}
        title={props.items[2].title}
        amount={props.items[2].amount}
        date={props.items[2].date}
      />
      <ExpenseItem
        id={props.items[3].id}
        title={props.items[3].title}
        amount={props.items[3].amount}
        date={props.items[3].date}
      />
      //---------------------------------------------
    </Card>
  );
}

export default Expenses;

자, 이렇게 하면!
스타일이 적용이 안되네!!


3. Card 컴포넌트에서 className 수정하기

📍 Card.js

//둥근 박스 스타일로 다른 컴포넌트를 감싸는 용도의 Card 컴포넌트
import "./Card.css";

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

export default Card;
  1. <div className={classes}>{props.children}</div>
    className={classes}
    Card 컴포넌트가 사용자 지정 컴포넌트라는 사실을 잊지 말자!
    현재 ExpenseItem 컴포넌트에서는 Card 컴포넌트로 다른 컨텐츠를 감싸면서 expense-item 클래스로 다른 스타일링도 추가하고 있다.
    Card 컴포넌트에서 className에 그냥 card라고만 적어버릴 경우, Card 컴포넌트에 지정되어 있는 card 클래스만 적용되고 다른 클래스의 스타일은 적용이 되지 않는다.

    박스 밖에 있는 모든 디폴트 html 컴포넌트(태그)는 랜더링된 html 요소에 css 클래스를 추가하는 className을 지원하지만, 사용자 정의 컴포넌트는 내가 지원하라고 지시한 것만 지원한다.

    따라서 Card 컴포넌트에 다른 className도 설정할 수 있게 하려면 2번 처럼 클래스에 들어갈 상수를 설정해 주고, className에 동적(dynamic)으로 상수(const)를 전달하면 된다.

  1. const classes = "card " + props.className;
    "card " <- 항상 적용되는 default 클래스인 card를 적어주고 반드시 한 칸 space를 주자! 그래야 다른 클래스도 추가 된다.
    ⚠️ 띄워쓰기 안하면 클래스명 다 붙어버려서 스타일 적용 안되니까 띄워쓰기 조심!

    props.className;
    밖에서 className으로 받는 것들을 문자열로 classes 상수에 추가할 수 있도록 한다.



컴포넌트 컴포지션(합성)을 사용하는 이유는?

css파일에서 일부 중복되는 코드를 별도의 래퍼 컴포넌트의 css파일로 추출할 수 있다.
jsx코드 중복도 마찬가지이다.
즉, 중복 사용되는 코드를 하나의 wrapper component로 만들어 코드 중복을 피하고 재사용성을 높이며 다른 컴포넌트의 코드를 깔끔하게 유지할 수 있다.
이는 모달, 알림창 등에 아주 유용하게 사용할 수 있다.

리액트 사용 시, 컴포넌트를 결합할 때 마다 합성을 사용한다.
특히 props.children은 특별한 컴포넌트로 종종 필요하다.

profile
Always have hope🍀 & constant passion🔥

0개의 댓글