합성(Composition)
- 여러 개의 컴포넌트를 합쳐서 새로운 컴포넌트를 만드는 것
Containment
- 하위 컴포넌트를 포함하는 형태의 합성 방법
- 리액트 컴포넌트의 props에 기본적으로 들어 있는 children 속성 사용
예제 1 - children이 한개일 때
- 자신의 하위 컴포넌트를 포함하여 예쁜 테두리로 감싸주는 컴포넌트
props.children
을 사용하여 해당 컴포넌트의 하위 컴포넌트가 children 이라는 이름으로 들어오게 됨
import React from 'react';
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
export default FancyBorder;
import React from "react";
import FancyBorder from "./FancyBorder";
function WelcomeDialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
어서오세요.
</h1>
<p className="Dialog-message">
제 블로그에 방문하신걸 환영합니다.!
</p>
</FancyBorder>
);
}
export default WelcomeDialog;
.FancyBorder {
border: 2px solid #aaa;
padding: 10px;
border-radius: 5px;
}
.FancyBorder-blue {
border-color: blue;
}
- 실행 결과
예제 2 - 여러 개의 children 집합
- 별도의 props를 정의해서 각각 원하는 컴포넌트를 넣어주면 됨
예시 코드
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.rigiht}
</div>
</div>
);
}
function App(props) {
return (
<SplitPane
left={
<Contacts />
}
right={
<Contacts />
}
/>
);
}
- 화면을 왼쪽과 오른쪽으로 분할해서 보여주는 SplitPane 컴포넌트
- App 컴포넌트에서는 SplitPane 컴포넌트를 사용하여 left, right 두 개의 props를 정의한 후 그 안에 각각 다른 컴포넌트를 넣어주게 됨
- SplitPaned에서는 이 left, right를 props로 받게되고 각각 화면의 왼쪽과 오른쪽에 분리해서 렌더링
Specialization
- 범용적인 개념을 구별이되게 구체화하는 것
- 범용적으로 쓸 수 있는 컴포넌트를 만들어 놓고 이를 구체화 시켜서 컴포넌트를 사용하는 합성 방법
예시 코드
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</FancyBorder>
);
}
function WelcomeDialog(props) {
return (
<Dialog title="어서 오세요"
message="제 블로그에 오신 것을 환영합니다!"
/>
);
}
- Dialog라는 범용적인 의미를 가진 컴포넌트와 Dialog 컴포넌트를 사용하는 WelcomeDialog 컴포넌트
- Dialog 컴포넌트에서는 title과 message라는 두 가지 props를 가짐
- 어떤 title과 message를 가지느냐에 따라 다양한 다이얼로그를 만들 수 있음
Containment + Specialization
- props.children을 통해 하위 컴포넌트를 포함시키고(Containment)
- 별도의 props를 선언하여 구체화 시키는 합성 방법(Specialization)
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</FancyBorder>
);
}
function SignUpDialog(props) {
const [nickname, setNickname] = useState('');
const handleChange = (event) => {
setNickname(event.target.value);
}
const handleSignUp = () => {
alert(`어서오세요, ${nickname} 님!`)
}
return (
<Dialog
title="화성 탐사 프로그램"
message="닉네임을 입력해주세요.">
<input
value={nickname}
onChange={handleChange}/>
<button onClick={handleSignUp}>
가입하기
</button>
</Dialog>
)
}
- Dialog 컴포넌트에
props.children
을 추가하여 하위 컴포넌트가 다이얼로그 하단에 렌더링 되도록 작성
- SignUpDialog 컴포넌트에서는 Specialization을 위해 props인 title과 message 값을 넣어주고 있음
- 또한, 사용자로부터 닉네임을 입력받고 가입하도록 유도하기 위해 input과 button 태그를 사용
- 이 두 개의 태그는 props.children으로 전달되어 다이얼로그에 표시됨(Containment)
상속
- 다른 컴포넌트로부터 상속받아서 새로운 컴포넌트를 만드는 것
- 상속을 사용하여 컴포넌트를 만드는 것을 추천할 만한 사용 사례를 찾지 못함
- 리액트에서는 상속이라는 방법을 사용하는 것보다는 합성을 사용하는 것이 좋음
실습 - Card 컴포넌트 만들기
- 하위 컴포넌트를 감싸서 카드 형태로 보여주는 Card 컴포넌트 작성
- Containment와 Specialization 두 가지 합성 방법 모두 사용
function Card(props) {
const { title, backgroundColor, children } = props;
return (
<div
style={{
margin: 8,
padding: 8,
borderRadius: 8,
boxShadow: "0px 0px 4px grey",
backgroundColor: backgroundColor || "white",
}}
>
{title && <h1>{title}</h1>}
{children}
</div>
);
}
export default Card;
- 위에서 작성한 Card 컴포넌트를 사용할 ProfileCard 컴포넌트 작성
import Card from "./Card";
function ProfileCard(props) {
return (
<Card title="Inje Lee" backgroundColor="#4ea04e">
<p>안녕하세요, 문지은입니다.</p>
<p>저는 리액트를 사용해서 개발하고 있습니다.</p>
</Card>
);
}
export default ProfileCard;
- 실행 결과
실습 전체 코드
References