리덕스에서 useSelector hook을 이용해 state 값을 받아올 때, 컴포넌트 최적화를 위해 사소하지만 유의해야 할 점이 있다.
다음은 여러 도시의 날씨 정보를 담고 있는 리스트 컴포넌트이다.
각 항목을 클릭하면 해당 도시의 정보를 메인 컴포넌트에 보여준다. 선택된 아이템과 선택 해제된 아이템 두 개를 제외한 나머지에는 어떠한 변화도 일어나지 않지만 불필요한 리렌더링이 함께 발생하고 있다.
state 구조는 이렇다.
- state
- weather
- cities (사용자가 추가한 도시 목록)
- currentCity (현재 선택된 도시)
- loading (로딩 여부)
- error (에러 여부)
위 코드를 보면 도시를 선택할 때 바뀌는 상태 값은 state.weather.currentCity
이고 컴포넌트에서 불러온 상태 값은 state.weather.cities
인데도 모두 리렌더링이 발생하고 있다.
useEffect로 아이템 컴포넌트가 가지고 있는 props와 state의 변화를 추적해보려 하였으나 콘솔에는 리렌더링 여부만 표시될 뿐이다.
원인은 아주 사소한 것이었다.
위 코드에서 useSelector로 받아온 state(weather)를 ES6의 구조 분해 문법으로 다시 한 번 추출(cities)하여 쓰고 있는데, 이때 cities state가 매번 새롭게 만들어지게 된다. 그래서 이 state의 변화를 추적하지 못하고 계속해서 리렌더링을 할 수밖에 없던 것이다.
아마 이때 리렌더링의 원인은 비구조화 할당 자체가 문제인 게 아니었던 것 같다.
state 값이 새롭게 만들어져서...가 아니라, 애초에 useSelector()를 통해서 필요한 state 값만이 아닌 전체 state 값을 가져왔기 때문에, 실제 해당 컴포넌트에서 쓰이지 않는 다른 상태가 변하더라도 그것까지 인식이 되어버려서 그랬던 것이다.
위 사진에서 전자는 state.weather
를, 후자는 state.weather.cities
를 가져오고 있다.
구조 분해 할당을 통해 cities
만 가져온 것으로 착각할 수 있지만, 실은 state.weather
자체를 가져온 후 그것을 한 껍데기 분해했을 뿐이다.
이때 다른 도시를 선택하면 state.weather.currentCity
항목이 바뀌게 되므로 전자의 컴포넌트가 리렌더링되는 것은 사실 당연한 현상이었다.
코드를 고친 후에는 이렇게 불필요한 컴포넌트 리렌더링이 일어나지 않는 모습을 볼 수 있었다.
문제의 원인을 잘못 짚고 넘어갔다가 1년이 넘은 지금 우연히 이 포스팅을 보게 되었다... 지금이라도 제대로 된 원인을 파악해서 다행이다.