Redux store의 전역상태를 useSelector Hooks로 가져와 사용할 수 있다. useSelector는 컴포넌트가 렌더링되거나 action이 dispatch 될 때마다 selector 함수를 실행하고 반환값을 비교한다.
selector 함수는 store의 state를 인자로 받고 useSelector Hooks는 selector 함수가 반환한 값을 반환한다.
Fortunately, useSelector automatically subscribes to the Redux store for us! That way, any time an action is dispatched, it will call its selector function again right away. If the value returned by the selector changes from the last time it ran, useSelector will force our component to re-render with the new data. All we have to do is call useSelector() once in our component, and it does the rest of the work for us.
However, there's a very important thing to remember here:
CAUTION
useSelector compares its results using strict === reference comparisons, so the component will re-render any time the selector result is a new reference! This means that if you create a new reference in your selector and return it, your component could re-render every time an action has been dispatched, even if the data really isn't different.
Redux 공식문서
state => state
이면, store 전체를 비교하는 것이므로 현재 컴포넌트와 상관이 없는 다른 상태가 변경되어도 상태가 업데이트된 것으로 간주하여 컴포넌트가 리렌더링된다.const { isLoggedIn } = useAppSelector((state) => state);
const { isOpenChatItemModal, isOpenScrapbookModal, isOpenScrapModal, isOpenWithdrawalModal } = useAppSelector(
(state) => state
);
const { number, diff } = useSelector(
state => ({
number: state.counter.number,
diff: state.counter.diff
})
);
const isOpenChatItemModal = useAppSelector((state) => state.isOpenChatItemModal);
const isOpenScrapbookModal = useAppSelector((state) => state.isOpenScrapbookModal);
const isOpenScrapModal = useAppSelector((state) => state.isOpenScrapModal);
const isOpenWithdrawalModal = useAppSelector((state) => state.isOpenWithdrawalModal);
독립적으로 선언된 상태들은 서로의 영향을 받지 않아 낭비 렌더링을 막을 수 있다.
공식 문서에서도 추천하고 있는 방법이다.
We can call useSelector multiple times within one component. In fact, this is actually a good idea - each call to useSelector should always return the smallest amount of state possible.
const { number, diff } = useSelector(
state => ({
number: state.counter.number,
diff: state.counter.diff
}),
shallowEqual
);
const a = { foo: "bar" }
const b = { foo: "bar" }
console.log( a === b ) // will log false
console.log( shallowEquals(a, b)) // will log true
shallowEqual은 객체의 가장 바깥을 비교한다.
첫 번째 코드는 매번 새로운 객체를 반환하기 때문에 상태 값이 바뀌었는지와 무관하게 매번 새로운 참조를 가지므로 리렌더링이 발생한다. 이 때 shallowEqual을 사용하여 값들을 비교하여 값의 변화가 없을 때 리렌더링을 방지한다.
const state = {
a: {
first: 5
},
b: 10
}
const selectA = state => state.a
const selectB = state => state.b
const selectA1 = createSelector([selectA], a => a.first)
const selectResult = createSelector([selectA1, selectB], (a1, b) => {
console.log('Output selector running')
return a1 + b
})
const result = selectResult(state)
// Log: "Output selector running"
console.log(result)
// 15
const secondResult = selectResult(state)
// No log output
console.log(secondResult)
// 15
createSelector 함수는 메모이제이션을 활용한다.
input selector와 output selector를 인자로 받아 새로운 select 함수를 반환한다.
result
와 secondResult
를 보면 selectA1
과 selectB
의 결과값이 그대로 5
와 10
이기 때문에 secondResult
의 output selector가 실행되지 않고 memoized result가 반환 된 것을 볼 수 있다.
memoization을 사용하는 것이기 때문에 연산이 복잡할 때와 같이 효율을 고려해서 사용하는 것이 좋겠다.
useSelector 사용시 성능 문제점 문의드립니다.
useSelector의 적절한 사용법에 대해 궁금합니다.
Redux의 useSelector 최적화 하기
How Exactly useselector works?
8. useSelector 최적화