며칠 전 궁금해약 프로젝트를 진행하면서 팀원분과 고민했던 일이 있었다.
하나의 state에 여러 종류의 버튼 상태를 관리하고 있었는데 이들을 각각의 버튼을 관리하는 state로 나누고 자식 컴포넌트에 props로 내려주는 방법으로 변경하기로 했다.
확실히 이 방법으로 state를 관리하기는 편해졌지만 자식 컴포넌트에서 state를 변경하게 되면 부모 컴포넌트의 state가 변경되었기 때문에 리렌더링이 일어나게 되고 이에 따라 해당 state의 변경과는 상관이 없는 다른 자식 컴포넌트들까지 모두 리렌더링이 일어나게 되었다.
내가 구현한 페이지에도 같은 이슈를 겪고 있는 페이지가 있었다.
알림을 세팅하는 페이지인데 부모 컴포넌트에 알림 세팅을 위한 여러 state를 두고 자식인 TimeForm과 RemindForm에서 state를 변경하고 있다.
그래서 TimeForm에서 시간을 바꾸면 알림 시간과 관련이 없는 RemindForm까지 리렌더링이 일어나고 있었다.
우선 컴포넌트가 리렌더링되는 경우는 다음과 같다.
여기서 RemindForm은 자신의 state가 변경되지 않고 부모에서 전달받은 props가 변경되지도 않았지만 부모 컴포넌트가 리렌더링되어 같이 리렌더링되었다.
이럴 때 사용하면 좋은 것이 React.memo이다.
React.memo로 컴포넌트를 래핑하면 React.memo는 컴포넌트를 렌더링하고 그 결과를 메모이징(Memoizing)한다. 그리고 다음 렌더링 시 동일한 props를 사용하고 있다면 컴포넌트를 새로 렌더링하지 않고 메모이징한 컴포넌트를 재사용한다.
자식 1인 TimeForm에서 사용자의 입력에 따라 리렌더링이 일어나자 부모와 자식 2까지 모두 리렌더링이 일어나는 모습을 볼 수 있다.
이전과 달리 변경이 일어난 TimeForm과 부모 컴포넌트만 리렌더링이 일어나는 것을 볼 수 있다.
자식 1이 두 번 리렌더링되는 이유는 자식 1에 있는 state의 변경이 일어나고 그에 따라 부모 컴포넌트의 state도 변경이 일어나고 있기 때문이다.
그래도 props의 변경이 없는 RemindForm이 리렌더링되지 않는 것을 확인할 수 있다.
React 공식문서 - React.memo에 따르면 이 메서드는 오직 성능 최적화만을 위하여 사용해야 하며 렌더링을 "방지"할 목적으로 사용해서는 안된다고 한다.
버그를 만들 수 있다고 하는데 이에 대한 자세한 설명은 없어 따로 찾아본 결과 그에 대한 설명은 React 공식문서 - useMemo에서 찾을 수 있었다.
You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance.
React는 이전에 저장된 값을 잊어버리고 다음 렌더링 때 다시 계산하여 메모리를 확보하도록 할 수 있기 때문에 useMemo나 useCallback, React.memo와 같이 메모이제이션을 사용하는 것들에 대해서 무조건 계산해둔 값이 메모리에 있을 것이라고 신뢰하면 안된다.
따라서 어떤 경우에도 렌더링이 일어나지 않도록 하기 위해 React.memo를 사용하면 안된다.
React 공식문서 - React.memo
React.memo() 현명하게 사용하기
stackoverflow - What makes React.memo unreliable for preventing renders?
React 공식문서 - useMemo