[React] redux-thunk 이해하기

Yuri Lee·2022년 6월 6일
1

이 글은 인프런 제로초님의 '[리뉴얼] React로 NodeBird SNS 만들기' 를 바탕으로 정리한 내용입니다.

redux-thunk 이해하기

  • redux-thunk란 redux 미들웨어로써 리덕스의 기능을 향상시켜주는 역할을 한다.
  • redux-thunk 는 이중에서도 리덕스가 비동기 액션을 디스패치 하도록 도와주는 역할을 한다. redux-saga를 강의에서 많이 활용하므로 redux-thunk 를 우선 소개하고자 한다.
  • redux-thunk 의 뜻 자체가 지연의 뜻을 담고 있다.
const INCREMENT_COUNTER = 'INCREMENT_COUNTER'

// 1 : 동기 액션 크리에이터 
function increment() {
  return {
    type: INCREMENT_COUNTER
  }
}

// 2 : 비동기 액션 크리에이터 
function incrementAsync() {
  return dispatch => {
    setTimeout(() => {
      // Yay! Can invoke sync or async actions with `dispatch`
      dispatch(increment())
    }, 1000)
  }
}

원래 redux는 비동기 액션이 실행되지 않지, redux-thunk 을 사용하면 실행 가능하다.

장점은?

하나의 액션에서 디스패치를 여러번 할 수 있다. 하나의 비동기 액션 안에 여러가지 동기 액션을 넣을 수 있다. 원래 리덕스 기능에서 확장된 개념이라고 보면 된다.

redux-thunk 소스코드

const thunk = createThunkMiddleware() as ThunkMiddleware & {
  withExtraArgument<
    ExtraThunkArg,
    State = any,
    BasicAction extends Action = AnyAction
  >(
    extraArgument: ExtraThunkArg
  ): ThunkMiddleware<State, BasicAction, ExtraThunkArg>
}

// Attach the factory function so users can create a customized version
// with whatever "extra arg" they want to inject into their thunks
thunk.withExtraArgument = createThunkMiddleware

export default thunk

소스코드를 보면 매우 간단하다. 다음의 코드를 바로 프로젝트에 활용해도 되지만 일단 설치해보자.

redux-thunk 설치

npm i redux-thunk

미들웨어는 항상 3단 고차함수 형태를 지닌다.

function createThunkMiddleware<
  State = any,
  BasicAction extends Action = AnyAction,
  ExtraThunkArg = undefined
>(extraArgument?: ExtraThunkArg) {
  // Standard Redux middleware definition pattern:
  // See: https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middleware
  const middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> =
    ({ dispatch, getState }) =>
    next =>
    action => {
      // The thunk middleware looks for any functions that were passed to `store.dispatch`.
      // If this "action" is really a function, call it and return the result.
      if (typeof action === 'function') {
        // Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
        return action(dispatch, getState, extraArgument)
      }

      // Otherwise, pass the action down the middleware chain as usual
      return next(action)
    }
  return middleware
}

simple middleware

const loggerMiddleware = ({ dispatch, getState }) => (next) => (action) => {
    console.log(action)
    return next(action)
}

action 을 실행하기 전에 콘솔 로그를 찍어주는 아주 간단한 미들웨어이다.

const loggerMiddleware = ({ dispatch, getState }) => (next) => (action) => {
    console.log(action)
    return next(action)
}

const configureStore = () => {
    const middlewares = [thunkMiddleware, loggerMiddleware];
    const enhancer = process.env.NODE_ENV === 'production'
        ? compose(applyMiddleware(...middlewares))
        : composeWithDevTools(applyMiddleware(...middlewares))
    const store = createStore(reducer, enhancer);
    return store;
};

const wrapper = createWrapper(configureStore, {
    debug: process.env.NODE_ENV === 'development', 
});

export default wrapper;

middlewares 안에다가 loggerMiddleware 를 다음의 형식으로 넣어주면 된다.

const middlewares = [thunkMiddleware, loggerMiddleware];

마치 리덕스 데브 툴에서 로깅해주는 것처럼 콘솔에 찍히는 모습을 확인할 수 있다.

커스텀하게 loggerMiddleware 를 만들어서 redux-dev-tools 을 대체할 수도 있다. 리덕스 데브퉅이 동작하는 것 역시 데브툴 미들웨어가 있기 때문이다.

대부분의 요청들은 비동기이다. 모든 기록들이 서버로 한번 갔다가 제대로 처리되면서 실패했으면 failure/ 성공하면 succes 이다.

원칙적으로는 3단계가 있다.

export const loginRequestAction = (data) => {
    return {
        type: 'LOG_IN_REQUEST',
        data
     }
}

export const loginSuccessAction = (data) => {
    return {
        type: 'LOG_IN_SUCCESS',
        data
     }
}

export const loginFailureAction = (data) => {
    return {
        type: 'LOG_IN_FAILURE',
        data
     }
}

thunk는 다음의 방식으로 활용할 수 있다. 10줄이 끝이다. 한번에 dispatch 를 여러번 할 수 있는 게 다이다. 나머지 것들은 다 직접 구현해야 한다.

export const loginAction = (data) => {
    return (dispatch, getState) => {
        const state = getState();
        dispatch(loginRequestAction());
        axios.post('/api/login')
            .then((res) => {
                dispatch(loginSuccessAction(res.data))
            })
            .catch((err) => {
                dispatch(loginFailureAction(err));
            })
    }
}

Redux-saga

Redux-saga 를 사용하면 딜레이 기능을 사용할 수 있다. saga 에서 미리 만들어줘서 제공해준다. 사용자의 실수로 클릭을 두번 했을 경우가 발생할 수 있는데 thunk의 경우 2번 모두 요청이 날라가지만 saga에서 take latest 를 이용하면 가장 나중에 발생한 요청 1개만 보낼 수 있다.

💥 셀프 디도스를 하지 말아라!

셀프 디도스를 막기 위해서는 saga의 throttle(스크롤 방지..) 를 사용하여 1초에 3번 이상 액션이 일어날 경우 차단할 수 있다.


Q. 미들웨어 사용 이유?

리듀서에서 처리하기 전에 다른 일을 하고자 미들웨어를 쓴다. 리듀서는 무조건 동기 작업만 수행할 수 있습니다. (태생적으로) 그래서 비동기 작업을 수행하기 위해서 thunk같은 미들웨어를 액션과 리듀서 사이에 끼어넣어야 한다.

Q. 리액트에서 비동기를 위해 thunk, saga는 필수인가 ?

직접 하셔도 되지만, 컴포넌트에서 axios를 호출하는 것은 지양하시는 게 좋다. 그렇게 하다보면 모양이 thunk나 saga처럼 구현되므로 처음부터 thunk나 saga를 쓰는게 좋다.

profile
Step by step goes a long way ✨

0개의 댓글