メインコンテンツへスキップ
非公式ベータ版翻訳

このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →

applyMiddleware(...middleware)

概要

ミドルウェアはReduxをカスタム機能で拡張する推奨方法です。ミドルウェアを使うとストアのdispatchメソッドをラップでき、強力な機能を実現できます。ミドルウェアの重要な特徴は構成可能性です。複数のミドルウェアを組み合わせることができ、各ミドルウェアはチェーン内の前後にある処理を意識する必要がありません。

警告

applyMiddlewareを直接呼び出す必要はありません。Redux ToolkitのconfigureStoreメソッドはデフォルトでミドルウェアをストアに追加します。また、追加するミドルウェアのリストを受け取ることも可能です。

ミドルウェアの最も一般的な使用例は、Rxのようなライブラリに依存せず、定型コードを最小限に抑えて非同期アクションをサポートすることです。これは通常のアクションに加えて非同期アクションをディスパッチできるようにすることで実現します。

例えば、redux-thunkはアクションクリエーターが関数をディスパッチすることで制御を逆転させます。関数はdispatchを引数として受け取り、非同期で呼び出すことができます。このような関数は_thunk_と呼ばれます。別の例としてredux-promisePromiseの非同期アクションをディスパッチでき、Promiseが解決された時に通常のアクションをディスパッチします。

元々のReduxのcreateStoreメソッドはミドルウェアをデフォルトで認識しません。この挙動を追加するにはapplyMiddlewareで設定する必要があります。ただし、Redux ToolkitのconfigureStoreメソッドはデフォルトでミドルウェアサポートを自動的に追加します。

引数

  • ...middleware (引数): Reduxの_ミドルウェアAPI_に準拠した関数。各ミドルウェアはStoredispatchおよびgetState関数を名前付き引数として受け取り、関数を返します。返される関数にはnextミドルウェアのdispatchメソッドが渡され、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 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ミドルウェアなどから発行される生のアクションを認識できません。

  • ミドルウェアを条件付きで適用したい場合、必要な時だけインポートするようにしてください:

    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() を使用できます。