React 합성과 상속

React 컴포넌트 합성 가이드

React는 컴포넌트 간에 상속합성을 모두 지원하지만, React는 강력한 합성 모델을 가지고 있기 때문에 상속 대신 합성을 사용하여 컴포넌트 간에 코드를 재사용하는 것을 권장합니다.

컴포넌트에 다른 컴포넌트 담기

어떤 컴포넌트들은 어떤 자식 엘리먼트가 들어올지 미리 예상할 수 없는 경우가 있는데, 대표적으로 범용적인 “박스” 역할을 하는 SidebarDialog와 같은 컴포넌트에서 특히 자주 볼 수 있습니다.

이런 종류의 컴포넌트에서는 특수한 propchildren을 사용하여 자식 엘리먼트를 출력에 그대로 전달하는 것이 좋습니다.

children prop 사용하기
function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}

이러한 방식으로 다음 코드와 같이 다른 컴포넌트에서 JSX를 중첩하여 임의의 자식을 전달할 수도 있습니다.

임의의 자식 전달하기
function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

위 코드에서는 <FancyBorder> JSX 태그 안에 있는 것들이 FancyBorder 컴포넌트의 children prop으로 전달되는데, FancyBorder{props.children}<div> 안에 렌더링하기 때문에 전달된 엘리먼트들이 최종 출력되는 것을 확인할 수 있습니다.

일반적이진 않지만, 다음의 코드와 같이 종종 컴포넌트에 “여러 개의 영역”이 필요할 수도 있는데, 이런 경우에는 children 대신 자신만의 고유한 방식을 적용할 수도 있습니다.

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

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

위 코드의 <Contacts /><Chat />같은 React 엘리먼트는 단지 객체이기 때문에 다른 데이터처럼 prop으로 전달할 수 있는데, 이러한 접근은 다른 라이브러리의 “슬롯“과 비슷해보이지만 React에서는 prop으로 전달할 수 있는 것에는 제한이 없습니다.

특수화

가끔은 특정 컴포넌트의 “특수한 컴포넌트”를 고려해야 하는 경우가 있는데, 예를 들면 다음 코드WelcomeDialogDialog의 특수한 경우라고 할 수 있습니다. 이런 경우 React는 역시 합성을 통해 해결할 수 있는데, 더 “구체적인” 컴포넌트가 “일반적인” 컴포넌트를 렌더링하고 props를 통해 내용을 구성하게 됩니다.

컴포넌트 합성하기
function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
    </FancyBorder>
  );
}

function WelcomeDialog() {
  return (
    <Dialog
      title="Welcome"
      message="Thank you for visiting our spacecraft!" />
  );
}

합성은 다음 코드와 같이 클래스로 정의된 컴포넌트에서도 동일하게 적용됩니다.

클래스 컴포넌트 합성하기
function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
      {props.children}
    </FancyBorder>
  );
}

class SignUpDialog extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.handleSignUp = this.handleSignUp.bind(this);
    this.state = {login: ''};
  }

  render() {
    return (
      <Dialog title="Mars Exploration Program"
              message="How should we refer to you?">
        <input value={this.state.login}
               onChange={this.handleChange} />
        <button onClick={this.handleSignUp}>
          Sign Me Up!
        </button>
      </Dialog>
    );
  }

  handleChange(e) {
    this.setState({login: e.target.value});
  }

  handleSignUp() {
    alert(`Welcome aboard, ${this.state.login}!`);
  }
}

React와 상속

Facebook에서는 수천 개의 React 컴포넌트를 사용하지만, 컴포넌트를 상속 계층 구조로 작성을 권장할만한 사례를 찾을 수 없었다고 합니다. React의 props합성은 명시적이고 안전한 방법으로 컴포넌트의 모양과 동작을 커스터마이징하는데 필요한 모든 유연성을 제공하고 있습니다.

컴포넌트는 원시 타입의 값, React 엘리먼트 혹은 함수 등 어떠한 props도 받을 수 있는데, UI가 아닌 기능을 여러 컴포넌트에서 재사용하기를 원하는 경우에는 별도의 JavaScript 모듈로 분리하여 컴포넌트에서 해당 함수, 객체, 클래스 등을 import하여 사용할 수 있기 때문에 상속을 받을 필요가 없습니다.

답글 남기기