applyMiddleware(...middleware)
Среднеая является рекомендуемым способом расширения Redux с помощью пользовательских функций. Среднеая позволяет вам обернуть метод dispatch хранилища для вашего удобства. Ключевой особенностью среднеая является её композиционность. Несколько среднеая можно объединить вместе, при этом каждая среднеая не нуждается в знаниях о предшествующих или последующих элементах в цепочке.
Наиболее распространённый случай использования среднеая — поддержка асинхронных действий без большого объёма кода или зависимости от библиотеки, такой как Rx. Она делает это, позволяя вам отправлять асинхронные действия в дополнение к обычным действиям.
Например, redux-thunk позволяет создателям действий инвертировать управление, отправляя функции. Они будут получать dispatch в качестве аргумента и могут вызывать его асинхронно. Такие функции называются thunk. Ещё один пример среднеая — redux-promise. Он позволяет отправлять асинхронное действие Promise, а при разрешении Promise отправляет обычное действие.
Среднеая не входит в состав createStore и не является фундаментальной частью архитектуры Redux, но мы считаем её достаточно полезной, чтобы поддерживать её прямо в ядре. Таким образом, существует единый стандартный способ расширения dispatch в экосистеме, и различные среднеая могут конкурировать по выразительности и полезности.
Аргументы
-
...middleware(аргументы): Функции, которые соответствуют API Redux среднеая. Каждая среднеая получает функцииStore'sdispatchиgetStateв качестве именованных аргументов и возвращает функцию. Эта функция получит метод диспетчеризации среднеаяnext, и ожидается, что она вернёт функцию отaction, вызывающуюnext(action)с потенциально другим аргументом, или в другое время, или, возможно, вообще не вызывая её. Последняя среднеая в цепочке получит реальный метод диспетчеризации хранилищаdispatchв качестве параметраnext, тем самым завершая цепочку. Таким образом, сигнатура среднеая имеет вид({ getState, dispatch }) => next => action.
Возвращаемое значение
(Функция) Расширитель хранилища, который применяет заданные среднеая. Сигнатура расширителя хранилища имеет вид 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 { connect } from 'react-redux'
import { Component } from 'react'
class SandwichShop extends Component {
componentDidMount() {
this.props.dispatch(makeASandwichWithSecretSauce(this.props.forPerson))
}
componentDidUpdate(prevProps) {
if (prevProps.forPerson !== this.props.forPerson) {
this.props.dispatch(makeASandwichWithSecretSauce(this.props.forPerson))
}
}
render() {
return <p>{this.props.sandwiches.join('mustard')}</p>
}
}
export default connect(state => ({
sandwiches: state.sandwiches
}))(SandwichShop)Советы
Среднеая только оборачивает функцию диспетчеризации хранилища
dispatch. Технически, всё, что может сделать среднеая, вы можете сделать вручную, обернув каждый вызовdispatch, но легче управлять этим в одном месте и определять преобразования действий в масштабе всего проекта.Если вы используете другие расширители хранилища помимо
applyMiddleware, убедитесь, чтоapplyMiddlewareнаходится перед ними в цепочке композиции, поскольку среднеая потенциально асинхронна. Например, она должна находиться перед redux-devtools, иначе DevTools не увидят исходные действия, отправляемые Promise-среднеая и т.п.-
Если вы хотите применять среднеая условно, убедитесь, что импортируете её только при необходимости:
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 строк, а вложенность и композиционность — вот что делает систему среднеая мощной.
Для применения нескольких расширителей хранилища, вы можете использовать
compose().
© 2015–2021 Dan Abramov
Licensed under the MIT License.
https://redux.js.org/api/applymiddleware