useSelector, useDispatch, useCreate를 사용해서 리덕스 상태를 조회하고 사용해보자.
useSelector Hook을 사용하면 connect함수를 사용하지 않고도 리덕스의 상태를 조회할 수 있다.
개념적으로 mapStateToProps와 비슷하다.
connect 함수는 해당 컨테이너 컴포넌트의 부모 컴포넌트가 리렌더링될 때 해당 컨테이너 컴포넌트의 props가 바뀌지 않았다면 리렌더링이 자동으로 방지되어 성능이 최적화된다.
이에비해, useSelector를 사용하여 리덕스 상태를 조회했을 때는 성능 최적화가 이루어지지 않기때문에 React.memo를 컨테이너 컴포넌트에 사용해줘야한다.
export default React.memo(Container);
import { useSelector } from 'react-redux';
const ExampleContainer = () => {
const target = useSelector(state => state.target);
return <Example target={target} />;
};
useDispatch Hook은 컴포넌트 내부에서 스토어의 내장함수 dispatch를 사용할 수 있게 도와준다.
import { useDispatch } from 'react-redux';
import { increase } from '../modules/counter';
const ExampleContainer = () => {
const dispatch = useDispatch();
return <Example onIncrease = { () => dispatch(increase()) } />
}
state가 바뀌어 컴포넌트가 리렌더링될 때마다 정의해놓은 함수들이 새롭게 만들어진다. 성능을 최적화시키기 위해서 useCallback과 함께 사용하는 것이 좋다.
const increase = useCallback( () => dispatch(increase()), [dispatch];
리덕스 상태들을 조회할 때 비구조화 할당을 사용하여 객체를 다시 생성하는 방식을 취했기 때문에, react에서는 각각의 상태가 바뀌는 것의 여부를 파악하지 못하고 무조건 다시 렌더링을 하게된다. = 즉, 최적화에 불리하다는 말이다.
const { count, prevCount } =useSelector((state: RootState) => ({
count: state.countReducer.count,
prevCount: state.countReducer.prevCount,
}));
공식문서의 해결방법 중 하나는 비구조화 할당을 사용하지 않고, 아래와 같이 각각의 값을 독립적으로 선언하는 방법이다.
const count = useSelector((state: RootState) => state.countReducer.count);
const prevCount = useSelector((state: RootState) => state.countReducer.prevCount);
각각의 상태변경여부를 파악할 수 있어 상태 최적화가 가능해진다. 위의 예제처럼 상태가 많지 않으면 사용할 법하지만, 속성이 많은 경우에 일일히 나열하게되면 코드가 길어져 가독성이 별로 좋지 않을 것이다.
shallowEqual함수는 selector로 선언한 값의 최상위 값들의 비교여부를 대신 작업해준다.
const { count, prevCount } = useSelector((state: RootState) => ({
count : state.countReducer.count,
prevCount: state.countReducer.prevCount,
}),shallowEqual);
*주의사항 : 최상위 값만 비교한다는 점에 주목해야한다.
구조가 아래 예시와 같다면 prevCount.a,prevCount.b,prevCount.c는 비교대상이지만, prevCount.c.d는 비교대상이 아니다.
prevCount = {
a : 0,
b : 1,
c : {d :2}
}
reselect는 넘어오는 인자 중 하나라도 변경이 되어야만 재계산을 실행한다. createSelector에 정의된
selector들의 반환값을 마지막 인자인 함수 형태의 인자들로 순서대로 넘겨받게 된다.
객체 형태로 반환하는 코드를 유지하면서 최적화를 가능하게 해준다.
또한, selector를 분리함으로써 재사용할 수 있고, 다른 셀렉터를 조합하여 사용할 수 있다.
import { createSelector } from 'reselect';
const getCount = state => state.count;
const getPrevCount = state => state.prevCount;
const { count, prevCount } = createSelector(
getCount,
getPrevCount,
(count, prevCount) => {
return {
count,
prevCount
}
}
)