본문 바로가기
프로그래밍/react

[react] Render Props

by 혀끄니 2023. 5. 19.
728x90

Render Props란?

  • "render prop"란, React 컴포넌트 간에 코드를 공유하기 위해 함수 porps를 이용하는 간단한 테그닉이다.
  • render props를 사용하는 라이브러리는 React Router, Downshift, Formik가 있다.

횡단 관심사(Cross-Cutting Concerns)를 위한 render props 사용법

  • 횡단 관심사 : 핵심적인 기능이 아닌 중간중간 삽입되어야 할 기능들을 횡산관심사라고 한다.
  • 컴포넌트는 React에서 코드의 재사용성을 위해 사용하는 주요 단위
  • 하지만 컴포넌트에서 캡슐화된 상태나 동작을 같은 상태를 가진 다른 컴포넌트와 공유하는 방법이 항상 명확하지 않다.
//아래 컴포넌트는 웹어플리케이션에서 마우스 위치를 추적하는 로직
class MouseTracker 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: '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 {
  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: '100vh' }} onMouseMove={this.handleMouseMove}>

        {/* ...하지만 <p>가 아닌 다른것을 렌더링하려면 어떻게 해야 할까요? */}
        <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 />
      </>
    );
  }
}
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 {
  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: '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>
    );
  }
}
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: '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>
        //render함수에 prop으로 전당해줌으로써, <Mouse> 컴포넌트는 동적으로 트래깅 기능을 가진
        //컴포넌트들을 렌더링 할 수 있다.
        //"render props pattern"으로 불리는 이유로 꼭 prop name으로 render를 사용할 필요없음
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

render 이외의 Props 사용법

  • "render props pattern"으로 불리는 이유로 꼭 prop name으로 render를 사용할 필요 없다
  • 위 예시에서는 render를 사용했지만, 우리는 childern prop을 더 쉽게 사용할 수 있다.
<Mouse children={mouse => (
  <p>The mouse position is {mouse.x}, {mouse.y}</p>
)}/>
  • 실제로 JSX element의 "어트리뷰트"목록에 하위 어트리뷰트 이름(ex: render)을 지정할 필요없이 element안에 직접 꽂아 넣을 수 있다.
<Mouse>
  {mouse => (
    <p>The mouse position is {mouse.x}, {mouse.y}</p>
  )}
</Mouse>
  • 이 테크닉은 react-motion API에서 실제로 사용된 것을 볼 수 있다.
  • 하지만 이 테크닉은 자주 사용되지 않기 때문에 API를 디자인 할때 children은 함수 타입을 가지도록 propTypes를 지정하는 것이 좋다.
Mouse.propTypes = {
  children: PropTypes.func.isRequired
};

주의사항

React.PureComponernt에서 render props pattern을 사용할 땐 주의 할 점

React.PureComponent란?

  • React.PureComponent는 React.Component와 비슷하다.
  • React.Component는 shouldComponentUpdate()를 구현하지 않지만, React.PureComponent는 props와 state를 이용한 얕은 비교를 구현한다는 차이점만이 존재
/*
<MouseTracker>가 render될때마다, <Mouse render>의 prop으로 넘어가는 함수가 계속 새로 생성
따라서 React.PureComponent를 상속받은 <Mouse>컴포넌트 효과가 사라지게 된다.
*/
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>
    );
  }
}

/*
이 문제를 해결하기 위해서 다음과 같이 인스턴스 메서드를 사용해서 prop을 정의 
prop을 정적으로 정의 할수 없는 경우에는 <Mouse> 컴포넌트는 React.Componenr를 상속
*/
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>
    );
  }
}
728x90

'프로그래밍 > react' 카테고리의 다른 글

[react] Form  (0) 2023.05.19
[react] 리스트와 Key  (0) 2023.05.18
[react] 조건부 렌더링  (0) 2023.05.17
[react] 이벤트 처리하기  (0) 2023.05.17
[react] State  (0) 2023.05.16