React - Redux Thunk (feat. redux-actions)

김정인·2023년 1월 8일
0

React

목록 보기
3/4

📌 Redux Thunk 란?


🔎 thunk란?

thunk란, 특정 작업을 나중에 하도록 미루기 위해서 함수형태로 감싼것을 칭합니다.

const sums = 1 + 2; //즉시 결과가 나오는 식
/*
즉시 결과가 나오는 것이 아닌,함수를 호출해야 결과가 나옵니다.
이 함수를 thunk라고 부릅니다.
*/
const sumFn = () => 1 + 2;
sumFn();

🔎 redux - thunk란?

리덕스는 기본적으로 액션 객체를 디스패치합니다.
일반 액션 생성자는 파라미터를 가지고 액션 객체를 생성하는 작업만 합니다. 따라서 특정 액션이 몇초뒤에 실행되게 하거나, 현재 상태에 따라 액션을 무시하려면 일반 액션 생성자로는 할 수 없습니다.

const actionCreator = (payload) => ({action: 'ACTION', payload});

리덕스 썽크란 대표적인 비동기 작업 처리를 위한 미들웨어로, 액션 객체가 아닌 함수를 디스패치 할 수 있습니다. 함수를 디스패치 할 때에는, 해당 함수에서 dispatch와 getState를 파라미터로 받아와주어야 합니다. 이 함수를 만들어주는 함수를 thunk 라고 합니다.

const getComments = () => async (dispatch, getState) => {
  // 이 안에서는 액션을 dispatch 할 수도 있습니다.
  // async/await을 사용해도 상관없습니다.
  
  // getState를 사용하여 현재 상태도 조회
  const id = getState().post.activeId
  
  // 요청이 시작했음을 알리는 액션
  dispatch({ type: 'GET_COMMENTS' });
  
  // 댓글을 조회하는 프로미스를 반환하는 getComments 가 있다고 가정
  try {
    // 성공시
    const comments = await api.getComments(id);
    dispatch({ type:  'GET_COMMENTS_SUCCESS', id, comments });
  } catch (e) {
    // 실패시
    dispatch({ type:  'GET_COMMENTS_ERROR', error: e });
  }
}

🔎 redux - thunk 참고 예시)

redux - thunk를 사용하여 글자색을 바꾸는 간단한 예제입니다.

  • 선언
import {createAction, handleActions} from 'redux-actions';

const SET_COLOR_BLACK = 'colorChangerMW/SET_COLOR_BLACK';
const SET_COLOR_BLUE = 'colorChangerMW/SET_COLOR_BLUE';

//액션 생성 함수
export const setColorBlack = createAction(SET_COLOR_BLACK);
//thunk 함수
export const setColorBlackAsync = () => dispatch => {
    setTimeout(() => {
        dispatch(setColorBlack());
    }, 1000);
} 

export const setColorBlue = createAction(SET_COLOR_BLUE);
export const setColorBlueAsync = () => dispatch => {
    setTimeout(() => {
        dispatch(setColorBlue());
    }, 1000);
}

const colorChangerMW = handleActions(
    {
        [SET_COLOR_BLACK]: (state, action) => ({
            text: '검정',
            color: '#000',
        }),
        [SET_COLOR_BLUE]: (state, action) => ({
            text: '파랑',
            color: '#00f',
        }),
    },
    initialState
);

export default colorChangerMW;

  • 호출
import React from 'react';
import {connect} from 'react-redux';
import {setColorBlackAsync, setColorBlueAsync} from '../modules/colorChangerMW';
import ColorChangerMW from '../components/ColorChangerMW';
import {bindActionCreators} from 'redux';

const ColorChangerContainer = ({text, color, setColorBlackAsync, setColorBlueAsync}) => {
    return (
        <ColorChangerMW text={text} 
						color={color}
                        onSetColorBlack={setColorBlackAsync}
                        onSetColorBlue={setColorBlueAsync}
                        />
    );
};

export default connect(
    state => ({
        text: state.colorChangerMW.text,
        color: state.colorChangerMW.color,
    }),
    dispatch => bindActionCreators(
        {
            setColorBlackAsync,
            setColorBlueAsync,
        },
        dispatch
    ),
)(ColorChangerContainer);


📌 Redux-actions 란?


위 글자색은 바꾸는 예제 코드 선언부의 코드에서 아래와 같은 redux-actions 라이브러리 import를 볼 수 있는데 이는 무엇이고, 왜 사용하는 것일까요?

import {createAction, handleActions} from 'redux-actions'; 

redux-actions 패키지에는 리덕스의 액션들을 관리하기 위한 유용한 createAction, handleActions 가 있습니다.

🔎 createAction을 이용한 액션 생성 자동화

export const increment = (index) => ({
    type: types.INCREMENT,
    index
});
 
export const decrement = (index) => ({
    type: types.DECREMENT,
    index
});

위와 같이 리덕스에서 액션을 만들다 보면 단순히 파라미터로 전달받은 값을 객체로 넣는 모든 액션 생성자를 일일이 만드는 것이 번거로울 수 있습니다.

createAction을 사용하면 이 작업을 편하게 자동화 할 수 있습니다.

export const increment = createAction(types.INCREMENT);
export const setColor = createAction(types.SET_COLOR);
increment(3);
/*
결과 :
{
    type: 'INCREMENT'
    patload:3
}
*/

setColor({index: 5, color:'#fff'})
/*
결과 :
{
    type: 'SET_COLOR'
    payload: {
        index:5,
        color:'#fff'
    }
}
*/

setColor와 같이 어떤 파라미터를 받는지 명시하지 않았기 때문에 헷갈린다면 createAction의 두 번째 파라미터에 payload 생성 함수를 전달하여 코드상 명시가 가능합니다.

export const setColor = createAction(types.SET_COLOR, ({index,color}) => ({index,color}) );

🔎 switch 문 대신 handleActions 사용하기

리듀서에서는 액션의 타입에 따라 다른 작업을 처리하기 위해 switch문을 사용했습니다. 하지만 switch문을 사용하는 방법은 scope가 리듀서 함수로 설정된다는 단점이 있기때문에 서로 다른 case에서 let 이나 const로 변수를 선언할 때 같은 이름이 중첩될 시 에러가 발생합니다.

handleActions을 사용하면 이러한 문제를 해결 할 수 있습니다.
handleActions() 함수의 첫번째 인자로는 action에 따라 실행할 함수들을 가지고 있는 객체를, 두번째 인자로는 상태의 기본값(initialState)를 넣어주면 됩니다.

import {createAction, handleActions} from 'redux-actions'
const initialState = {
  count: 0,
}

const UP = 'COUNT/UP';
const DOWN = 'COUNT/DOWN';

export const up = createAction(UP);
export const donw = createAction(DOWN);

const count = handleActions(
  {
  [UP]: (state, action) => ({
    counter: state.counter + action.payload
  }),
  [DOWN]: (state, action) => ({
    counter: state.counter - action.payload
  })
}
  , 
  initialState);


📌 Immer


위 코드에서 handleActions()함수를 사용해서 상태값을 업데이트 할 때 스프레드 연산자를 사용하여 {...state,~} 와 같이 기존의 상태값을 복사하여 새로운 객체 안에 넣어주는 방식으로 불변성을 지키며 상태값을 바꿔주는 것을 확인할 수 있습니다. 하지만 이는 객체의 구조가 복잡해진다면 불변성을 유지하면서 변경 내용을 업데이트하는 것이 까다로워집니다.

immer를 사용하면 복잡한 구조일 경우에도 쉽고 편하게 불변성을 유지하면서 코드를 작성할 수 있습니다. (주로 produce 함수를 사용)

  • produce()함수는 기본적으로 두개의 인자값을 받습니다.

    • 첫번째 인자값으로는 업데이트 하고자하는 현재의 상태값을 받고

    • 두번째 인자값으로는 상태를 어떻게 업데이트 할 지 정의하는 함수를 받습니다. 아래 코드에서 (draft)=>{} 함수입니다.

      • 여기서 draft는 현재의 상태값이 복사된 새로운 state입니다. 그렇기 때문에 두번째 인자값으로 전달된 함수 내부에서 원하는 값을 변경하면 불변성을 유지하며 새로운 상태를 생성해줍니다.
import {createAction, handleActions} from 'redux-actions'
import {produce} from 'immer'
const initialState = {
  count: 0,
}

const UP = 'COUNT/UP';
const DOWN = 'COUNT/DOWN';

export const up = createAction(UP);
export const donw = createAction(DOWN);

const count = handleActions(
  {
  [UP]: (state, action) => produce(state, (draft) => {
    draft.count = state.count + action.payload
  }),
  [DOWN]: (state, action) => produce(state, (draft) => {
    draft.count = draft.count - action.payload
  })
}
  , 
  initialState);


🔗 참고한 글

[React/Redux] redux-thunk
redux-thunk
Redux - redux-actions / immer

profile
FE 개발자

0개의 댓글