-
[Redux] Hooks - useSelector, useDispatchFront-End(Web)/React - 라이브러리들 2021. 2. 17. 02:55반응형
😅 서론
react-redux의 connect() 함수와, mapStateToProps, mapDispatchToProps 를 열심히 공부했는데...
Dev Ed님 유튜브 영상을 복습하면서, useSelector, useDispatch 라는 간편한 Hooks들을 지원해주는 것을 알았다.
(하마터면 connect() 함수로 프로젝트 진행할뻔)
이를 계기로, react-redux에서 지원하는 Hooks 들의 종류와 기능에 대해 한번 공부해보았다.
💜 React-Redux의 Hooks 적용
React-Redux 7버전 이후로, React 에 대한 Redux 관련 Hooks API들을 제공하게 되었다.
이에 따라, 이전의 connect() HOC(Higher-Order Component) 작업이 불필요해진 것이다. (mapStateToProps 등)
즉, Container 컴포넌트 Wrapping 할 필요가 없어졌으며, 컴포넌트 자체에서 스토어의 참조와 디스패치가 가능하단 것이다.
- 필수: Provider Wrapping
const store = createStore ( rootReducer ) ReactDOM.render ( <Provider store={store}> <App /> </Provider> , document.getElementById('root') )
기존 React-Redux와 마찬가지로 최상위를 Provider로 랩핑해준다. 그 내부의 컴포넌트들은 Hooks를 통해 스토어에 접근할 수 있다.
💜 useSelector()
스토어에 저장된 상태를 참조하는 Hooks 이다. (mapStateToProps 역할과 유사)
import React from 'react' import { useSelector } from 'react-redux' export const CounterComponent = () => { const counter = useSelector(state => state.counter) return <div>{counter}</div> }
우선, useSelector() Hooks를 react-redux 에서 import 해준다.
그리고, 필요한 state를 변수에 담는다. 콜백함수를 인자로 받으며, state(스토어)에서 필요한 필드를 가져오면 된다.
// Bad const {user, isLoggedIn} = useSelector((state) => state.user); // Good const isLoggedIn = useSelector((state) => state.user.isLoggedIn);
위 경우가 Bad인 이유는, 만약 현재 컴포넌트에서 user state가 불필요한 경우이다.
스토어에서 state가 수정되면 이를 참고하는 모든 컴포넌트가 리렌더되기 때문에, 최적화를 위해 아래처럼 필요한 state만 가져오는 것이다.
- useSelector() 최적화
useSelector() 는 스토어의 상태를 참고하는 Hooks 라고 했다.
이 상태가 변경되면, 연계된 컴포넌트들이 리렌더되므로 이를 최적화하는 3단계를 소개해보겠다.
1) 독립 선언(useSelector 여러 번 사용)
// Bad const { count, prevCount } = useSelector((state: RootState) => ({ count : state.countReducer.count, prevCount: state.countReducer.prevCount, })); // Good const count= useSelector((state: RootState) => state.countReducer.count); const prevCount= useSelector((state: RootState) => state.countReducer.prevCount);
Bad 경우는, 두 state를 하나의 객체처럼 선언한 경우이다. 물론 메모리적인 이점도 있겠지만.
state 객체 하나만 수정해도 두 필드가 연결된 컴포넌트들이 모두 리렌더되기 때문에, Good 경우처럼 셀렉터를 여러 번 쓰는 것이 유리하다.
2) equalityFn 파라미터
useSelector는 두 번째 인자로, equalityFn 이라는 함수를 받는다.(선택옵션) 문법은 다음과 같다.
equalityFn?: (prev, next) => boolean
prev와 next는 전후의 state 이며, 이를 비교한 boolean이 true면 리렌더를 생략, false면 리렌더를 진행한다.
3) shallowEqual 함수
useSelector의 equalityFn에 대입할 수 있는 함수이다. 전후비교의 개념은 같지만, 최상위 값만 비교한다.
state = { selectedId: 0, username: 'taeng', friends: [ {id: 0, name: 'hyemi'}, {id: 1, name: 'taejin'}, ], hobby: { indoor: 'game', outdoor: 'surfing', } }
이러한 state를 예로 들면, 최상위 요소인 selectedId, username 변경여부만 확인할 것이다. (friends 배열, hobby 객체 무시)
💜 useDispatch()
디스패치 함수를 실행하는 Hooks 이다. (mapDispatchToProps 역할과 유사)
import React from 'react' import { useDispatch } from 'react-redux' import { openLoginModalScreen } from "../reducers/global"; export const CounterComponent = ({ value }) => { const dispatch = useDispatch() return ( <div> <span>{value}</span> <button onClick={() => dispatch(openLoginModalScreen)}> Increment counter </button> </div> ) }
마찬가지로, useDispatch() Hooks를 react-redux 에서 import 해준다. 그리고, 변수에 처리없이 담는다. (Hooks 자체가 디스패치)
디스패치 함수이므로, 내부 인자는 action 객체를 받는다. 그렇기 때문에, action 객체나 생성함수를 별도로 만들어주면 유용하다.
* 규모가 작은 프로젝트는 각 Reducer 파일에 action 함수를 포함, 큰 프로젝트는 Action 디렉토리에 별도로 action 함수들만 모은 파일로 관리
- useDispatch() 와 useCallback()
공식문서는, 자식 컴포넌트에 디스패치를 props로 하달할 때, useCallback() Hooks로 메모이즈된 콜백형태를 권장한다.
import React, { useCallback } from 'react' import { useDispatch } from 'react-redux' export const CounterComponent = ({ value }) => { const dispatch = useDispatch() const incrementCounter = useCallback( () => dispatch({ type: 'increment-counter' }), [dispatch] ) return ( <div> <span>{value}</span> <MyIncrementButton onIncrement={incrementCounter} /> </div> ) } export const MyIncrementButton = React.memo(({ onIncrement }) => ( <button onClick={onIncrement}>Increment counter</button> ))
- JSX 인라인 코딩에서 분리하여 가독성을 높인다.
- 자식 컴포넌트의 불필요한 리렌더를 방지할 수 있을 것 같다. (디스패치 변동이 없다면 리렌더 발생하지 않음)
💜 useSelector, useDispatch 활용 예시
Dev Ed의 클론 강의에서, App.js 에 적용된 useSelector와 useDispatch의 React 적용 예시이다.
import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { increment, decrement } from './Actions/Action'; function App() { const counter = useSelector(state => state.counter.count) const isLogged = useSelector(state => state.logged.isLogged) const dispatch = useDispatch(); return ( <div className="App"> <h1>Counter: {counter}</h1> {isLogged && <h3>Valuable Information I shouldn't see.</h3>} <button onClick={() => dispatch(increment(1))}>+</button> <button onClick={() => dispatch(decrement(1))}>-</button> </div> ); } export default App;
* combineReducers
reducer 함수들을 기능별로 분리했고, 이들이 스토어에 참조되기 위해 하나의 리듀서로 묶여야 한다. 이 때 사용되는 메서드이다.
스토어는, 모든 리듀서가 묶인 RootReducers 만 참조하면 된다.
// Reducer.js import { combineReducers } from 'redux'; import counterReducer from './counter'; import loggedReducer from './logged'; const RootReducers = combineReducers({ counter: counterReducer, logged: loggedReducer, }) export default RootReducers; // Store.js import { createStore } from 'redux'; import RootReducers from './Reducers/Reducer'; const store = createStore( RootReducers, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), ); export default store;
하마터면, 컨테이너 컴포넌트들 만드느라 이골이 날 뻔 했다!!! 😅😅😅
Redux Hooks 들이라는 유용한 기능으로 좀 더 편리하고 트렌디한 투두 리스트를 만들어봐야 겠다.
리덕스의 장점중 하나가 바로 생태계이고, 이를 기반으로 많은 기능들을 지원해준다. (이를 미들웨어라고 칭한다.)
Redux 스택에 종종 같이 등장하는, Redux-thunk, Redux-saga 와 같은 미들웨어들에 대해서 내일 공부해보려고 한다!!
[출처]
- Redux 공식문서: react-redux.js.org/api/hooks
- HotHandCoding 님의 블로그: darrengwon.tistory.com/559
- 황은지 님의 블로그: velog.io/@hwang-eunji/%EB%A6%AC%EB%8D%95%EC%8A%A4%EB%A6%AC%EC%95%A1%ED%8A%B8-Redux-with-React-8-useSelector-useDispatch-hooks
반응형'Front-End(Web) > React - 라이브러리들' 카테고리의 다른 글
[Redux] Redux-thunk (0) 2021.02.18 [Redux] 미들웨어(Middleware) (0) 2021.02.18 [Redux] React-Redux (0) 2021.02.16 [Redux] React + Redux App 만들기(생활코딩) (0) 2021.02.16 [Redux] 순수 Redux App 만들기(생활코딩) (0) 2021.02.15