Spec-Zone.ru › Redux 3

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’s dispatch и 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API