useContext를 안 쓰다가 컴파운드로 컴포넌트를 만들면서 종종 사용하기 시작했다. context api는 친숙하지 않고 전체 렌더링 이슈 있어서 뭔가 찝찝했는데 이번 기회에 context를 최적화 해보장!
위와 같은 앱이 있다고 가정하자. 주요 컴포넌트는 두 개다.
dog name을 value로 받는 input
사용자가 지정한 만큼 cell을 보여주는 grid
두 컴포넌트는 Provider의 children이고
Provider는 state와 dispatch를 value로 준다.
가장 큰 렌더링 비용을 사용하는 Grid는 React.memo를 한다.
이제 강제 리렌더링을 해보자!
보는 것처럼 컴포넌트에 React.memo를 해줬음에도 모든 컴포넌트가 리렌더링 됐다.
그 이유는 provider에 props로 주는 value가 참조객체이기 때문에 컴포넌트는 매번 새로운 props로 인지하는 것이다.
위 value를 useMemo로 메모해준다.
그리고 다시 테스트 해보면
value의 변화가 없을 때, 모든 컴포넌트가 리렌더링이 되지 않는 것을 확인할 수 있다!
이제 Grid
컴포넌트에서 state 변화가 일어났다고 가정하자.
가령, cell을 클릭하여 cell에 적힌 숫자를 변경한다고 하자.
위에 보이는 것처럼 업데이트가 된 Cell은 하나인데 전체가 리렌더링 된 것을 볼 수 있다.
memo를 했음에도 이러한 현상이 일어난 이유는 생각해보면 간단하다.
useMemo의 의존성 배열에 있는 state 즉, dogName, grid 중 grid가 변경되었으니 새로운 value 를 만든 것이다.
<Cell>
컴포넌트의 부모인 <Grid>
컴포넌트는 실질적으로 state 변경과 상관이 없음에도 리렌더링이 된다.
실제 UI 업데이트가 일어나는 하나의 Cell만 리렌더링을 시켜야 한다!
가장 쉬운 방법은 context를 분리하는 것이다.
dispatch context를 따로 만들고
state, dispatch 를 필요한 곳에만 준다!
그리고 이전과 동일하게 cell을 클릭해서 리렌더링을 발생시키면
모든 컴포넌트가 렌더링 되지 않음을 알 수 있다!
context 분리 전
context 분리 후
아직 이상한 부분이 있다. 분명 하나의 cell을 클릭했는데 상관없는 몇몇 cell들이 리렌더링 되는 것이다.
아래 보이는 것처럼 memo는 되고 있지만 리렌더링 되는 cell들이 있다. (처음엔 버그인 줄 알았다.)
그 이유는 <Cell>
컴포먼트가 state를 다루는 데 있는데, <Cell>
컴포먼트는 <Cell>
는 직접 state를 받고 있는 일과 받은 state를 통해 cell을 렌더링 하는 일. 두 가지 일을 하고 있기 때문이다. (이 부분의 이해가 확실하지 않음)
이를 해결 하기 위해서는 로직을 분리하여 새로운 컴포너틑를 만들어주는 것이다. 그리고 props를 통해 전달하여 React.memo가 제대로 작동할 수 있게 해주는 것이다.
먼저 <Cell>
컴포넌트에서 provider에서 받은 state와 관련된 로직을 지우고 <CellImpl>
로 컴포넌트 이름을 변경한다.
아래와 같이 <Cell>
컴포넌트를 만들고 state를 받고 필요한 로직을 거쳐 cell을 <CellImpl>
에게 props로 준다.
그리고 다시 테스트를 해보면
<CellImpl>
는 렌더링 되지 않았고
<Cell>
는 렌더링되었다.
즉, react element를 반환하는 <CellImpl>
의 불필요한 렌더링을 막으면서 최적화한 것이다. (이거 짱이다...)
참고