applyMiddleware(...middleware)
Middleware — это рекомендуемый способ расширения Redux с помощью пользовательской функциональности. Middleware позволяет обернуть метод dispatch хранилища для получения удовольствия и прибыли. Ключевой особенностью middleware является его композируемость. Несколько middleware могут быть объединены вместе, где каждое middleware не нуждается в знании того, что находится перед или после него в цепочке.
Наиболее распространённый случай использования middleware — поддержка асинхронных действий без большого количества шаблонного кода или зависимости от библиотеки, такой как Rx. Это делается путём разрешения отправлять асинхронные действия в дополнение к обычным действиям.
Например, redux-thunk позволяет создателям действий инвертировать управление, отправляя функции. Они будут получать dispatch в качестве аргумента и могут вызывать его асинхронно. Такие функции называются thunk. Ещё один пример middleware — redux-promise. Он позволяет отправлять асинхронное действие Promise, и отправляет обычное действие, когда Promise разрешается.
Middleware не встроено в createStore и не является фундаментальной частью архитектуры Redux, но мы считаем его достаточно полезным, чтобы поддержать его прямо в ядре. Таким образом, существует единый стандартный способ расширения dispatch в экосистеме, и различные middleware могут конкурировать по выразительности и полезности.
Аргументы
-
...middleware(аргументы): Функции, которые соответствуют API Redux middleware. Каждое middleware получаетStore’sdispatchиgetStateфункции в качестве именованных аргументов и возвращает функцию. Эта функция будет получатьnextметод отправки middleware и должна вернуть функцию отactionвызоваnext(action)с потенциально другим аргументом, или в другое время, или, возможно, вообще не вызывать его. Последнее middleware в цепочке получит реальный методdispatchхранилища в качествеnextпараметра, тем самым завершая цепочку. Таким образом, подпись middleware выглядит как({ getState, dispatch }) => next => action.
Возвращаемое значение
(Функция) Улучшитель хранилища, который применяет указанные middleware. Подпись улучшителя хранилища — createStore => createStore', но самый простой способ его применить — передать его в createStore() в качестве последнего enhancer аргумента.
Пример: пользовательское middleware для регистрации
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.
let 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
}
}
let 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' ]
Пример: использование middleware thunk для асинхронных действий
import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import * as reducers from './reducers'
let reducer = combineReducers(reducers)
// applyMiddleware supercharges createStore with middleware:
let 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 { connect } from 'react-redux'
import { Component } from 'react'
class SandwichShop extends Component {
componentDidMount() {
this.props.dispatch(makeASandwichWithSecretSauce(this.props.forPerson))
}
componentWillReceiveProps(nextProps) {
if (nextProps.forPerson !== this.props.forPerson) {
this.props.dispatch(makeASandwichWithSecretSauce(nextProps.forPerson))
}
}
render() {
return <p>{this.props.sandwiches.join('mustard')}</p>
}
}
export default connect(state => ({
sandwiches: state.sandwiches
}))(SandwichShop)
Советы
-
Middleware только оборачивает функцию отправки
dispatchхранилища. Технически, всё, что может сделать middleware, можно сделать вручную, обернув каждый вызовdispatch, но проще управлять этим в одном месте и определять преобразования действий в масштабе всего проекта. -
Если вы используете другие улучшители хранилища помимо
applyMiddleware, убедитесь, что вы помещаетеapplyMiddlewareперед ними в цепочке композиции, так как middleware потенциально асинхронно. Например, оно должно идти перед redux-devtools, так как в противном случае DevTools не увидят исходные действия, испускаемые middleware Promise и т.п. -
Если вы хотите условно применить middleware, убедитесь, что вы импортируете его только тогда, когда он нужен:
let middleware = [a, b] if (process.env.NODE_ENV !== 'production') { let c = require('some-debug-middleware') let d = require('another-debug-middleware') middleware = [...middleware, c, d] } const store = createStore( reducer, preloadedState, applyMiddleware(...middleware) )Это упрощает инструментам сборки исключать ненужные модули и уменьшает размер ваших сборок.
-
Вы когда-нибудь задумывались, что такое само
applyMiddleware? Это должен быть механизм расширения, более мощный, чем само middleware. Действительно,applyMiddleware— это пример наиболее мощного механизма расширения Redux, называемого улучшителями хранилища. Вряд ли вам когда-либо захочется написать собственный улучшитель хранилища. Ещё одним примером улучшителя хранилища является redux-devtools. Middleware менее мощный, чем улучшитель хранилища, но его легче написать. -
Middleware звучит гораздо сложнее, чем на самом деле. Единственный способ по-настоящему понять middleware — посмотреть, как работает существующее middleware, и попробовать написать своё. Вложенность функций может быть пугающей, но большинство middleware, которые вы найдёте, на самом деле представляют собой 10-строчные фрагменты кода, а вложенность и композируемость — то, что делает систему middleware мощной.
-
Для применения нескольких улучшителей хранилища вы можете использовать
compose().
© 2015–2017 Dan Abramov
Licensed under the MIT License.
http://redux.js.org/docs/api/applyMiddleware.html