Эта страница переведена PageTurner AI (бета). Не одобрена официально проектом. Нашли ошибку? Сообщить о проблеме →
applyMiddleware(...middleware)
Обзор
Middleware — рекомендуемый способ расширения функциональности Redux. Он позволяет обернуть метод dispatch хранилища для гибкой настройки. Ключевая особенность middleware — композируемость. Несколько middleware можно комбинировать в цепочку, где каждый элемент не требует знания о предыдущих или последующих звеньях.
Вам не следует вызывать applyMiddleware напрямую. Метод configureStore из Redux Toolkit автоматически добавляет стандартный набор middleware в хранилище или принимает список middleware для добавления.
Наиболее частый сценарий использования middleware — поддержка асинхронных действий без избыточного кода или зависимостей вроде Rx. Это достигается возможностью диспетчеризации асинхронных действий наравне с обычными.
Например, redux-thunk позволяет создателям действий инвертировать контроль, диспетчеризуя функции. Они получают dispatch как аргумент и могут вызывать его асинхронно. Такие функции называются thunks. Другой пример — redux-promise, который позволяет диспетчеризовать Promise как асинхронное действие и отправляет обычное действие при его разрешении.
Стандартный метод Redux createStore не поддерживает middleware "из коробки" — для этого требуется настройка через applyMiddleware. Однако метод configureStore в Redux Toolkit по умолчанию включает поддержку middleware.
Аргументы
...middleware(аргументы): Функции, соответствующие API middleware Redux. Каждый middleware получает методыStoredispatchиgetStateкак именованные аргументы и возвращает функцию. Этой функции передаётся методnextследующего middleware, и она должна вернуть функцию отaction, вызывающуюnext(action)с возможным изменением аргумента, временного промежутка или вообще без вызова. Последний middleware в цепочке получит настоящийdispatchхранилища как параметрnext, завершая цепочку. Сигнатура middleware:({ getState, dispatch }) => next => action.
Возвращаемое значение
(Функция) Усилитель хранилища (store enhancer), применяющий указанные 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.
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 middleware для асинхронных действий
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)
Советы
-
Middleware оборачивает только метод
dispatchхранилища. Технически, всё что делает middleware, можно реализовать вручную через обёртки для каждого вызоваdispatch, но централизованное управление упрощает масштабирование преобразований действий на весь проект. -
При использовании других усилителей хранилища вместе с
applyMiddleware, размещайтеapplyMiddlewareперед ними в цепочке композиции из-за асинхронной природы middleware. Например, он должен идти до redux-devtools, иначе DevTools не увидит исходные действия от Promise middleware. -
Если вы хотите применять middleware условно, убедитесь, что импортируете его только при необходимости:
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? Это должен быть механизм расширения более мощный, чем middleware. Действительно,applyMiddleware— пример самого мощного механизма расширения Redux, называемого усилителем хранилища (store enhancer). Маловероятно, что вам когда-либо понадобится писать усилитель самостоятельно. Другой пример усилителя — redux-devtools. Middleware менее мощны, чем усилители, но их проще создавать. -
Middleware кажутся сложнее, чем есть на самом деле. Единственный способ по-настоящему понять middleware — изучить работу существующих реализаций и попробовать создать свою. Вложенность функций может пугать, но большинство middleware на самом деле занимают около 10 строк, а их вложенность и композируемость — именно то, что делает систему middleware мощной.
-
Для применения нескольких усилителей хранилища используйте
compose().