Jun 개발노트

Render Props

December 06, 2020

1. Render Prop?

  • props의 이름이 render.
  • render는 해당 컴포넌트의 state를 props로 내린다.
  • render prop은 무엇을 렌더링할지 컴포넌트에 알려주는 함수
<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>

2. 사용하는 이유?

  • React 컴포넌트 간에 코드를 공유하기 위해
  • Cross-Cutting Concerns을 위해 (공통된 로직을 재사용하기 위해)

    • 컴포넌트는 React에서 코드의 재사용성을 위해 사용하는 주요 단위
    • 컴포넌트에서 캡슐화된 상태나 동작을 같은 상태를 가진 다른 컴포넌트와 공유하는 방법이 항상 명확하지 않다.

3. 예제를 통해 이해하자

  • 마우스 위치 추적 컴포넌트
class MouseTracker extends React.Component {
 state = { x: 0, y: 0 };
  

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

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
        <h1>Move the mouse around!</h1>
        <p>The current mouse position is ({this.state.x}, {this.state.y})</p>
      </div>
    );
  }
}
  • 마우스에 대한 로직은 분리 시키자

    • Mouse 컴포넌트는 마우스에 대한 행위를 캡슐화

      class Mouse extends React.Component {
      state = { x: 0, y: 0 };
      
      
      handleMouseMove = (event) => {
        this.setState({
          x: event.clientX,
          y: event.clientY
        });
      }
      
      render() {
        return (
          <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
            <p>The current mouse position is ({this.state.x}, {this.state.y})</p>
          </div>
        );
      }
      }
      
      class MouseTracker extends React.Component {
      render() {
        return (
          <>
            <h1>Move the mouse around!</h1>
            <Mouse />
          </>
        );
      }
      }
  • P 태그가 아닌 고양이 사진을 넣어주려면?

    • 오로직 Cat 컴포넌트만 사용할 뿐 재사용하려면 계속해서 WithSomething을 만들어줘야한다.

      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 MouseWithCat extends React.Component {
      state = { x: 0, y: 0 };
      
      
      handleMouseMove = (event) => {
        this.setState({
          x: event.clientX,
          y: event.clientY
        });
      }
      
      render() {
        return (
          <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
      
            {/*
              여기서 <p>를 <Cat>으로 바꿀 수 있습니다. ... 그러나 이 경우
              Mouse 컴포넌트를 사용할 때 마다 별도의 <MouseWithSomethingElse>
              컴포넌트를 만들어야 합니다, 그러므로 <MouseWithCat>는
              아직 정말로 재사용이 가능한게 아닙니다.
            */}
            <Cat mouse={this.state} />
          </div>
        );
      }
      }
      
      class MouseTracker extends React.Component {
      render() {
        return (
          <div>
            <h1>Move the mouse around!</h1>
            <MouseWithCat />
          </div>
        );
      }
      }
  • render Props를 통해 동적으로 렌더링을 할 수 있게 만들어주자

    • Mouse 역할 : 마우스 위치만 건네줄뿐!!! render는 props의 render값이 할 것!

      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 {
      state = { x: 0, y: 0 };
      
      
      handleMouseMove = (event) => {
        this.setState({
          x: event.clientX,
          y: event.clientY
        });
      }
      
      render() {
        return (
          <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
      
            {/*
              <Mouse>가 무엇을 렌더링하는지에 대해 명확히 코드로 표기하는 대신,
              `render` prop을 사용하여 무엇을 렌더링할지 동적으로 결정할 수 있습니다.
            */}
            {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>
        );
      }
      }

    주의사항

    • React.PureComponent에서 render props pattern을 사용할 땐 주의해주세요.
    • 얕은 prop 비교는 새로운 prop에 대해 항상 false를 반환합니다.
    • 이 경우 render마다 render prop으로 넘어온 값을 항상 새로 생성합니다.
    class Mouse extends React.PureComponent {
      // 위와 같은 구현체...
    }
    class MouseTracker extends React.Component {
      render() {
        return (
          <div>
            <h1>Move the mouse around!</h1>
    
            {/*
              이것은 좋지 않습니다! `render` prop이 가지고 있는 값은
              각각 다른 컴포넌트를 렌더링 할 것입니다.
            */}
            <Mouse render={mouse => (
              <Cat mouse={mouse} />
            )}/>
          </div>
        );
      }
    }
    • 이 예제에서 가 render 될때마다, 의 prop으로 넘어가는 함수가 계속 새로 생성됩니다. 따라서 React.PureComponent를 상속받은 컴포넌트 효과가 사라지게 됩니다.
    class MouseTracker extends React.Component {
      // `this.renderTheCat`를 항상 생성하는 매서드를 정의합니다.
      // 이것은 render를 사용할 때 마다 *같은* 함수를 참조합니다.
      renderTheCat(mouse) {
        return <Cat mouse={mouse} />;
      }
    
      render() {
        return (
          <div>
            <h1>Move the mouse around!</h1>
            <Mouse render={this.renderTheCat} />
          </div>
        );
      }
    }

Written by Junho You 배운것을 기록하자