ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Redux-thunk 코드 살펴보기
    Web/Redux 2021. 5. 9. 05:31
    반응형

    최근에 코치님께서 Redux-Thunk의 코드가 엄청 짧고 강렬하다고 알려주셔서

    코드를 봤는데 뭔지 이해가 하나도 안 가더라. 

    그래서 코치님께서 간단한 예제를 만들어주셨는데, 보면서 고민하다보니 어렴풋이 뭔가 떠오를 듯 하다.

    잊어버리지 않기 위해 적어두려고 호다닥 포스팅.


    // redux-thunk 코드
    function createThunkMiddleware(extraArgument) {
      return ({ dispatch, getState }) => (next) => (action) => {
        if (typeof action === 'function') { // 2번
          return action(dispatch, getState, extraArgument);
        }
    
        return next(action);
      };
    }
    // ----------------
    const ReduxThunk = createThunkMiddleware();
    
    const someAction = { type: SOME_ACTION };
    
    // 코치님께서 만드신 미들웨어(간소화)
    const beholder = (store) => (next) => (action) => {
      if (action.type === SOME_ACTION) {
        return next({
          type: SOME_ACTION,
          message: '메세지입니당',
        });
      }
    
      return next(action);
    };
    
    const store = createStore(reducer, applyMiddleware(ReduxThunk, beholder)); // 1번
    
    // 코치님께서 만드신 비동기 로직
    const getDog = () => (dispatch, getState) => {
      fetch('https://dog.ceo/api/breeds/image/random/' + getState().count)
        .then((response) => response.json())
        .then(({ message, status }) => {
          const [firstDogImageUrl] = message;
    
          dispatch({ type: 'GET_DOG_SUCCESS', payload: firstDogImageUrl });
        })
        .catch((e) => dispatch({ type: 'GET_DOG_ERROR', error: e }));
    };
    
    // 3번
    document.querySelector('.some_button').addEventListener('click', function () {
      store.dispatch(getDog());
    });
    
    document.querySelector('.another_button').addEventListener('click', function () {
      store.dispatch(someAction);
    });

    store 생성 부분(1번)을 보면, createStore에서 reducer를 넘겨줌과 동시에

    applyMiddleware(ReduxThunk, beholder) 를 넘겨주는 것을 볼 수 있다.

     

    applyMiddleware는 Redux 에서 제공하는, 미들웨어를 적용할 수 있는 enhancer.

    enhancer는 저장소에 부가적인 기능을 추가하기 위해 사용되는 함수 정도로 생각하면 된다.

    (Redux에서 기본으로 제공하는 enhancer는 applyMiddleware가 유일)

     

    '미들웨어'는 action이 reducer에 도달하기 전에 끼어들어서 애플리케이션 로직을 수행한다.

    즉, store에 action이 dispatch 될 때마다 항상 미들웨어가 action을 가로챈다고 보면 됨.

    결과적으로, 리덕스의 동작은 action → middleware → reducer → store 순인 것.

     

    미들웨어를 적용하고 싶으면, 그 미들웨어를 applyMiddleware의 인자로 넣어주기만 하면 된다.

    만약 미들웨어를 여러 개 적용하고 싶으면, applyMiddleware(middleware1, middleware2, ...)

    요딴 식으로 그냥 여러 개 넣어주면 된다.


    Redux의 모든 미들웨어들은 다음과 같은 형태를 가진다.

    function middleware({ dispatch, getState }) {
      return function wrappedDispatch(next) {
        return function dispatchToSomething(action) {
          // do something...
          return next(action);
        }
      }
    }

    리덕스의 모든 미들웨어들은 store의 dispatch와 getState 함수를 인자로 받는다.

    (정확하게는 store를 인자로 받아서 dispatch와 getState를 destructuring 하여 받아온다)

    그리고 함수(wrappedDispatch 함수)를 return하는데, 해당 함수의 인자로 next(체이닝 함수)가 들어오고,

    wrappedDispatch 함수는 또 action을 인자로 받는 함수(dispatchToSomething 함수)를 return 한다.

    dispatchToSomething 함수의 내부에서는 next(action)이 호출될 수 있는데,

    특정 미들웨어의 내부에서 next(action)이 호출되면, 다음 미들웨어가 있는 경우에는

    다음 미들웨어로 넘어가고 그게 아니면 그냥 reducer로 action을 바로 전달(dispatch)하게 된다.

     

    그냥 다 때려치우고, 간단하게 '리덕스 미들웨어는 store(dispatch, getState), next, action을 인자로 받는다.'

    (어차피 '커링'이므로 저 중첩된 함수들은 다 무시하고, 그냥 인자를 3개 받는 하나의 함수라고 생각해도 될 것 같다)

    그리고 'next(action)이 호출되면 다음 미들웨어 혹은 reducer로 넘어간다' 라는 정도만 알아도 될 듯.

     

    화살표 함수를 사용해서 요딴 식으로 줄이기도 한다.

    const middleware = ({ dispatch, getState }) => (next) => (action) => {
      // do something...
      return next(action);
    }
    

    맨 위의 코드에서 보면, createThunkMiddleware 내부 함수와 beholder가 미들웨어라는 것을 확인할 수 있을 것.

    createThunkMiddleware는 redux-thunk에서 제공하는 미들웨어의 wapper 함수이고,

    beholder는 코치님께서 예시로 작성한 코드.

    store를 생성할 때(1번) 보면, createThunkMiddleware를 실행한 결과인 미들웨어 'ReduxThunk'와

    또 다른 미들웨어인 beholder를 미들웨어로 넘겨주고 있다.

    이렇게 되면, ReduxThunk의 내부에서 next(action)이 실행됐을 때 beholder로 넘어가게 된다.

     

    ReduxThunk의 내부(2번)를 보면, action의 타입이 function이면

    해당 action 함수를 (dispatch, getState 인자와 함께) 실행하고, 

    그렇지 않으면 다음 미들웨어(여기서는 beholder, 만약 존재하지 않는다면 바로 reducer)로 action을 넘겨주고 있다.

    (applyMiddleware에서 ReduxThunk 다음에 beholder가 들어갔으니 ReduxThunk의 next는 beholder인 것)


    redux-thunk를 이용해서 비동기 로직을 실행하려면, 비동기 로직을 함수로 만들어 action으로 넣어줘야한다.

    리덕스를 이용해 action을 dispatch하면, 해당 action이 reducer에 도착하기 전에

    ReduxThunk 미들웨어가 그걸 가로채서, action이 function인지 아닌지 검사한다.

    만약 action이 function이면 해당 function에 dispatch와 getState를 인자로 넣어서 실행해주고

    function이 아니면 next(action)을 그냥 호출해준다.

     

    풀어서 설명하면,

    store에 action이 dispatch될 때마다, 항상 미들웨어가 그 action을 가로채간다.

    만약 그 action이 그냥 plain javascript object평소대로 그 action을 reducer에 전달해주고,

    그 action이 함수(비동기 로직)라면 redux는 그걸 처리하지 못하기 때문에

    '개발자 니가 알아서 처리해라!' 하고 개발자에게 해당 action의 처리를 넘겨주는 것.


    코드의 맨 아래(3번)를 보면 some_button 버튼을 클릭했을 때 getDog()(비동기 함수)를

    action으로 dispatch 하도록 해줬다.

    그럼, 이제 해당 버튼을 클릭했을 때 어떤 일이 일어나는지를 순차적으로 나열해보도록 하자.

     

    1. 버튼을 클릭한다.

    2. getDog()가 action으로 dispatch 된다.

    3. ReduxThunk 미들웨어가 해당 action을 가로채서 function인지 아닌지 검사한다.

    4. getDog 함수는, 실행된 채로 인자로 넘겨졌기 때문에 결과적으로 '(dispatch와 getState를 인자로 받는) getDog의 내부함수'가 action으로 dispatch되는 것과 같다. 

    5. 해당 action은 function이기 때문에, dispatch, getState를 인자로 받아서 해당 action이 실행된다.

    (extraArgument는 안 넣어줬으니까 패스)

    6. 해당 action 내부에 있는 비동기 로직이 실행된다.

     

    이번엔 another_button을 클릭했다고 가정해보자.

     

    1. 버튼을 클릭한다.

    2. someAction이 action으로 dispatch 된다.

    3. ReduxThunk 미들웨어가 해당 action을 가로채서 function인지 아닌지 검사한다.

    4. someAction은 plain javascript object이므로 next(action)이 실행된다.

    5. 다음 미들웨어인 beholder가 실행된다.

    6. action의 type이 SOME_ACTION 이므로 next({ type: SOME_ACTION, message: '메세지입니당' }) 이 실행된다.

    7. 다음 미들웨어가 존재하지 않으므로 { type: SOME_ACTION, message: '메세지입니당' }이 reducer로 전달된다.

    8. state가 변경된다.

     

    reducer는 요 코드를 보는 데에 있어서 별로 안 중요해서 그냥 생략.

    뭔가 reducer가 있겠거니. 실행되겠거니. 그냥 그러려니 하면 될 듯.

    근데 이렇게 이해한 게 맞는 건가? ㅎㅎ;

     

     

     

    참고

     

    reduxjs/redux-thunk

    Thunk middleware for Redux. Contribute to reduxjs/redux-thunk development by creating an account on GitHub.

    github.com

    redux.js.org/api/applymiddleware/

     

    Redux 분석하기 : NHN Cloud Meetup

    Redux 분석하기

    meetup.toast.com

     

    리덕스 미들웨어, 그리고 비동기 작업 (외부데이터 연동) | VELOPERT.LOG

    이 문서는 패스트캠퍼스 오프라인 강의를 위해서 준비된 문서이기에, 일부 내용들은 설명이 생략되어있을 수도 있습니다. 이번 강의에서 다룰 주제는 리덕스 미들웨어를 사용하여 외부 데이터

    velopert.com

     

    반응형

    댓글

Designed by Tistory.