12. React-Redux Hooks - useSelector와 useDispatch
11편의 connect()가 여전히 유효하다고 했지만, 실무 코드 대부분은 이제 Hooks 방식을 씁니다. 함수형 컴포넌트와 자연스럽게 어울리고, mapStateToProps/mapDispatchToProps를 별도로 정의할 필요가 없어 코드가 짧아집니다.
학습 목표
useSelector로 상태의 일부를 구독하고,useDispatch로 Action을 dispatch할 수 있다.useSelector가 리렌더 여부를 판단하는 방식(얕은 비교)을 설명할 수 있다.useSelector에서 매번 새 객체를 반환해 생기는 불필요한 리렌더 문제를 진단하고 고칠 수 있다.
useSelector: 상태의 일부를 구독한다
useSelector는 selector 함수를 받아, 그 함수가 반환하는 값을 컴포넌트에 연결합니다.
| |
useSelector는 내부적으로 08편의 참조 비교(===)를 사용해, selector가 반환하는 값이 이전 렌더링과 다를 때만 컴포넌트를 리렌더링합니다. state.counter.count처럼 원시값(숫자·문자열·불리언)을 반환하면 값 자체가 비교되므로 예측 가능하게 동작합니다.
왜 하필 참조 비교(===)를 기본값으로 골랐을까요? 11편의 connect()가 쓰는 얕은 비교(객체의 각 속성을 하나씩 비교)는 객체의 속성 개수만큼 비교 연산이 필요합니다. 반면 참조 비교는 객체 크기와 무관하게 항상 한 번의 연산으로 끝나 성능이 예측 가능하고, useSelector를 어디서 몇 번 호출하든 비용이 일정합니다. 이 선택은 동시에 개발자에게 “selector는 되도록 원시값이나 안정된 참조를 반환하도록 작게 쪼개라"는 방향을 자연스럽게 유도합니다 — 객체를 반환하고 싶은 유혹이 들 때마다, 뒤에서 볼 “매번 새 객체 반환” 문제를 먼저 마주치게 되기 때문입니다.
useDispatch: dispatch 함수를 가져온다
useDispatch는 Store의 dispatch 함수를 그대로 반환합니다.
| |
두 Hook을 조합하면 connect()보다 훨씬 짧은 코드로 같은 결과를 얻습니다.
| |
Hooks 버전은 mapStateToProps/mapDispatchToProps를 따로 정의할 필요가 없고, 컴포넌트 안에서 상태와 액션을 바로 사용합니다. 다만 이 방식은 컴포넌트가 Redux를 직접 알게 되므로(11편에서 강조한 “프레젠테이션 컴포넌트는 Redux를 몰라야 한다"는 분리가 약해집니다), 재사용성이 중요한 컴포넌트라면 여전히 connect()나 컨테이너/프레젠테이션 분리를 고려할 수 있습니다.
흔한 함정: useSelector에서 매번 새 객체를 반환하기
useSelector가 매번 다른 참조를 가진 새 객체를 반환하면, 값이 실제로 바뀌지 않았는데도 매 렌더링마다 리렌더가 발생합니다.
| |
state.cart가 전혀 바뀌지 않아도, selector 함수 자체가 호출될 때마다 새 객체 리터럴 { total, itemCount }를 만들기 때문에 useSelector의 참조 비교는 항상 “다르다"고 판단합니다. 그 결과 이 컴포넌트는 Store의 다른 부분이 바뀔 때마다 불필요하게 리렌더됩니다.
| |
shallowEqual은 반환된 객체의 속성값들을 하나씩 비교하므로, 참조는 달라도 내용이 같으면 리렌더를 건너뜁니다. 원시값 여러 개를 각각 구독하는 방식(개선 1)이 더 간단하고 명시적이라 우선 권장되고, 객체 형태로 묶어야 할 이유가 있을 때만 shallowEqual을 씁니다. 이 주제는 13편(컴포넌트 최적화)에서 리렌더 성능 관점으로 더 깊이 다룹니다.
Hooks의 성능 특성: connect와의 차이
connect()는 여러 mapStateToProps 호출을 배치로 처리해 최적화하지만, useSelector는 각 Hook 호출이 독립적으로 구독을 관리합니다. 즉 한 컴포넌트에서 useSelector를 여러 번 호출하면, 각 호출이 개별적으로 리렌더 여부를 판단합니다.
| |
이 특성 때문에, 한 컴포넌트가 여러 개의 무관한 상태 조각을 구독하면 그중 하나만 바뀌어도 전체가 리렌더됩니다. 컴포넌트를 상태 단위로 잘게 쪼개는 것이 이 문제를 완화하는 방법이며, 13편에서 구체적으로 다룹니다.
실무 체크리스트
useSelector가 반환하는 값이 원시값인가, 아니면 매번 새로 만들어지는 객체/배열인가?- 객체를 반환해야 한다면
shallowEqual을 함께 쓰거나, 원시값 여러 개로 나눠 구독했는가? useDispatch()로 얻은dispatch를 매 렌더링마다 새로 만든 함수로 감싸서 자식 컴포넌트의 리렌더를 유발하고 있지 않은가?
연습 과제
기초(★☆☆)
Counter를connect()버전에서useSelector/useDispatch버전으로 직접 리팩터링해보세요.
중급(★★☆)
OrderSummaryBad를 실제로 렌더링하고 React DevTools Profiler로 불필요한 리렌더가 발생하는지 확인한 뒤,shallowEqual버전으로 고쳐보세요.
고급(★★★)
- 하나의 컴포넌트에서
useSelector를 5번 이상 호출하는 대신, 컴포넌트를 상태 단위로 쪼개 각각 하나씩만 구독하도록 리팩터링해보세요.
요약
useSelector는 참조 비교로 리렌더 여부를 판단하고,useDispatch는 dispatch 함수를 그대로 반환한다.- selector가 매번 새 객체를 반환하면 값이 안 바뀌어도 리렌더가 발생하므로, 원시값으로 나누거나
shallowEqual을 쓴다. useSelector호출은 각각 독립적으로 구독을 관리하므로, 컴포넌트를 상태 단위로 잘게 나누는 것이 성능에 유리하다.
참고 문헌 및 출처(추천)
- React-Redux 공식 문서, “useSelector” API 레퍼런스
- React-Redux 공식 문서, “useDispatch” API 레퍼런스
- React-Redux 공식 문서, “Hooks FAQ: Why should I not create functions in mapDispatchToProps or useSelector?”
![Featured image of post [Redux] 12. React-Redux Hooks - useSelector와 useDispatch](/post/redux/react-redux-hooks/wordcloud_hu_794cda80e41d4064.webp)
![[Redux] 10. Redux를 사용하는 이유와 적절한 사용 시기](/post/redux/when-to-use-redux/wordcloud_hu_5206c8759804db52.webp)
![[Redux] 11. React-Redux 시작하기 - Provider와 connect](/post/redux/react-redux-basics/wordcloud_hu_ff09d62dce5f2caf.webp)
![[Redux] 12. React-Redux Hooks - useSelector와 useDispatch](/post/redux/react-redux-hooks/wordcloud_hu_71b8875b416f1345.webp)
![[Redux] 13. 컴포넌트 최적화 - 리렌더링 제어](/post/redux/component-optimization/wordcloud_hu_6a65d0a31ce36910.webp)
![[Redux] 14. 데이터 선택자 - Selector 패턴](/post/redux/selector-patterns/wordcloud_hu_e73d33d3c19de96.webp)
![[Redux] 08. 불변성의 중요성 - Immutability in Redux](/post/redux/immutability-in-redux/wordcloud_hu_3a5feb2a8eb157f4.webp)
![[Redux] 00. 커리큘럼 - 학습 로드맵](/post/redux/getting-started-redux/wordcloud_hu_2ed9b2e29c50fe99.webp)
![[Tutorial] Learn Prompting - 프롬프트 엔지니어링 무료 가이드 정리](/post/2022-12-30-learn-prompting/wordcloud_hu_6a9d105de4834753.webp)
![[Hardware] LattePanda Alpha에 Ubuntu 16.04 LTS 설치 가이드](/post/2018-12-06-install-ubuntu-16.04-on-lattepanda/wordcloud_hu_fc536f8de2cbd4bf.webp)