2019-06-10 TIL

2019-06-10

React: Render Props Pattern

공식문서

리액트에서, 어떤 컴포넌트에 사용된 로직을 재사용 가능하게 만들 때 사용할 수 있는 패턴이다.

이를 위해서는 우선 로직과 UI를 별개의 컴포넌트로 분리해야 한다. (로직만 재사용 해야하니까 분리는 당연한 전제조건이다)

The term “render prop” refers to a technique for sharing code between React components using a prop whose value is a function.

“render prop”이란, 함수를 값으로 갖는 prop을 사용해 리액트 컴포넌트간 코드를 공유하기 위한 테크닉을 일컫습니다.

<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>

prop의 이름이 꼭 render일 필요는 없다. 함수를 넘겨주는것은 필수조건이다.

위 코드에서 <DataProvider ../>라고 이름붙인 컴포넌트가 로직을 담당한다. 공식 문서의 예제에서는 현재 마우스 커서의 좌표를 받아오는 기능을 한다.

renderprop을 통해 어떤 컴포넌트가 렌더될지 정해준다. 이 함수가 리턴하는 컴포넌트는 DataProvider가 제공하는 데이터에 따라 다른 내용으로 렌더될 수 있다.

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}

class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>

        {/*
          Instead of providing a static representation of what <Mouse> renders,
          use the `render` prop to dynamically determine what to render.
        */}
        {this.props.render(this.state)}
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

<Cat /> 컴포넌트가 <Mouse />컴포넌트가 제공하는 좌표를 받아 화면에 렌더되며, state가 변할 때 마다 update가 일어난다.

마우스 커서를 따라다니는 고양이 이미지를 볼 수 있다.

PureComponent와 사용시 주의점

class Mouse extends React.PureComponent {
  // Same implementation as above...
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>

        {/*
          This is bad! The value of the `render` prop will
          be different on each render.
        */}
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

로직을 제공하는 <Mouse />컴포넌트가 PureComponent를 extend하는 경우, shallow compare를 수행하므로 render prop으로 전달된 함수가 매 렌더마다 새로 생성된다. (deep compare라면 실제로 새 함수가 전달되어도 내용이 같다면 같은 함수로 받아들일 것이다)

이런 경우 함수를 메서드로 정의해 사용하도록 한다.

class MouseTracker extends React.Component {
  // Defined as an instance method, `this.renderTheCat` always
  // refers to *same* function when we use it in render
  renderTheCat(mouse) {
    return <Cat mouse={mouse} />;
  }

  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={this.renderTheCat} />
      </div>
    );
  }
}

이러면 renderprop에 전달되는 값은 항상 renderTheCat메서드를 가리키는 동일한 주소가 된다.


Minchang Kim
Minchang Kim
웹/앱 개발자 김민창입니다! 좋은 하루 되세요!