이 페이지는 PageTurner AI로 번역되었습니다(베타). 프로젝트 공식 승인을 받지 않았습니다. 오류를 발견하셨나요? 문제 신고 →
applyMiddleware(...middleware)
개요
미들웨어는 Redux를 사용자 정의 기능으로 확장하는 권장 방식입니다. 미들웨어를 사용하면 스토어의 dispatch 메서드를 감싸 다양한 활용이 가능합니다. 미들웨어의 핵심 특징은 조합 가능하다는 점입니다. 여러 미들웨어를 함께 결합할 수 있으며, 각 미들웨어는 체인에서 자신 앞이나 뒤에 무엇이 오는지 알 필요가 없습니다.
applyMiddleware를 직접 호출할 필요가 없습니다. Redux Toolkit의 configureStore 메서드는 스토어에 기본 미들웨어 세트를 자동으로 추가하거나 추가할 미들웨어 목록을 받아들일 수 있습니다.
미들웨어의 가장 일반적인 사용 사례는 보일러플레이트 코드를 최소화하거나 Rx 같은 라이브러리에 의존하지 않고 비동기 액션을 지원하는 것입니다. 미들웨어는 일반 액션 외에 비동기 액션을 디스패치할 수 있게 함으로써 이를 가능하게 합니다.
예를 들어 redux-thunk는 액션 생성자가 함수를 디스패치하여 제어를 역전시킬 수 있게 합니다. 이때 함수는 dispatch를 인자로 받고 비동기적으로 호출할 수 있습니다. 이러한 함수를 _thunk_라고 부릅니다. 다른 미들웨어 예시로 redux-promise가 있습니다. 이는 Promise 비동기 액션을 디스패치할 수 있게 하며, Promise가 이행될 때 일반 액션을 디스패치합니다.
원본 Redux createStore 메서드는 미들웨어를 기본적으로 이해하지 못합니다. 해당 동작을 추가하려면 applyMiddleware로 설정해야 합니다. 그러나 Redux Toolkit의 configureStore 메서드는 기본적으로 미들웨어 지원을 자동으로 추가합니다.
인자
...middleware(인자): Redux _미들웨어 API_를 준수하는 함수들. 각 미들웨어는Store의dispatch및getState함수를 명명된 인자로 받습니다. 이후 함수를 반환하는데, 이 함수는 다음 미들웨어의 디스패치 메서드(next)를 인자로 받습니다. 그리고action을 인자로 받아next(action)을 호출하는 함수를 반환해야 하며, 이때 다른 인자나 다른 시점에 호출하거나 전혀 호출하지 않을 수도 있습니다. 체인의 마지막 미들웨어는 실제 스토어의dispatch메서드를next파라미터로 받아 체인을 종료합니다. 따라서 미들웨어 시그니처는({ getState, dispatch }) => next => action입니다.
반환값
(함수) 주어진 미들웨어를 적용하는 스토어 강화자(enhancer). 스토어 강화자 시그니처는 createStore => createStore이며, 가장 쉬운 적용 방법은 createStore()에 마지막 enhancer 인자로 전달하는 것입니다.
예제
예제: 커스텀 로거 미들웨어
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' ]
예제: Thunk 미들웨어를 사용한 비동기 액션 처리
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { thunk } from 'redux-thunk'
import * as reducers from './reducers'
const reducer = combineReducers(reducers)
// applyMiddleware supercharges createStore with middleware:
const store = createStore(reducer, applyMiddleware(thunk))
function fetchSecretSauce() {
return fetch('https://www.google.com/search?q=secret+sauce')
}
// These are the normal action creators you have seen so far.
// The actions they return can be dispatched without any middleware.
// However, they only express “facts” and not the “async flow”.
function makeASandwich(forPerson, secretSauce) {
return {
type: 'MAKE_SANDWICH',
forPerson,
secretSauce
}
}
function apologize(fromPerson, toPerson, error) {
return {
type: 'APOLOGIZE',
fromPerson,
toPerson,
error
}
}
function withdrawMoney(amount) {
return {
type: 'WITHDRAW',
amount
}
}
// Even without middleware, you can dispatch an action:
store.dispatch(withdrawMoney(100))
// But what do you do when you need to start an asynchronous action,
// such as an API call, or a router transition?
// Meet thunks.
// A thunk is a function that returns a function.
// This is a thunk.
function makeASandwichWithSecretSauce(forPerson) {
// Invert control!
// Return a function that accepts `dispatch` so we can dispatch later.
// Thunk middleware knows how to turn thunk async actions into actions.
return function (dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
)
}
}
// Thunk middleware lets me dispatch thunk async actions
// as if they were actions!
store.dispatch(makeASandwichWithSecretSauce('Me'))
// It even takes care to return the thunk's return value
// from the dispatch, so I can chain Promises as long as I return them.
store.dispatch(makeASandwichWithSecretSauce('My wife')).then(() => {
console.log('Done!')
})
// In fact I can write action creators that dispatch
// actions and async actions from other action creators,
// and I can build my control flow with Promises.
function makeSandwichesForEverybody() {
return function (dispatch, getState) {
if (!getState().sandwiches.isShopOpen) {
// You don't have to return Promises, but it's a handy convention
// so the caller can always call .then() on async dispatch result.
return Promise.resolve()
}
// We can dispatch both plain object actions and other thunks,
// which lets us compose the asynchronous actions in a single flow.
return dispatch(makeASandwichWithSecretSauce('My Grandma'))
.then(() =>
Promise.all([
dispatch(makeASandwichWithSecretSauce('Me')),
dispatch(makeASandwichWithSecretSauce('My wife'))
])
)
.then(() => dispatch(makeASandwichWithSecretSauce('Our kids')))
.then(() =>
dispatch(
getState().myMoney > 42
? withdrawMoney(42)
: apologize('Me', 'The Sandwich Shop')
)
)
}
}
// This is very useful for server side rendering, because I can wait
// until data is available, then synchronously render the app.
import { renderToString } from 'react-dom/server'
store
.dispatch(makeSandwichesForEverybody())
.then(() => response.send(renderToString(<MyApp store={store} />)))
// I can also dispatch a thunk async action from a component
// any time its props change to load the missing data.
import React from 'react'
import { connect } from 'react-redux'
function SandwichShop(props) {
const { dispatch, forPerson } = props
useEffect(() => {
dispatch(makeASandwichWithSecretSauce(forPerson))
}, [forPerson])
return <p>{this.props.sandwiches.join('mustard')}</p>
}
export default connect(state => ({
sandwiches: state.sandwiches
}))(SandwichShop)
활용 팁
-
미들웨어는 스토어의
dispatch함수만 감쌉니다. 기술적으로 미들웨어가 할 수 있는 모든 작업은 매번dispatch호출을 수동으로 감싸 구현할 수 있지만, 단일 장소에서 관리하고 전체 프로젝트 규모로 액션 변환을 정의하는 것이 더 쉽습니다. -
applyMiddleware외에 다른 스토어 강화자를 사용하는 경우, 미들웨어가 비동기적일 수 있으므로 컴포지션 체인에서applyMiddleware를 앞에 배치해야 합니다. 예를 들어 redux-devtools보다 앞에 위치시켜야 합니다. 그렇지 않으면 DevTools가 Promise 미들웨어 등에서 발생한 원시 액션을 볼 수 없기 때문입니다. -
미들웨어를 조건부로 적용하려는 경우, 필요한 경우에만 import하도록 합니다:
let middleware = [a, b]
if (process.env.NODE_ENV !== 'production') {
const c = require('some-debug-middleware')
const d = require('another-debug-middleware')
middleware = [...middleware, c, d]
}
const store = createStore(
reducer,
preloadedState,
applyMiddleware(...middleware)
)이렇게 하면 번들링 도구가 불필요한 모듈을 제거하기 쉬워지고 빌드 크기가 줄어듭니다.
-
applyMiddleware자체가 무엇인지 궁금한 적이 있나요? 이는 미들웨어 자체보다 더 강력한 확장 메커니즘입니다. 실제로applyMiddleware는 스토어 인핸서라고 불리는 가장 강력한 Redux 확장 메커니즘의 대표적인 예입니다. 스토어 인핸서를 직접 작성할 일은 거의 없을 것입니다. 다른 스토어 인핸서 예로는 redux-devtools가 있습니다. 미들웨어는 스토어 인핸서보다 기능이 제한적이지만 작성하기는 더 쉽습니다. -
미들웨어는 실제보다 훨씬 복잡하게 들릴 수 있습니다. 미들웨어를 진정으로 이해하는 유일한 방법은 기존 미들웨어가 어떻게 동작하는지 살펴보고 직접 작성해보는 것입니다. 함수 중첩이 부담스러울 수 있지만, 대부분의 미들웨어는 실제로 10줄 내외이며, 이러한 중첩과 조합 가능성이 미들웨어 시스템을 강력하게 만듭니다.
-
여러 개의 스토어 인핸서(store enhancers)를 적용하려면
compose()를 사용할 수 있습니다.