2019-08-16 TIL

2019-08-16

React Component 종류에 따른 rerender 특성 ?

TL;DR

  1. setState하는 값이 primitive라 shallow equal이다 -> local state의 변경이 없는 것으로 판단, rerender하지 않는다.

  2. setState하는 값이 primitive가 아니라서 shallow non-equal이다 (자식에게 내리는 prop 값은 primitive인 경우) -> local state가 변했으니 rerender한다. 근데 새 state에서 값을 props로 받는 자식들은 어떻게 행동할까?

  • PureComponent : prop을 shallow compare한다. (다를때만 rerender)
  • Component : 무조건 rerender한다.
  • Functional Component : 무조건 rerender한다. (하지만 React.memo로 감싸주면 props에 대해 shallow compare한다.)

React.PureComponent

React.PureComponent is similar to React.Component. The difference between them is that React.Component doesn’t implement shouldComponentUpdate(), but React.PureComponent implements it with a shallow prop and state comparison.

React.PureComponentReact.Component와 유사하다. 둘의 차이점은 React.ComponentshouldComponentUpdate()를 수행하지 않으며 React.PureComponent는 수행하는데 prop과 state를 shallow compare한다는 점이다.

If your React component’s render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost in some cases.

만약 여러분의 리액트 컴포넌트의 render()함수가 같은 props와 state에 대해 같은 결과를 렌더한다면, 어떤 케이스에서는 성능 향상을 위해 React.PureComponent를 사용할 수 있다.

Note

React.PureComponent’s shouldComponentUpdate() only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only extend PureComponent when you expect to have simple props and state, or use forceUpdate() when you know deep data structures have changed. Or, consider using immutable objects to facilitate fast comparisons of nested data.

Furthermore, React.PureComponent’s shouldComponentUpdate() skips prop updates for the whole component subtree. Make sure all the children components are also “pure”.

React.PureComponentshouldComponentUpdate()는 오브젝트를 shallow compare할 뿐이다. 만약 오브젝트의 데이터 구조가 복잡하다면 더 깊은 차이에 대해 거짓음성 판정(문제가 있는데 없다고 판단)을 할 수 있다. 오직 단순한 props와 state를 가지는 경우 혹은 여러분이 직접 깊은 데이터 구조의 변화를 알아내 forceUpdate()를 사용하는 경우에만 PureComponent를 extend 하길 바란다. 또는, nested된 데이터의 빠른 비교를 위해 immutable objects를 사용하는걸 고려해보길.

게다가, React.PureComponentshouldComponentUpdate()는 전체 컴포넌트 서브트리에 대한 prop 업데이트를 생략한다. 모든 자식 컴포넌트도 반드시 “pure”하게 만들도록. (무슨얘긴지 잘 모르겠다. PureComponent 밑에 자식 컴포넌트를 만들고, 최상위에서 props을 변경해 내렸을 때 PureComponent 및 자식 컴포넌트도 모두 업데이트 되었다.)


간단한 실험

function App() {
  const [state, setState] = useState("PURE");
  
  useEffect(() => {
    const id = setInterval(() => {
      setState({str: "PURE!!!"});
      // setState("PURE!!!"); // 이렇게 주면 어떤 컴포넌트든 rerender하지 않음. setState선에서 state변화가 없다는걸 알아차리는 것 같다.
    }, 2000);
    return () => clearInterval(id);
  }, []);
  
  console.log("ancestor rendered!");
  return (
    <div className="App">
      <Pure title={state.str} />
    </div>
  );
}
// 아래 세가지 Pure라는 이름의 컴포넌트(Pure한건 하나뿐이지만 편의상 같은이름으로 함)를 이용해 실험해봄
class Pure extends PureComponent {
  render() {
    const {title} = this.props;
    console.log("PureComponent Rendered");
    return (
      <div>
        <h2>{title}</h2>
        <NestedChild content={`i'm a child`} subcontent={title} />
      </div>
    );
  }
}
========
class Pure extends Component {
  render() {
    const {title} = this.props;
    console.log("Component Rendered");
    return (
      <div>
        <h2>{title}</h2>
        <NestedChild content={`i'm a child`} subcontent={title} />
      </div>
    );
  }
}
========
const Pure = ({ title }) => {
  console.log("PureComponent Rendered");
  return (
    <div>
      <h2>{title}</h2>
      <NestedChild content={`i'm a child`} subcontent={title} />
    </div>
  );
};

// 이건 자식 컴포넌트 props를 update하지 않는다길래 넣어본 놈
class NestedChild extends Component {
  render() {
    console.log("NestedChild rendered");
    return (
      <h3>
        {this.props.content} {this.props.subcontent}
      </h3>
    );
  }
}

export default Pure;
  • title={state} / setState({str: "PURE!!!"})로 내리면 매번 새 객체니까 PureComponent든 뭐든 재렌더가 일어남. 자식 컴포넌트들 입장에서도 매번 새로운 props가 내려오니 항상 rerender.

  • title={state.str} / setState({str: "PURE!!!"})로 내리면 PureComponent는 재렌더하지 않음 (Component와 함수형 컴포넌트는 재렌더 일어남) 하지만 state를 가진 최상위 컴포넌트는 재렌더가 일어남. 즉, 부모에 의해 자식의 rerender가 일어나는 경우 PureComponent의 경우에만 props가 같은 값인지 shallow compare를 하는 것. 그런데 함수형 컴포넌트의 경우 React.memo()로 감싸주면 props의 shallow compare를 한다. 곧, memoized 함수형 컴포넌트 = PureComponent 라고 할 수 있다.

  • title={state} / setState("PURE!!!")면 모든 종류의 컴포넌트가 재렌더하지 않음. 아마 setState() 선에서 값이 같다는걸 인지하는 것 같다. 그래서 이 경우에만 최상위 컴포넌트조차 재렌더되지 않는 것!!


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