メインコンテンツへスキップ

初期状態の初期化

アプリケーションの状態を初期化するには、主に2つの方法があります。createStoreメソッドは、2番目の引数としてオプションのpreloadedState値を受け入れることができます。リデューサーは、undefinedである着信状態引数を探し、デフォルトとして使用したい値を返すことで、初期値を指定することもできます。これは、リデューサー内での明示的なチェック、またはデフォルト引数値構文 function myReducer(state = someDefaultValue, action) を使用して行うことができます。

これら2つのアプローチがどのように相互作用するかは、必ずしも明確ではありません。幸いなことに、プロセスはいくつかの予測可能なルールに従います。各要素がどのように組み合わされるかを示します。

概要

combineReducers()または同様の手動コードがない場合、preloadedStateは常にリデューサー内の state = ... よりも優先されます。なぜなら、リデューサーに渡される statepreloadedState であり、undefined ではないため、引数構文は適用されないからです。

combineReducers()を使用すると、動作はよりニュアンスがあります。preloadedStateで状態が指定されているリデューサーは、その状態を受け取ります。他のリデューサーはundefinedを受け取り、そのため、指定した state = ... デフォルト引数にフォールバックします。

一般的に、preloadedState はリデューサーによって指定された状態よりも優先されます。これにより、リデューサーはデフォルト引数としてそれらに意味のある初期データを指定できますが、永続ストレージまたはサーバーからストアをハイドレートするときに既存のデータ(全部または一部)をロードすることもできます。

注意:preloadedStateを使用して初期状態が設定されているリデューサーは、undefinedstateが渡された場合の処理のためにデフォルト値を引き続き提供する必要があります。すべてのリデューサーは初期化時にundefinedを渡されるため、undefinedが与えられたときに何らかの値が返されるように記述する必要があります。これはundefined以外の任意の値で構いません。デフォルトとしてpreloadedStateのセクションをここで複製する必要はありません。

詳細

単一のシンプルなリデューサー

まず、単一のリデューサーがある場合を考えてみましょう。combineReducers()を使用しないとしましょう。

リデューサーは次のようになる可能性があります。

function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}

次に、それを使用してストアを作成するとします。

import { createStore } from 'redux'
const store = createStore(counter)
console.log(store.getState()) // 0

初期状態はゼロです。なぜでしょうか?それは、createStoreの2番目の引数がundefinedであったからです。これは、最初にリデューサーに渡されるstateです。Reduxが初期化すると、状態を埋めるために「ダミー」アクションをディスパッチします。そのため、counterリデューサーはstateundefinedに等しい状態で呼び出されました。これが、デフォルト引数を「アクティブ化」するまさにケースです。したがって、デフォルトのstate値(state = 0)に従って、stateは現在0です。この状態(0)が返されます。

別のシナリオを考えてみましょう。

import { createStore } from 'redux'
const store = createStore(counter, 42)
console.log(store.getState()) // 42

なぜ今回は0ではなく42なのでしょうか?それは、createStoreが2番目の引数として42で呼び出されたからです。この引数は、ダミーアクションとともにリデューサーに渡されるstateになります。今回は、stateundefinedではない(42です!)ため、デフォルト引数構文は効果がありません。state42であり、リデューサーから42が返されます。

結合されたリデューサー

次に、combineReducers()を使用する場合を考えてみましょう。2つのリデューサーがあります。

function a(state = 'lol', action) {
return state
}

function b(state = 'wat', action) {
return state
}

combineReducers({ a, b })によって生成されたリデューサーは、次のようになります。

// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
return {
a: a(state.a, action),
b: b(state.b, action)
}
}

preloadedStateなしでcreateStoreを呼び出すと、state{}に初期化されます。したがって、state.astate.bは、abのリデューサーを呼び出すまでにundefinedになります。abの両方のリデューサーは、それらの state引数としてundefinedを受け取り、デフォルトのstate値を指定している場合、それらが返されます。これが、結合されたリデューサーが最初の呼び出しで{ a: 'lol', b: 'wat' }状態オブジェクトを返す方法です。

import { createStore } from 'redux'
const store = createStore(combined)
console.log(store.getState()) // { a: 'lol', b: 'wat' }

別のシナリオを考えてみましょう。

import { createStore } from 'redux'
const store = createStore(combined, { a: 'horse' })
console.log(store.getState()) // { a: 'horse', b: 'wat' }

次に、preloadedStatecreateStore()への引数として指定しました。結合されたリデューサーから返される状態は、aリデューサーに指定した初期状態と、bリデューサーが自身で選択した'wat'デフォルト引数とを組み合わせます

結合されたリデューサーが何をするかを思い出してみましょう。

// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
return {
a: a(state.a, action),
b: b(state.b, action)
}
}

この場合、stateが指定されたため、{}にフォールバックしませんでした。aフィールドが'horse'に等しく、bフィールドがないオブジェクトでした。これが、aリデューサーがstateとして'horse'を受け取り、それを喜んで返しましたが、bリデューサーはstateとしてundefinedを受け取ったため、デフォルトのstate(この例では'wat')に対する自身のアイデアを返した理由です。これにより、{ a: 'horse', b: 'wat' }が返されます。

まとめ

まとめると、Reduxの慣例に従い、state引数としてundefinedで呼び出されたときにリデューサーから初期状態を返す場合(これを実装する最も簡単な方法は、stateのデフォルト引数値を指定することです)、結合されたリデューサーに対して適切な便利な動作が得られます。createStore()関数に渡すpreloadedStateオブジェクトの対応する値が優先されますが、何も渡さなかった場合、または対応するフィールドが設定されていない場合は、代わりにリデューサーによって指定されたデフォルトのstate引数が選択されます。このアプローチは、初期化と既存データのハイドレーションの両方を提供し、データが保持されなかった場合に個々のリデューサーが状態をリセットできるようにするため、うまく機能します。もちろん、このパターンを再帰的に適用できます。combineReducers()を複数のレベルで使用したり、リデューサーを呼び出して状態ツリーの関連部分を与えることでリデューサーを手動で構成したりできます。