리덕스 reselect 사용하기

2021년 02월 22일 by Xion

    리덕스 reselect 사용하기 목차

 

reselect를 알기 전, Selector를 먼저 알아야합니다.

| Selector란 ?

  • state 에서 필요한 데이터를 가져오거나, 계산을 수행해서 원하는 형태의 데이터를 가져오는 역할을 말합니다.

왜 사용할까?

selector는 Redux가 적은 양의 필요한 데이터만을 갖고 있게 data들의 연산을 도우며, state를 가져오는 컴포넌트들과 state의 구조 사이에 1개의 층(selector)를 두어 구조가 바뀌어도 연관된 모든 컴포넌트를 바꿀 필요 없이 selector 만 바꿔주면 성능이 향상되기 때문에 사용합니다.

| reselect 란?

selector 역할을 수행하면서 캐싱을 통해 동일한 계산을 방지해 성능을 향상시켜줍니다.
연산을 효율적으로 처리할 수 있게 도와주는 역할을 합니다.
즉, 새롭게 계산하지 않고 저장된 결과 값을 돌려주는 라이브러리메모이제이션 기능 지원합니다. (성능적인 장점 )

또한 data를 가공해서 사용하는 코드가 많을 경우에는 선택자함수(useSelector) 를 따로 분리해주는 장점이 있어서 가독성이 높아진다.
(다른 곳에서도 재사용 가능)

createSelector 함수를 통하여 선택자 함수를 만들면 메모이제이션 기능이 동작합니다.

리덕스에는 원본 데이터만 저장하고
필터 연산은 컴포넌트 쪽에서 계산하는 방법입니다.

왜 사용할까 ?

일번적으로는 selector 를 한 파일에 보관하고 관리합니다.
이럴경우에, 한 파일 내 있는 selector가 갱신되면 다른 selector도 갱신된다고합니다.
=> 필요하지 않은 만큼 컴포넌트 렌더링이 발생하겠죠?

이 때, 불필요한 렌더링을 막아주고 성능을 향상시키는 것이 바로 reselector라는 라이브러리의 memoization의 기능입니다.

( memoization ?

함수에 대한 입력을 추적하고, 나중에 참조할 수 있도록 입력과 결과를 저장하는 작업을 뜻합니다. 즉, 이전과 동일한 입력으로 함수를 호출하면 함수는 실제 작업을 건너뛰고 해당 입력 값을 마지막으로 수신했을 때 생성한 것과 동일한 결과를 return해줍니다. )

다음은 사용자가 option을 선택한 값에 따라 결과값이 바뀌는 코드입니다.

NumberSelect.js


function NumberSelect({ value, options, postfix, onChange }) {
    return (
        <div>
            <select onChange={e => {
                const value = Number(e.currentTarget.value);

                onChange(value);
            }}
                value={value}
            >
                {options.map(option => (
                    <option key={option} value={option}>
                    </option>
                ))}
            </select>
            {postfix}
        </div>
    )
}

FriendMain.js

import { shallowEqual, useDispatch, useSelector } from "react-redux";
import { getNextFriend } from "../../common/mockData";
import FriendList from "../component/FriendList";
import { addFriend, setAgeLimit, setShowLimit } from "../state";
import NumberSelect from '../component/NumberSelect';
import { MAX_AGE_LIMIT, MAX_SHOW_LIMIT } from "../common";
//FriendMain
function FriendMain() {

    const [ageLimit, showLimit, friendsWithAgeLimit, friendsWithAgeShowLimit] = useSelector(state => {
        //초기 ageLimit : 30
        const { ageLimit, showLimit, friend } = state.friend;

        //만약 ageLimit:30 보다 같거나 작으면 true 값 반환 즉, 30보다 밑인 애들만 반환
        const friendsWithAgeLimit = friend.filter(item => item.age <= ageLimit);

        return [ageLimit, showLimit, friendsWithAgeLimit, friendsWithAgeLimit.slice(0, showLimit)];
    }, shallowEqual);


    const dispatch = useDispatch();

    function onAdd() {
        const friends = getNextFriend();
        dispatch(addFriend(friends));
    }

    console.log("FriendMain render");


    return (
        <div>
            <button onClick={onAdd}>친구 추가</button>
            <hr />
            <NumberSelect
                onChange={v => {
                    dispatch(setAgeLimit(v))
                }}
                value={ageLimit}
                options={AGE_LIMIT_OPTIONS}
                postfix="세 이하만 보기"
            />
            <FriendList friends={friendsWithAgeLimit} />

            <NumberSelect
                onChange={v => {
                    dispatch(setShowLimit(v));
                }}

                value={showLimit}
                options={SHOW_LIMIT_OPTIONS}
                postfix="명 이하만 보기(연령 제한 적용)"
            />
            <FriendList friends={friendsWithAgeShowLimit} />

        </div>
    )
};

const AGE_LIMIT_OPTIONS = [15, 20, 25, MAX_AGE_LIMIT];
const SHOW_LIMIT_OPTIONS = [2, 4, 6, MAX_SHOW_LIMIT];


export default FriendMain;

state.js

import createReducer from "../common/createReducer";
import { MAX_AGE_LIMIT, MAX_SHOW_LIMIT } from './common';


//액션 생성 변수
const ADD = "friend/ADD";
const REMOVE = "friend/REMOVE";
const EDIT = "friend/EDIT";
//추가 액션
const SET_AGE_LIMIT = "friend/SET_AGE_LIMIT";
const SET_SHOW_LIMIT = "friend/SET_SHOW_LIMIT";


//액션 크리에이터 생성 함수
export const addFriend = friend => ({ type: ADD, friend });
export const removeFriend = friend => ({ type: REMOVE, friend });
export const editFriend = (friend) => ({ type: EDIT, friend });
export const setAgeLimit = ageLimit => ({ type: SET_AGE_LIMIT, ageLimit });
export const setShowLimit = showLimit => ({ type: SET_SHOW_LIMIT, showLimit });


// 초기 값 객체 friend 배열 초기화
const init = {
    friend: []
    , ageLimit: MAX_AGE_LIMIT
    , showLimit: MAX_SHOW_LIMIT
};

//리듀서 생성
//첫 번쨰 값 : 초기 값, 
//두 번째 값 : handlerMap
//handlerMap에 [ADD] key값은 state와 action을 인자로 받은 함수 발생
//그럼 새로운 배열객체 생성

const reducer = createReducer(init, {
    //객체를 만들어서 handler 함수를 작성
    [ADD]: (state, action) => state.friend.push(action.friend),
    [REMOVE]: (state, action) => state.friend = state.friend.filter(
        friend => state.friend.id !== action.friend.id
    )

    , [EDIT]: (state, action) => {
        const index = state.friend.findIndex(

            friend => friend.id === action.friend.id
        );
        if (index >= 0) {
            state.friend[index] = action.friend;
        }
    },
    [SET_AGE_LIMIT]: (state, action) => {
        state.ageLimit = action.ageLimit
    }
    , [SET_SHOW_LIMIT]: (state, action) => {
        state.showLimit = action.showLimit
    }
});

export default reducer;

위의 코드는 reselector 를 이용하지 않은 코드입니다.

아래에서는 reselector를 이용한 코드를 활용해보겠습니다.

선택자함수 selector.js 추가

import { createSelector } from "reselect";


//3가지 data를 단순히 가져오는 함수
const getFriends = state => state.friend.friend;
export const getAgeLimit = state => state.friend.ageLimit;
export const getShowLimit = state => state.friend.showLimit;


//reselect 함수 사용
import { createSelector } from "reselect";


//3가지 data를 단순히 가져오는 함수
const getFriends = state => state.friend.friend;
export const getAgeLimit = state => state.friend.ageLimit;
export const getShowLimit = state => state.friend.showLimit;


//reselect 함수 사용
export const getFriendsWithAgeLimit = createSelector(
    [getFriends, getAgeLimit],

    //선택자 함수
    //위에서 반환하는 값(getFriends,getAgeLimit) 을 받아서 작성
    //해당 함수는 friends 와 ageLimit이 변경되지 않았더라면 filter를 사용하지 않고 이전의 연산 값을 재사용.
    (friends, ageLimit) => friends.filter(item => item.age <= ageLimit),
);


//reselect 함수 사용
export const getFriendsWithAgeShowLimit = createSelector(
    [getFriendsWithAgeLimit, getShowLimit],

    //마찬가지로 해당 함수는 friendsWithAgeLimit 와 showLimit 가 변경되지 않았더라면, 이전의 값을 사용함.
    (friendsWithAgeLimit, showLimit) => getFriendsWithAgeLimit.slice(0, showLimit)
);

선택자 함수를 이용한 FriendMain.js

function FriendMain() {

    const [ageLimit, showLimit, friendsWithAgeLimit, friendsWithAgeShowLimit] = useSelector(state =>
        [
            //선택자 함수를 이용하여 사용
            getAgeLimit(state)
            , getShowLimit(state)
            , getFriendsWithAgeLimit(state)
            , getFriendsWithAgeShowLimit(state)
        ]
        , shallowEqual);

위와 같이 선택자 함수를 이용하여 변경할 수 있습니다.

이렇게 reselector 라이브러리createSelector를 사용하면., 전과는 다르게 값에 아무런 변화가 없어도 filter()slice() 는 실행되지 않고 이전의 연산된 값을 재사용한다는 것이 중요합니다.

'React' 카테고리의 다른 글

리액트의 Switch는 언제 사용할까?  (0) 2021.03.17
제너레이터(Generator)  (0) 2021.02.23
react-redux의 shallowEqual 사용하기  (0) 2021.02.22
redux-saga  (0) 2021.02.21
CORS와 Webpack DevSercer Proxy  (0) 2021.02.18