useSelector란?
useSelector
는 리덕스의 상태값을 조회하기 위한 hook 함수로 이전의 connect
를 통해 상태값을 조회하는 것보다 훨씬 간결하게 작성하고 코드가독성이 상승되는 장점이 있는 함수입니다. 사용방법은 다음과 같습니다.
//counterReducer에 있는 count 값 가져오기
const count = useSelector(state => state.counterReducer.count);
// 여러 reducer의 값 한번에 가져오기
const {counter, person } = useSelector(state => ({
count : state.counterReducer.count,
person: state.personReducer.person,
}));
이를 통해 우선 useSelector
를 사용하는 간단한 예제를 작성해 보도록 하겠습니다.
예제 코드 작성
counter 리듀서 작성
import { createReducer, createAction } from 'typesafe-actions';
import { produce } from 'immer';
const prefix: string = 'COUNT_';
const INCREASE_COUNT = `${prefix}INCREASE_COUNT`;
export const increaseCount = createAction(INCREASE_COUNT);
export interface countReducerType {
count: number;
prevCount: number;
}
const initialState: countReducerType = {
count: 0,
prevCount: 0,
};
export default createReducer(initialState, {
[INCREASE_COUNT]: (state, action) =>
produce<setCountType>(state, draft => {
draft.prevCount = draft.count;
draft.count = draft.count + 1;
}),
});
person 리듀서 작성
import { createReducer, createStandardAction } from 'typesafe-actions';
import { produce } from 'immer';
const prefix: string = 'PERSON_';
const SET_PERSON_NAME = `${prefix}SET_PERSON_NAME`;
export const setPersonName = createStandardAction(SET_PERSON_NAME)<string>();
export interface personReducerType {
name: string;
age: number;
}
const initialState: personReducerType = {
name: '',
age: 0,
};
export default createReducer(initialState, {
[SET_PERSON_NAME]: (state, action) =>
produce<setPersonType>(state, draft => {
draft.name = action.payload;
}),
});
위의 reducer들은 각각 다음과 같은 역할을 담당합니다.
- countReducer : 이전값과 현재값을 보여주고 increaseCount 함수를 통해 count를 증감시킵니다.
- personReducer : person의 정보를 보여주고 setPersonName함수를 통해 name를 변경합니다.
이제 각각 리듀서에 대한 컴포넌트를 작성하고 이를 합쳐서 보여주도록 하겠습니다.
countContainer 작성
import React from 'react';
import styled from 'styled-components';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '../types/redux/RootState';
import { increaseCount } from '../store/reducers/countReducer';
type CountContainerProps = {}
function CountContainer({}: CountContainerProps) {
const dispatch = useDispatch();
const { count, prevCount } = useSelector((state: RootState) => ({
count : state.countReducer.count,
prevCount: state.countReducer.prevCount,
}));
const onIncreaseCount = () => dispatch(increaseCount());
return (
<S.CountContainer>
<p>이전 count : {prevCount}</p>
<p>현재 count :{count} </p>
<button onClick={onIncreaseCount}>버튼 증가</button>
</S.CountContainer>
);
};
CountContainer.defaultProps = {} as CountContainerProps;
export default CountContainer;
const S: any = {};
S.CountContainer = styled.div`
`;
personContainer 작성
import React, { useState } from 'react';
import styled from 'styled-components';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '../types/redux/RootState';
import { setPersonName } from '../store/reducers/personReducer';
type PersonContainerProps = {}
function PersonContainer({}: PersonContainerProps) {
const [changeCount, setChangeCount] = useState('');
const dispatch = useDispatch();
const { name, age } = useSelector((state: RootState) => ({
name : state.personReducer.name,
age: state.personReducer.age,
}));
const onChangeName = () => {
const updateName = changeCount +'이름추가 ';
setChangeCount(updateName);
dispatch(setPersonName(updateName));
};
return (
<S.PersonContainer>
<p>이름 : {name}</p>
<p>나이 : {age}</p>
<button onClick={onChangeName}>이름 변경</button>
</S.PersonContainer>
);
};
PersonContainer.defaultProps = {
} as PersonContainerProps;
export default PersonContainer;
const S: any = {};
S.PersonContainer = styled.div`
border-bottom: 1px solid #5c6370;
margin: 1rem;
`;
각각의 컴포넌트는 다음과 같은 역활을 수행합니다.
- personContainer : personReducer의 정보를 보여주고 name 값을 게속 이름추가 를 추가로 넣어줍니다.
- countContainer : 이전과 현재 count 값을 보여주고 count값을 증가 시킵니다.
container들을 모아주는 view 컴포넌트 작성
import React from 'react';
import PersonContainer from '../containers/PersonContainer';
import CountContainer from '../containers/CountContainer';
function Main() => {
return (
<>
<PersonContainer/>
<CountContainer/>
</>
);
};
Main.getInitialProps = async ({}) => {
return {};
};
export default Main;
실행결과

성공적으로 useSelector
를 통해 값을 조회하는 것을 볼 수 있습니다.
성능최적화
그럼 이제 위의 작업한 내용을 react 개발도구
를 실행시켜 최적화여부를 검사해 보도록 하겠습니다. 설치가 안되었다면 react-developer-tools 로 들어가 설치하면 됩니다.~
react-dev-tool 실행결과

위의 하늘색으로 표기되는 부분은 컴포넌트가 다시 렌더링 되었다는 뜻입니다. 그러나 현재 예제의 personContainer
와 CountContainer
는 서로의 reducer 값을 조회를 안하지만 그럼에도 전부 다시 렌더링이 되고 있습니다.
다시 렌더링 되는 이유
const { count, prevCount } = useSelector((state: RootState) => ({
count : state.countReducer.count,
prevCount: state.countReducer.prevCount,
}));
위의 예시를 보면 count와 prevCount 를 조회할때 다시 객체를 생성하는 방식으로 선언하였기 때문에 react
에서는 이를 상태가 바뀌는 것의 여부를 파악할수 없어 무조껀 다시 렌더링 해버리게 됩니다.
첫번째 해결방법 - 독립 선언
위에서 선언한것처럼 객체 방식이 아닌 각각의 값을 독립적으로 선언하게 되면 이에대한 상태변경여부를 파악할수 있어 상태가 최적화 될 수 있습니다.
const count= useSelector((state: RootState) => state.countReducer.count);
const prevCount= useSelector((state: RootState) => state.countReducer.prevCount);

다음과 같이 선언하면 짜잔.!! 서로 값이 변경되어도 다시 랜더링 되지 않습니다.~!
두번째 해결방법 - equalityFn
useSelector
에는 다음과 같이 선택옵션으로 equalityFn
라는 파리미터가 존재합니다.
const result: any = useSelector(selector: Function, equalityFn?: Function)
의 함수 표현은 다음과 같습니다.
equalityFn?: (prev: any, next: any) => boolean
equalityFn
는 이전 값(prev)과 다음 값(next)을 비교하여 true가 나오면 다시 렌더링을 하지 않고 false가 나오면 렌더링
을 진행 합니다.
이를 사용해 다음과 같이 작성하면 첫번째방법과 마찬가지로 최적화가 완료됩니다.!
const { count, prevCount } = useSelector((state: RootState) => ({
count : state.countReducer.count,
prevCount: state.countReducer.prevCount,
}),(prev, next) => {
return prev.count === next.count && prev.prevCount === next.prevCount;
});
세번째 해결방법 - shallowEqual
두번째 해결방법은 사실 이전값 다음값의 비교인데 위의 예시는 2개이지만 여러개일 겨우 항상 비슷한 내용을 반복해서 적어줘야 하는 번거로움이 존재합니다. 이를 위해 redux 에서 shallowEqual
라는 함수를 제공 합니다. shallowEqual
는 selector로 선언한 값의 최상위 값들의 비교여부를 대신 작업해 줍니다.
const { count, prevCount } = useSelector((state: RootState) => ({
count : state.countReducer.count,
prevCount: state.countReducer.prevCount,
}),shallowEqual);
위의 2번째 최적화 방법과 방식은 동일하지만 더욱 간결한 코드로 작성이 가능한 장점이 존재합니다. 이때 주의할 사항은 shallowEqual
은 최상위 값만 비교한다 입니다. 예를 들어 위의 prevCount 가 현재처럼 단순한 0이 아닌
prevCount = {
a : 0,
b : 1,
c : {d :2}
}
일 경우 prevCount.a
, prevCount.b
, prevCount.c
는 비교하지만 prevCount.c.d
는 비교하지 않습니다.
참조