리액트에는 컴포넌트 간에 코드를 재사용할 수 있게 해주는 합성 (Composition) 모델이라는 것이 존재한다. 작은 빌딩 블록(컴포넌트)을 모아서 사용자 인터페이스를 만드는 것이라고도 한다.
구체적으로는 컴포넌트의 공통된 UI 적인 요소, 즉 border-radius
, box-shadow
등의 인터페이스를 만드는 박스나 쉘 역할을 하는 컴포넌트를 만들고 그것이 Wrapper 컴포넌트가 되어 자식 요소의 출력을 그대로 돕는다.
예시를 들어 설명해보자면 아래 이미지에서 (1)번 컴포넌트와 (2)번 컴포넌트에서 공통적으로 적용된 CSS 요소인 border-radius
, box-shadow
를 따로 Card
라는 이름의 컴포넌트로 만들어서 분리하고 그것을 사용하고자 하는 컴포넌트 파일에 import 하여 사용하는 것이다.
웹 개발 영역에서 Card
용어를 사용한다면 그것은 상자처럼 보이는 것을 의미한다고 한다.
Card.js 컴포넌트
import './Card.css'; function Card(props) { return <div className="card"></div>; } export default Card;
Card.css
.card { border-radius: 12px; box-shadow: 0 1px 8px rgba(0, 0, 0, 0.25); }
변경 전 (2) ExpenseItem.js 컴포넌트
import ExpenseDate from './ExpenseDate'; import Card from './Card'; import './ExpenseItem.css'; function ExpenseItem(props) { return ( <div className="expense-item"> <ExpenseDate date={props.date} /> <div className="expense-item__description"> <h2>{props.title}</h2> <div className="expense-item__price">${props.amount}</div> </div> </div> ); } export default ExpenseItem;
변경 후 (2) ExpenseItem.js 컴포넌트
import ExpenseDate from './ExpenseDate'; import Card from './Card'; import './ExpenseItem.css'; function ExpenseItem(props) { return ( <Card className="expense-item"> // <Card> 로 변경됨 <ExpenseDate date={props.date} /> <div className="expense-item__description"> <h2>{props.title}</h2> <div className="expense-item__price">${props.amount}</div> </div> </Card> // </Card> 로 변경됨 ); } export default ExpenseItem;
위의 파일에서 <div>
가 아닌 <Card>
라는 Wrapper 컴포넌트로 감싸주게 되면 화면에 출력되는 모든 요소가 사라지게 된다. 그 이유는 Custom 컴포넌트인 Card.js
를 다른 컨텐츠에서 Wrapper 로 사용할 수 없기 때문이다. 또한 opening tag 와 closing tag 사이에 컨텐츠가 있으면 작동하지 않는다.
그렇다면 이를 해결할 방법이 아예 없는 것은 아니다! 리액트에는 방법이 있다.
바로 props.children
을 사용하는 것이다.
모든 컴포넌트가 받을 수 있는 특별한 props 가 리액트에 빌트인 되어 있는데 그것이 바로 props.children
이다. 다른 컴포넌트에서 opening tag 와 closing tag 사이에 컨텐츠를 모두 받는 방법이며 custom 컴포넌트 (Card.js
) 를 다른 컴포넌트에서 Wrapper 로 사용할 수 있게 해준다.
Card.js 컴포넌트
Card.js
에서 {props.children}
을 작성해준다면 다른 컴포넌트에서 JSX를 중첩하여 임의의 자식을 전달할 수 있다.
또한 Card.js
의 className 을 const classes = 'card ' + props.className
으로 작성함으로써 공통적으로 적용하기 위해 따로 분리한 UI 적인 요소 뿐만 아니라 Wrapper 컴포넌트(ExpenseItem.js
)의 className 도 함께 적용이 되어 화면에 출력된다.
ExpenseItem.js 컴포넌트
<Card>
JSX 태그 안에 있는 것들이 Card
컴포넌트의 children prop으로 전달된다. Card
는 {props.children}
을 <div>
안에 렌더링하므로 전달된 엘리먼트들이 최종 출력된다.
이러한 작업을 통해 우리는 HTML 코드를 추출할 수도 있고 div에 있는 JSX 코드도 추출 할 수 있다. 코드를 추출 및 복제를 저장할 수 있게 되어 다른 컴포넌트는 깔끔하게 유지할 수 있다. 즉, Card
를 Wrapper 로 구성할 수 있고 빌트인 HTML 요소를 이용해서 구성할 수 있다.
모든 컴포넌트와 요소가 함께 모여서 전체적인 ExpenseItem
컴포넌트를 형성하며 사용자 인터페이스를 만든다.
결론적으로, 합성 (Composition) 에서 중요한 점은 props.children
이며 이것은 Wrapper 컴포넌트를 만들 수 있게 도와준다.
Udemy 강의
React 공식 문서