ng-redux async action with redux-thunk

참고한 문서는 다음 두 개!
공식 홈페이지: AsyncActions
깃허브 공식 예제: ng-redux/examples/async

 


redux-thunk 의 아이디어는 다음과 같다.

lazy-evaluation of action!

즉, 객체를 리턴하는 대신 객체를 리턴하는 함수를 리턴해서 함수가 비동기적으로 작업을 진행하는 도중에 중간중간 객체를 리턴하자는 심플한 아이디어! 그럼 원래 액션객체를 받아먹던 redux reducer가 놀랄(?) 수도 있으니 그 중간에서 redux-thunk가 middleware로 중재해주겠다는 것.

그럼 결국 이 포스팅의 남은 목적은 어디서부터 angular의 dependency injection을 사용하고, 어디까지 es6의 import를 사용할 것인가의 문제로 수렴한다. 일단 대부분의 async action들은 $http 서비스가 필요하다. $ngRedux.dispatch 함수도 필요하고, 그 외에도 프로젝트에서 쓰고 있는 각종 상수들을 담고 있는 임의의 서비스 Config 도 필요하다고 하자.

 


 

아래 설명은 잘못된 구조라서 포스트를 발행한 이후에 비공개로 설정했다.
왜 잘못된 구조였는지 다음 <hr> 뒤에 이어서 설명함!

어디가 잘못됐는지 찾아보라는 뜻ㅎ

dispatch 정도야 thunk가 반환하는 함수의 인자로 넣어주면 크게 문제되진 않는다. 그 정도 수고는 별거 아닌 것 같아. 하지만, 그 때 그 때 thunk에 맞는 angular service들을 thunk를 호출할 때 마다 넣어주는 것은 너무 번거롭다고 느꼈다. 그래서 thunk를 angular.service() 의 생성자 함수 안에 넣는 것이 베스트라고 판단하게 된 것!

 

import angular from 'angular';

const REQUEST_EXCHANGE_RATES = 'REQUEST_EXCHANGE_RATES';
const SUCCESS_EXCHANGE_RATES = 'SUCCESS_EXCHANGE_RATES';
const FAILURE_EXCHANGE_RATES = 'FAILURE_EXCHANGE_RATES';

export const rateTypes = {
  REQUEST_EXCHANGE_RATES,
  SUCCESS_EXCHANGE_RATES,
  FAILURE_EXCHANGE_RATES,
};

export default angular.module('olaf.service', [])
  .service('GeneralService', function($http, $ngRedux, Config) {
    'ngInject';
    const self = this;
    const dispatch = $ngRedux.dispatch;

    self.getExchangeRates = () => {
      const url = Config.BASE_URL + '/v1/rate';
      // return $http.get(url, { cache: true }, Config.HTTP_HEADER_CONFIG);
      function requestExchangeRates() {
        return {
          type: REQUEST_EXCHANGE_RATES,
          payload: null,
        };
      }

      function successExchangeRates(data) {
        return {
          type: SUCCESS_EXCHANGE_RATES,
          payload: {
            data
          }
        }
      }

      function failureExchangeRates(data) {
        return {
          type: FAILURE_EXCHANGE_RATES,
          payload: {
            data
          }
        }
      }
      return () => {
        dispatch(requestExchangeRates());

        return $http.get(url, { cache: true }, Config.HTTP_HEADER_CONFIG)
        .then(
          res => dispatch(successExchangeRates(res.data)),
          res => dispatch(failureExchangeRates(res.data))
        );
      };
    };
  })
  .name;

이렇게 선언해준 뒤에 이후 컨트롤러의 생성자 함수내부에서, 또는 mapStateToTarget() 내부에서 GeneralService.getExchangeRates()(); 와 같이 currying 해주면 된다.

 


Screenshot from 2016-07-08 23-50-40

그 결과로 콘솔에서 위와 같이 action이 찍히는 것을 볼 수 있다!

 

 


 

여기서부터 수정된 구조!

 

위 코드는 정말 잘 작동하는데 정말 마음 껏 비웃음 당해도 할 말 없는 부분이 있으니, 한 달 이불킥 감인 ‘currying 해주면 된다’ 라는 문장이다. 여기서 다들 마음껏 웃어주시면 됨미다 ^00^ 어하ㅓㅏ하하ㅎㅎ..

여기까지 짜놓고 보니, redux-thunk가 없어도 되겠더라고. 즉, thunk를 전혀 쓰지 않고 구현한 예제라는 것이 일단 첫 번째 문제. 그리고 내가 만든 api service와 ngRedux의 결합도가 너무 높다는 것도 또 문제였다. api 호출하고 프로미스만 리턴하는 게 딱 적당한 수준인데, 여기서 action까지 뱉어낸다? 이게 두 번째 문제.

그럼 어째. 일단, state를 변경할 수 있는 건 반드시 그에 상응하는 action을 받아먹은 reducer에서 이뤄져야 한다는 것이 flux 구조니, action이 적당히 바뀌어야 하고, 그 적당히 바뀌어야 하는 부분을 바로 redux-thunk가 도와주는 것임을 다시 한 번 상기해봤다. 그리고 또한 angular의 dependency injection이 무조건 필요하다는 것도 잊지 말자.

그럼 redux와 angular가 이어지는 부분은 어디지? 컴포넌트의 생성자 함수에서 $ngRedux 를 주입받아서 action을 컨트롤러에 connect 해주는 부분이다. 이 action을 angular service로 만드는 것이 해답이었다.

OlafService 와 같은 angular service 에서 api 호출하는 promise를 리턴하고, 그것을 주입받는 OlafAction 과 같은 angular service를 만들고, 이것을 컴포넌트의 컨트롤러에서 주입받아서 connect의 두 번째 인자로 넘겨주는 것!

 

function olafActionConstructor(OlafService) {
  'ngInject';
  
  const getExchangeRates = () => {
    return (dispatch) => {
      dispatch(requestExchangeRates());

      OlafService.getExchangeRates()
      .then(
        res => dispatch(successExchangeRates(res.data)),
        res => dispatch(failureExchangeRates(res.data))
      )
    };
  }

  return {
    aaa,
    bbb,
    ccc,
    ddd,
    getExchangeRates,
  };
}

export default angular.module('OlafApp')
  .factory('OlafAction', olafActionConstructor)
  .name;

 

위와 같이 OlafAction을 만들어서, 컨트롤러에 connect 해준 뒤에 this.getExchangeRates(); 를 불러주면 아주 잘 작동한다. 내부에서 쓰인 이 angular service에서 쓰인 [request|success|failure]ExchangeRates() action creator들은 모두 syncronous한 녀석들. async한 녀석들만 생성자 함수 안에 넣어주고, 이 녀석들 안에서 sync한 녀석들을 불러주는 형식으로 개념적 모듈화를 달성해볼까 한다.

 

Leave a Reply