redux middleware

김민석·2021년 6월 1일
0

Redux

목록 보기
3/5
import { createWrapper } from "next-redux-wrapper";
import { applyMiddleware, compose, createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension"; //chrome devtools
import rootReducer from "../reducers/index";

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

const wrapper = createWrapper(configureStore, {
  debug: process.env.Node_ENV === "developoment",
});

export default wrapper;

스토어에 미들웨어를 적용하는 부분을 가져왔다.
미들웨어에 대해서 좀 더 찾아보는 것은 처음이라 기록해본다.

Compose

  • 여러 enhancer들을 적용하기 위해 사용할 수 있는 redux가 제공하는 유틸리티
import { createStore, combineReducers, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import DevTools from './containers/DevTools'
import reducer from '../reducers/index'

const store = createStore(
  reducer,
  compose(applyMiddleware(thunk), DevTools.instrument())
)

compose는 우에서 좌를 향하여 함수를 조합해 나간다.

middleware

dispatch 함수를 감싸는 고차함수

import { createStore, applyMiddleware } from 'redux'
import todos from './reducers'

function logger({ getState }) {
  return next => action => {
    console.log('will dispatch', action)

    // Call the next dispatch method in the middleware chain.
    const returnValue = next(action)

    console.log('state after dispatch', getState())

    // This will likely be the action itself, unless
    // a middleware further in chain changed it.
    return returnValue
  }
}

const store = createStore(todos, ['Use Redux'], applyMiddleware(logger))

store.dispatch({
  type: 'ADD_TODO',
  text: 'Understand the middleware'
})
// (These lines will be logged by the middleware:)
// will dispatch: { type: 'ADD_TODO', text: 'Understand the middleware' }
// state after dispatch: [ 'Use Redux', 'Understand the middleware' `

단순한 미들웨어의 내부는 이런 식으로 짜여있다고 한다.
위 함수를 더욱 단순화 시키면 아래와 같다.

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

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

즉 미들웨어는 getstate,dispatch 두 인자를 받아 다음 미들웨어나 (다음 미들웨어가 없다면) 리듀서로 전달하는 함수를 return 하는 것이다.

next는 다음 middleware의 dispatch method라고 한다.

applyMiddleware

createStore를 감싸는 고차함수

곱씹어보기.

function applyMiddleware(...middlewares) {
  // applyMiddleware는 기존 createStore의 고차 함수를 반환한다.
  return (createStore) => (reducer, preloadedState, enhancer) => {
    const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch
    let chain = []

    // 미들웨어들에게 인자로 전달되는 객체이다.
    // dispatch가 단순히 store.dispatch의 참조를 전단하는 것이 아니라,
    // 함수를 한번 더 감싸 사용하는 점을 기억하자.
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }

    // 미들웨어들이 반환하는 체인 함수들(= wrapDispatch)을 가져온다.
    chain = middlewares.map(middleware => middleware(middlewareAPI))

    // 미들웨어가 반환하는 체인 함수들을 중첩시킨 후 새로운 dispatch 함수를 만든다.
    dispatch = compose(...chain)(store.dispatch)
    // 즉 위에서 만들어진 각 wrapDispatch 함수들을 compose로 중첩 시켜 고차함수화 하는 부분이다.

    // applyMiddleware를 통해 반환된 createStore 고차 함수는
    //  기존 스토어와 동일한 API, 그리고 새로 만들어진 dispatch 함수를 반환한다.
    return {
      ...store,
      dispatch
    }
  }
}

참고 :


좀 더 이해하기

참고

아래는 미들웨어의 단순한 예시다. (이해를 위해 arrow syntax를 사용하지 않음)

function logger(store) {
  return function wrapDispatchToAddLogging(next) {
    return function dispatchAndLog(action) {
      console.log('dispatching', action)
      let result = next(action)
      console.log('next state', store.getState())
      return result
    }
  }
}

아래의 applyMiddleware 실제 코드와는 조금 다르게 이해를 위한 코드다.(redux 홈페이지에서 가져왔다.)

// Warning: Naïve implementation!
// That's *not* Redux API.
function applyMiddleware(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()
  let dispatch = store.dispatch
  middlewares.forEach(middleware => (dispatch = middleware(store)(dispatch)))
  return Object.assign({}, store, { dispatch })
}

보면 최초의 dispatch는 store의 method지만, 그 이후는

  1. 이전 middleware에 store을 넣고
  2. 거기서 나온 함수에 dispatch를 인자로 넣은 결과인 함수를
  3. dispatch에 재할당한다.

따라서, 아래와 같이 정리할 수 있겠다.

applyMiddleware(logger, crashReporter)처럼 두 가지 이상의 middleware가 적용되어 있다면

const crashReporter = store => next => action => {
  try {
    return next(action)
  } catch (err) {
    console.error('Caught an exception!', err)
    Raven.captureException(err, {
      extra: {
        action,
        state: store.getState()
      }
    })
    throw err
  }
}
  1. crashReporter에 대해
    carshReporter(stroe)(store.disaptch)가 실행됨.
  1. 그 결과를 dispatch에 할당 (dispatch = carshReporter(stroe)(store.disaptch))

    할당되는 함수는 정확하게는 아래의 부분 (action 객체가 들어오기전)

action => {
  try {
    return next(action)
  } catch (err) {
    console.error('Caught an exception!', err)
    Raven.captureException(err, {
      extra: {
        action,
        state: store.getState()
      }
    })
    throw err
  }
}
  1. logger 미들웨어는 위의 함수를 next 위치에 인자로 받아서 그것을 자신의 함수로 덮어씌운다.
    (합수를 합성하는 것을 생각하면 되겠다.)
  1. 따라서 최종적으로 store의 dispatch는 모양이 최초와 달라지게 된다.
    형태는 가장 오른쪽 미들웨어가 action 객체를 인자로 하는 store.dispatch를 감싼 함수를 -> 그 다음 왼쪽 middleware가 action 객체를 인자로 하는 오른쪽 미들웨어가 return한 함수를 감싸는 형태가 나올 것이다.

0개의 댓글