ttaeng_99 2021. 2. 18. 15:40
반응형

🤔 Redux 의 비동기 오류

Context API 에서 Redux 로 수정하는 토이 프로젝트를 진행하는 중이었다.

action 생성자들을 별도파일로 제작하여 import 해서 사용중이었고, 이처럼 fetch() 로 가져온 데이터로 상태를 최신화할때 에러가 생겼다.

 

내용은 대충, fetchList() 라는 액션 생성자에 cartList를 담았는데 이것이 객체형태로 정상적인 반환이 되지 않은 것 같다.

또한, 대놓고 Async action에 대한 미들웨어를 사용하라고 권장하고 있다.

 

이러한, 비동기 처리를 위한 적절한 미들웨어를 찾아보았다.


💜 Redux-thunk 알아가기

리덕스를 사용하는 앱에서 비동기 작업을 사용할 때, 가장 기본적인 방법으로 사용되는 것이 redux-thunk 이다.

이 미들웨어는, 리덕스를 개발한 Dan Abramov 에 의해 만들어졌으며, 공식문서도 이 미들웨어를 통한 비동기 처리를 권장한다.

Redux-thunk 의 장점은 직관적이고 익숙한 비동기 작업이 가능하다는 것이다.

 

- thunk 란?

출처: 네이버 영어사전(https://en.dict.naver.com/)

?????? 과연 Redux 와 '푹, 탁' 의 연결고리는 무엇일까 ㅋㅋㅋㅋㅋㅋㅋ

thunk는 특정 작업을 나중에 하도록 미루기 위해 함수형태로 감쌌다는 의미이다. (출처:ko.wikipedia.org/wiki/%EC%8D%BD%ED%81%AC)

const sumNow = 1 + 2;
const sumSync = () => 1 + 2;
// sumSync()

이처럼, sumNow는 바로 덧셈을 진행하지만, sumSync() 함수는 호출되기 전까지 덧셈을 진행하지 않는다.

객체가 아닌 함수를 반환하는 액션 생성자를 만들 수 있는 Redux-thunk 의 기능에 아주 어울리는 단어이지 않나 싶다.

 

- Redux-thunk 의 역할

위에서 얘기했듯, Redux-thunk 는 액션 생성자가 객체 대신 함수를 반환할 수 있도록 해주는 미들웨어이다.

일반 액션 생성자는 아래처럼 받은 변수를 활용해서, 액션 객체를 반환하는 역할을 한다.

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

 

이러한 객체 대신 함수를 반환하는 이유는, 특정 액션을 딜레이 시키거나 조건부로 취소하는 등에 있다.

// 디스패치를 지연시키는 경우
function incrementAsync(sec, gap) {  
  return function(dispatch) {  
    setTimeout(() => {  
      dispatch(increment(gap));  
    }, 1000);  
  }  
}

// 디스패치를 상태 조건부로 실행하는 경우
function incrementIfOdd() {  
  return (dispatch, getState) => {  
    const { counter } = getState();  
    if (counter % 2 === 0) {  
      return;  
    }​  
    dispatch(increment());  
  };  
}  

* 액션 생성자 인자로 사용된 함수의 인자에 dispatch, getState를 전달하면, 스토어의 상태를 참조해서 디스패치가 가능한 것


💜 Redux-thunk 사용하기

- 설치

npm install redux-thunk

 

- 적용

이전 포스팅의, 미들웨어나 redux-logger 적용방법과 똑같다. Store를 설정할 때, applyMiddleware() 를 사용한다.

import { createStore, applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';	// default 모듈을 받아온다.
import reducer from './reducer';

const store = createStore(reducer, applyMiddleware(ReduxThunk))

export default store;

 

* 기본 템플릿

const thunkActionCreator = (payload) => 
  (dispatch, getState) => {
    // Code
}

다음과 같이, 액션 생성자를 만드는데 내부 인자로는 함수가 적용된다. 

 

- Redux-thunk 반영 (Redux-Cart)

// store.js
import { applyMiddleware, createStore } from 'redux';
import ReduxThunk from 'redux-thunk';
import RootReducer from './Reducer';

const store = createStore(
  RootReducer,
  applyMiddleware(ReduxThunk),
);


// app.js
  const fetchData = async () => {
    dispatch(loadStart());
    try {
      const res = await fetch(DATA_API);
      const cartList = await res.json();
      await dispatch(fetchList(cartList));	// 디스패치에 함수 적용
      dispatch(loadEnd());
    }
    catch(err) {
      alert(err);
    }
  }

store.js 에서 applyMiddleware 를 통해 redux-thunk 를 반영한 모습이다.

fetchData() 는 패치함수이면서, 동시에 액션 생성자가 된다. 또, thunk 덕에 하나의 생성자에서 다중 디스패치가 가능하다.

이처럼, 인자로 함수를 받기 때문에 Async Function 를 적용했을 뿐 아니라, 내부에 try - catch 로직도 반영했다.

 

- 단점

Redux-thunk 는 보시다시피, 액션 생성자에 함수를 적용할 수 있다는 특징이 있다. 하지만, 이것이 한편으론 단점이 될 수 있다.

  • 로직의 일관성이 없어진다. 액션 생성자가 어떤 경우에는 액션 객체를, 어떤 경우에는 API 요청이나 비동기를 처리하게 된다.
  • 액션 생성자의 로직이 복잡해진다. 생성자는 액션 객체를 반환해서 디스패치로 전달하는 역할에 충실해야 하는데 부가로직이 붙는 것이다.

redux-thunk를 통해, 쇼핑카트의 서버를 패치하는 Async Function 을 액션 생성자에 활용할 수 있었다.

Redux 비동기, redux-thunk 를 공부하면 항상 단짝저럼 붙는 미들웨어가 있는데, 이것이 바로 redux-saga 이다.

다음 포스팅에선 redux-saga에 대해 공부하고, 둘 중 더 적합한? 혹은 효율적인 미들웨어를 프로젝트 비동기 처리에 적용고자한다.

 

[출처]

- 이소영 님의 미디엄: medium.com/humanscape-tech/redux%EC%99%80-%EB%AF%B8%EB%93%A4%EC%9B%A8%EC%96%B4-thunk-saga-43bb012503e4

- 벨로퍼트 님의 블로그: react.vlpt.us/redux-middleware/04-redux-thunk.html , velopert.com/3401  

- a-tothe-z 님의 블로그: a-tothe-z.tistory.com/14

 

반응형