리액트 기본 배우기 -합성과 상속-

1. 합성(composition)

 

여러개의 컴포넌트를 합쳐서 새로운 컴포넌트를 만드는 것

 

 

여기서 A라는 컴포넌트와 B라는 컴포넌트가 반복적으로 나타남

 

이 페이지 자체도 하나의 리액트 컴포넌트

 

그래서 이 페이지는 컴포넌트 A와 컴포넌트 B를 합쳐서 페이지 컴포넌트를 만든 것이기 때문에 합성 컴포넌트를 사용했다고 볼 수 있다.

 

리액트로 개발을 하다보면 이처럼 여러개의 컴포넌트를 합쳐서 새로운 컴포넌트를 만드는 일이 많다

 

합성이라고 무작정 그냥 컴포넌트들을 붙이는 것이 아니고 어떻게 조합할 것인가에 대한 고민이 필요하며 이에 따라 합성의 사용기법이 나뉜다

 

 

2. containment

 

하위 컴포넌트를 포함하는 형태의 합성 방법

 

사이드바나 다이얼로그같은 박스 형태의 컴포넌트는 자신의 하위 컴포넌트를 미리 알 수 없다

 

예를 들어 동일한 사이드바 컴포넌트를 사용하는 2개의 쇼핑몰이 있다고 가정해보자

 

하나의 쇼핑몰에는 의류와 관련된 메뉴가 8개 들어있고, 다른 쇼핑몰에는 식료품과 관련된 메뉴가 10개 존재한다.

 

사이드바 컴포넌트 입장에서는 자신의 하위 컴포넌트로 어떤 것들이 올지 알 수 없을 것이다.

 

해당 컴포넌트를 사용하는 개발자가 어떤 것을 넣느냐에 따라 하위 컴포넌트가 달라지기 때문이다.

 

그렇기 때문에 이런 경우 containment 방법을 사용하여 합성을 사용하게 된다.

 

2-1) containment를 사용하는 방법은 리액트 컴포넌트의 props에 기본적으로 들어있는 children 속성을 사용한다.

 

function FancyBorder(props) {
    return (
        <div className={'FancyBorder FancyBorder-' + props.color}>
            {props.children}
        </div>
    )
}

 

위 코드에서 props.children을 사용하면, 해당 컴포넌트의 하위 컴포넌트는 모두 children으로 들어온다

 

children이라는 prop은 개발자가 직접 넣어주는 것이 아닌, 리액트에서 제공해주는 것이다.

 

 

crreateElement()도 위와 같이 호출했는데, 여기서 3번째 파라미터가 바로 children이다

 

children이 배열로 되어있는 이유는, 여러개의 하위 컴포넌트를 가질 수 있기 때문이다.

 

FancyBorder은 결과적으로 자신의 하위 컴포넌트를 모두 포함하여, 예쁜 테두리로 감싸주는 컴포넌트이다.

 

function WelcomeDialog(props) {
    return (
        <FancyBorder color="blue">
            <h1 className="Dialog-title">
                어서오세요
            </h1>
            <p className="Dialog-message">
                우리 사이트에 방문하신 것을 환영합니다!
            </p>
        </FancyBorder>
    )
}

 

위 코드에서 WelcomeDialog라는 컴포넌트가 나오며, 여기에서 FancyBorder 컴포넌트를 사용하고 있다.

 

FancyBorder 컴포넌트로 감싸진 부분 안에는 <h1>과 <p> 이렇게 2개의 태그가 들어가있다.

 

이 2개의 태그는 모두 FancyBorder 컴포넌트에 children이라는 이름의 props로 전달된다.

 

결과적으로 파란색의 테두리로 모두 감싸지는 결과가 나온다

 

 

2-2) 여러개의 children 집합이 필요한 경우가 있다.

 

리액트에서는 props.children을 통해 하위 컴포넌트를 하나로 모아서 제공한다.

 

그런데 여러개의 children이 필요하다면, 별도로 props를 정의해서 각각 원하는 컴포넌트를 넣어주게 된다

 

function SplitPane(props) {
    return (
        <div className = "SplitPane">
            <div className = "SplitPane-left">
                {props.left}
            </div>
            <div className = "SplitPane-right">
                {props.right}
            </div>
        </div>
    )
}

function App(props){
    return (
        <SplitPane
        left={
            <Contacts />
        }
        right={
            <Chat />
        }
        />
    )
}

 

위 코드는 왼쪽과 오른쪽으로 분할해서 보여주는 SplitPane이라는 컴포넌트가 있다

 

그리고 아래에 App 컴포넌트는 이 splitpane을 사용해서 left,right라는 2개의 props를 정의하고,

 

그 안에 각각 다른 컴포넌트를 넣어주고 있다.

 

splitpane에는 이 left,right를 props로 받게 되고 각각 화면 왼쪽과 오른쪽에 분리해서 렌더링한다

 

여러개의 children 집합이 필요하다면 위와 같이 별도의 props를 정의해서 사용한다.

 

 

3. specialization

 

"welcomedialog는 dialog의 특별한 케이스"

 

다이얼로그는 모든 종류의 다이얼로그를 다 포함하는 개념

 

반면 welcomedialog는 누군가를 반기기 위한 다이얼로그, 범용적인 의미가 아닌 구체화된 것

 

이렇게 범용적인 개념을 구별이 되게 구체화하는 것을 specialization이라고 부른다.

 

리액트에서는 합성을 사용하여 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를 가지고 있다

 

각각 다이얼로그에 나오는 제목과 메시지를 의미한다

 

그래서 제목과 메시지를 어떻게 사용하느냐에 따라 경고 다이얼로그, 인사말 다이얼로그가 될 수 있다.

 

welcomedialog에서는 제목을 "어서 오세요"라 하고 사이트에 접속한 사용자에게 인사말을 하는 다이얼로그를 만들었다.

 

이처럼 specialization은 범용적으로 쓸 수 있는 컴포넌트를 만들어 놓고, 이를 특수화 시켜 컴포넌트를 사용하는 합성 방식이다.

 

 

4. containment와 specialization을 같이 사용

 

containment를 위해 props.children을 사용하고, specialization을 위해 직접 정의한 props를 사용하면 될 것 같다

 

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 컴포넌트는 이전에 나온 Dialog 코드에 containment를 위한 props.children을 끝에 추가했다.

 

이를 통해 하위 컴포넌트가 다이얼로그 하단에 렌더링된다.

 

실제로 Dialog 컴포넌트를 사용하는 SignUpDialog 컴포넌트를 살펴보면

 

Specialization을 위한 props인 title, message에 값을 넣어 주고 있으며

 

사용자로부터 닉네임을 입력받고 가입하도록 유도하기 위해 <input>과 <button>태그가 들어있다.

 

이 2개의 태그 input, button는 모두 props.children으로 전달되어 다이얼로그에 표시된다.

 

이런 형태로 containment와 specialization을 동시에 사용할 수 있다.

 

 

5. 상속(inheritance)

 

컴퓨터 프로그래밍에서 상속은 객체지향 프로그래밍에서 나온 개념이다.

 

부모 클래스를 상속받아 새로운 자식 클래스를 만든다는 개념

 

자식 클래스는 부모 클래스가 가진 변수나 함수 등의 속성을 모두 갖는다.

 

리액트에서도 다른 컴포넌트로부터 상속받아 새로운 컴포넌트를 만드는 것을 고려해볼 수 있다.

 

리액트를 개발한 메타는 수천개의 리액트 컴포넌트를 사용한 경험을 바탕으로,

 

추천할만한 상속 기반의 컴포넌트 생성 방법을 찾아보려 했지만 그러지 못했다고 한다...

 

그래서 리액트에서는 상속보다는 합성을 사용해서 개발하는 것이 더 좋은 방법

 

결론은

 

"복잡한 컴포넌트를 쪼개 여러개의 컴포넌트를 만들고, 만든 컴포넌트를 조합해서 새로운 컴포넌트를 만든다"

 

 

 

6. 실습으로 따라해보기

 

$npx create-react-app my-app으로 프로젝트 생성

 

src 폴더 내에 chapter_13이라는 이름의 폴더 하나 생성

 

Card.jsx 파일을 만들고 코드 작성

 

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;

 

하위 컴포넌트 children을 감싸서 카드 형태로 보여주는 컴포넌트 Card

 

containment와 specialization을 모두 사용한 합성기법

 

children을 사용한 부분이 containment이고 title, background를 사용한 부분이 specialization

 

범용적으로 재사용이 가능하며, 이를 이용해 ProfileCard 컴포넌트를 만들어본다

 

import Card from './Card'

function ProfileCard(props) {
    return (
        <Card title="daehyuck" backgroundColor="#ffc0cb">
            <p> 안녕하세요, 대혁입니다.</p>
            <p> 리액트를 배우고 있습니다. </p>
        </Card>
    )
}

export default ProfileCard

 

 

title에 이름을 넣고, background에 분홍색으로 설정

 

children에는 간단한 소개글을 넣었다

 

이렇게 하면 card 컴포넌트가 사용자의 프로필을 나타내는 profilecard 컴포넌트가 된다.

 

화면에 렌더링하기 위해 index.js 파일을 수정

 

import ProfileCard from './chapter_13/ProfileCard';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <ProfileCard />
  </React.StrictMode>
);

 

npm start로 실행

 

 

TAGS.

Comments