Front end/React

React -redux thunk, redux saga

Ideveloper2 2018. 3. 7. 15:01

React -redux thunk, redux saga


>reudx-saga에 들어가기에 앞서, 


|redux-thunk란?


redux-saga를 적용시키기 전에 redux-thunk를 먼저 사용해봤다면, 왜 redux-saga를 써야하는지 더 쉽게 파악할 수 있으므로, 시간이 있다면 간단히라도 학습하길 바란다. 아래에서 간단히 소개하겠다.


:가장 간단히 설명하자면, 이 미들웨어는 객체 대신 함수를 생성하는 액션 생성함수를 작성 할 수 있게 해준다. 리덕스에서는 기본적으로는 액션 객체를 디스패치한다.

function increment() {
 return {
   type: INCREMENT_COUNTER
};
}

예전에는 액션생성함수에서 위와 같은 type을 가지는 객체를  생성해 줬었다면


function increment() {
 return {
   type: INCREMENT_COUNTER
};
}

function incrementAsync() {
 return dispatch => { // dispatch 를 파라미터로 가지는 함수를 리턴합니다.
   setTimeout(() => {
     // 1 초뒤 dispatch 합니다
     dispatch(increment());
  }, 1000);
};
}
redux-thunk를 쓰면 위의 incrementAsync 같은 함수를 생성하는 액션 생성함수를 만들수 있게 됩니다.

| redux -thunk로 하는 비동기처리

만약에, 리턴하는 함수에서 dispatch, getState 를 파라미터로 받게 한다면 아래와 같이 스토어의 상태에도 접근 할 수있다. 따라서, 현재의 스토어 상태의 값에 따라 액션이 dispatch 될 지 무시될지 정해줄 수 있다.
function incrementIfOdd() {
 return (dispatch, getState) => {
   const { counter } = getState();

   if (counter % 2 === 0) {
     return;
  }

   dispatch(increment());
};
}
| 요약

간단하게 정리를 하자면 redux-thunk 는 일반 액션 생성자에 날개를 달아줍니다. 보통의 액션생성자는 그냥 하나의 액션객체를 생성 할 뿐이지만 redux-thunk 를 통해 만든 액션생성자는 그 내부에서 여러가지 작업을 할 수 있습니다. 이 곳에서 네트워크 요청을 해도 무방하죠. 또한, 이 안에서 액션을 여러번 디스패치 할 수도 있습니다.



-velopert님 블로그 참고


> redux-saga를 쓰는 이유


요약해서 말하면 비동기처리를 위해서이다.


리덕스 만으로는, 특정 action을 dispatch 받는걸 기다렸다가 dispatch 받았을때, 다른 action을 dispatch하는 동기적인 액션의 dispatch 상황에 대한 것이나 (쉽게 말하면 순서대로 액션을 처리), 디스패치된 액션들을 모두 모아서 병렬적으로 task를 수행한다던지, 외부라이브러리를 사용하고 호출하는것이 어려운데 리덕스 saga를 사용하면 이러한 점들이 해결된다.


그리고 리덕스 사가는 middleware 인데,



"미들웨어는, 액션이 디스패치(dispatch) 되어서 리듀서에서 이를 처리하기전에 사전에 지정된 작업들을 설정합니다. 미들웨어를 액션과 리듀서 사이의 중간자라고 이해하시면 되겠습니다."

라고 velopert님이 간결하게 표현해 주셨다. 이를 리덕스 saga와 연결시켜 보면, 액션을 비동기적으로  디스패치 했을때 미들웨어인 리덕스 사가가 디스패치 액션들을 잡아서, 우리가 입맛에 맞게 리덕스 사가를 이용해 어떤 행동을 취할지 정해주면 이를 바탕으로 reducer에 전달하게 되는것이다.


redux-saga의 effect


위에서 입맛에 맞게 리덕스 사가를 이용해 준다 하였는데, 이때 사용되는 개념이 redux-saga의 effect이다.

    redux-saga는 "Task"라는 개념을 Redux로 가져오기위한 지원 라이브러리입니다. 여기서 말하는 Task란 일의 절차와 같은 독립적인 실행 단위로써, 각각 평행적으로 작동합니다. redux-saga는 이 Task의 실행환경을 제공합니다. 더불어 비동기처리를 Task로써 기술하기 위한 준비물인 "Effect"와 비동기처리를 동기적으로 표현하는 방법을 제공하고 있습니다. Effect란 Task를 기술하기 위한 커맨드(명령, Primitive)와 같은 것으로, 예를들면 다음과 같은 것들이 있습니다.

    https://github.com/reactkr/learn-react-in-korean/blob/master/translated/deal-with-async-process-by-redux-saga.md 

    참고

    참고한 글에서 위와 같이 redux-saga에 대해 자세히 말해주고있다. 나도 처음 리덕스 사가를 접했을때, 정말 막막했으니 이해가 안가는게 당연하다. 일단 느낌만 알고 가자. 이 상태에서 위에서 말한 effect들의 종류에 대해서 살펴보자.아래 첨부한 이미지를 참고하면 많은 도움이 된다.

    • select: State로부터 필요한 데이터를 꺼낸다.
    • put: Action을 dispatch한다.
    • take: Action을 기다린다. 이벤트의 발생을 기다린다.
    • call: Promise의 완료를 기다린다.
    • fork: 다른 Task를 시작한다.
    • join: 다른 Task의 종료를 기다린다.

    위에서 간략하게 설명되어 있는 각각의 effect들에 대해서 이해하기 쉽게 내가 이해한것을 바탕으로 다시 설명해보겠다. 물론 아래의 그림을 참고하는게 좋다.

    |Select

    state (여기서 말하는 state는 reudx의 state이다.)를 가져오고 싶을때 쓰는 effect이다. 어떨때 사용하나면 saga를 작성하다보면 state를 가져와서 그 state 데이터를 바탕으로, 그 state 데이터들로 액션을 다시 dispatch 해주거나, 다른 행동들을 취해줄 수있다.

    ex1) select한  state 데이터를 로컬스토리지에 저장하는 예제
        const cart = yield select(selectCart);
       localStorage.setItem('cartCache', JSON.stringify(cart));

    ex2) select한 state 데이터를 다시 디스패치해주는 예제

       const state = yield select();
       const nextPage = state.reviewData.meta.reviewPage;
       console.log(nextPage);
       yield put(setPage(nextPage));

    위는 state에서 리뷰페이지를 받아와 page를 set해주는 예제이다. put 에 대해서는 뒤에 나올예정이다. 간단히 알려주자면 action을 디스패치 하는것이다.

    |Put

    : action을 디스패치 해준다는 effect이다.

    |Take

    비동기적으로 액션이 디스패치 되는것을 기다리고 있는 effect이다.
    ex) 카트에 추가되거나,카트에 삭제되거나,카트가 클리어되는것을 기다렸다가 로컬스토리지 set 예제

      yield take([ADD_CART, REMOVE_CART, CLEAR_CART]);
       const cart = yield select(selectCart);
       localStorage.setItem('cartCache', JSON.stringify(cart));


    위는 select예제에서 봤던 것인데, 위에 추가된 줄을 확인해보면, ADD_CART, REMOVE_CART, CLEAR_CART 중 디스패치된 액션이 있으면 state를 select해서 로컬스토리지에 set하는것이다.
    |Call
    :call 은 api요청을 할때 많이 쓰는 efect이다. api요청을 해서 promise가 resolve되면 그 데이터를 받아오게 되는것이다.

    const addresses = yield call(getAddressInfo, {
         query,
      });

    yield put(loadAddressesSuccess(addresses));  

    위는 address 데이터를 api를 요청해 받아오고, 그 주소를 dispatch하는 예제이다.

    |Fork

    :다른 task를 실행하는것이다.
    ex)

    export function* initDeliveryAt() {
     while (true) {
       yield take(LOAD_AVAILABLE_DATES);
       yield fork(initAvailableDatesFlow);
    }
    }

    ...

    export function* initAvailableDatesFlow() {
     ...
    }

    위와 같이 다른 task를 진행할때 fork를 쓰는것을 알수있다.




    병렬 이펙트
    : saga 를 사용하다 보면, 병렬적으로 이펙트를 줘야 할때 도 있다.

    비동기적인 액션들을 동기적으로 사용하기 위해 ES6 GENERATOR 개념을 도입해서 이펙트들을 관리한다. 이외에도 병렬적으로 이펙트들을 처리 할때도 있다. 아래를 살펴보자.


    빠르게 훑는 generator

    • iterable
    • 비동기든 동기든 간에 yield 구문으로 순차적 처리가 가능하다.



    function* fetchAll() {
     yield [
       call(fetchResource, 'users'),     // task1
       call(fetchResource, 'comments'),  // task2,
       call(delay, 1000)
    ]
    }

    function* main() {
     yield call(fetchAll)
    }

    병렬 이펙트 1

    export default function* metaRoot() {
     yield all([
       watchLoadUserSuccess(),
       watchSignOut(),
       watchSetServiceType(),
       watchPopup(),
       initPopup(),
    ]);
    }

    병렬 이펙트 2