Redux와 함께 쓰면 유용한 라이브러리 정리

1. redux-actions

액션 생성함수를 간단히 작성할 수 있게 도와주고, 리듀서에서 switch문을 생략해주는 라이브러리

코드비교 [기존]

export const increase = () => ({type: INCREASE});
export const decrease = () => ({type: DECREASE});

[사용후]

import {createActions} from 'redux-actions';

export const increase = createActions(INCREASE);
export const decrease = createActions(DECREASE);

[기존]

function counter(state = initialState, action) {

    switch(action.type){
        case INCREASE:
            return { number: state.number+1};
        case DECREASE:
            return { number: state.number-1};
        default:
            return state;
    }
}

[사용후]

import {handleActions} from 'redux-actions';
const counter = handleActions(
    {
        [INCREASE]: (state,action) => ({number: state.number +1}),
        [DECREASE]: (state,action) => ({number: state.number -1}),
    },
    initialState
)

action 객체에 파라미터로 넘겨주는 값이 있을때는 payload 라는 객체 안에 데이터가 들어가게 된다.

{type: INCREASE , payload: 'data'}

따라서 handleAction에서 값을 참조하고자 할때 action.payload 안에서 값을 가져와야한다

const todos = handleActions({
    [INSERT]: (state,action) => ({...state, todos: state.todos.concat(action.payload)}),
    [CHANGE_INPUT]: (state,action) => ({...state, input: action.payload}),
    [REMOVE]: (state,action) => ({...state, todos: state.todos.filter((todo) => (todo.id !== action.payload))}),
    [TOGGLE]: (state,{payload: id}) => ({...state, todos: state.todos.map((todo) => todo.id === id ? {...todo,done: !todo.done} : todo)})
},initialState)

코드 중간에 보면 모든 값이 payload로만 대체되기 때문에 가독성 부분에서 추후 헷갈릴수가 있다. 따라서 Toggle 케이스에서 보이는것 처럼 {payload: id} 라는 문법을 통해 payload -> id 값으로 대체해주면 가독성 또한 향상될 수 있다.


2. immer

reducer 에서 객체를 복사하고자 할때 객체의 구조가 복잡할수록 전개연산자로 복사할 수 있는 범위가 까다로워 지므로 immer 라이브러리에 이쓴 produce를 사용하여 간단하게 새로운 객체를 생성할 수 있게 사용한다.

import produce from 'immer';

const todos = handleActions({
    [INSERT]: (state,{payload:todo}) => produce(state,draft => {draft.todos.push(todo)}),
    [CHANGE_INPUT]: (state,{payload:input}) => produce(state,draft => {draft.input = input}),
    [REMOVE]: (state,{payload: id}) => produce(state,draft => {
        const index = draft.todos.findIndex(todo => todo.id === id);
        draft.todos.splice(index,1);
    }),
    [TOGGLE]: (state,{payload: id}) => produce(state,draft => {
        const todo = draft.todos.find(todo => todo.id === id);
        todo.done = !todo.done;
    }),
}, initialState)


3. Hook과 함께 사용하기

기존 react 라이브러리에서 사용했던 useState, useEffect 등과 같이 함수형 컴포넌트에서 사용하는 hook이 react-redux 라이브러리 안에도 존재한다. 이를 사용하여 좀 더 간결하게 redux 구조를 짜는것이 가능하니 사용하는 방법을 잘 익혀두면 좋을 것 같다.


useSelector, useDispatch


import { useSelector,useDispatch } from 'react-redux'

const CounterContainer = () => {
    const number = useSelector(state => state.counter.number);
    const dispatch = useDispatch();
    return (
        <Counter 
            number={number}
            onIncrease= {() => dispatch(increase())}
            onDecrease= {() => dispatch(decrease())}
            />
    )
}

위의 코드를 자세히 살펴보면 useSelector 훅을 통해 store에 있는 state에 접근하여 사용하고자 하는 number 값을 현재 컴포넌트의 값으로 가져왔다. 마찬가지로 useDispatch() 훅을 통해 store에 이벤트를 호출할 수 있는 dispatch 객체를 가져왔고, 이를 사용하여 store에 있는 action 함수를 호출했다.

기존의 mapStateToProps 와 mapDispatchToProps, connect 이런 redux 구조적인 문법들이 사라지고 조금 더 익숙한 훅 표현 방식으로 더 간략하게 사용되는 장점이 있다.

하지만 이를 더 최적화 처리를 한다면 현재 number 값이 바뀔때마다 onIncrease와 onDecrease 함수가 새로 만들어지고 있기 때문에 useCallback을 사용하여 함수 생성을 최적화 해줄 수 있다.


<Counter 
            number={number}
            onIncrease= {useCallback(() => dispatch(increase()),[dispatch])}
            onDecrease= {useCallback(() => dispatch(decrease()),[dispatch])}
            />


useStore


useStore의 경우 직접 store에 접근하여 state 값을 가져오거나 dispatch 를 호출할때 사용가능하지만 정말 어쩌다가 스토어에 직접 접근하는 경우가 아니라면 보통 쓰진 않는다.