Reduxレデューサーでストアの初期状態を読み取る


85

Reduxアプリの初期状態は、次の2つの方法で設定できます。

初期状態をストアに渡す場合、ストアからその状態をどのように読み取り、それをレデューサーの最初の引数にしますか?

回答:


185

TL; DR

なければcombineReducers()または類似マニュアルコード、initialStateいつも以上の勝利state = ...ので減速にstate減速に渡されている initialStateいない undefinedES6引数の構文は、この場合には適用されませんので、。

combineReducers()行動より微妙です。で状態が指定されているレデューサーは、それinitialStateを受け取りstateます。他のレデューサーは受信undefined し、そのため、state = ...指定したデフォルトの引数にフォールバックします。

一般に、initialStateレデューサーによって指定された状態に勝ちます。これは、レデューサーは理にかなっている初期データを指定することができます彼らにデフォルト引数として、だけでなく、あなたには、いくつかの永続ストレージやサーバーからのストアを水和しているときに(完全または部分的に)ロード既存のデータを可能にします。

まず、レデューサーが1つしかない場合を考えてみましょう。
使用しないとしましょう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';
let store = createStore(counter);
console.log(store.getState()); // 0

初期状態はゼロです。どうして?の2番目の引数はcreateStoreでしたundefined。これは、state初めてレデューサーに渡されます。Reduxが初期化されると、状態を埋めるために「ダミー」アクションがディスパッチされます。したがって、counterレデューサーはにstate等しいで呼び出されましたundefinedこれは、デフォルトの引数を「アクティブ化」する場合とまったく同じです。したがって、state0デフォルトstate値(state = 0)のとおりになりました。この状態(0)が返されます。

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

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

今回はなぜであり42、そうではないの0ですか?そのためcreateStoreに呼ばれた422番目の引数として。この引数は、stateダミーアクションとともにレデューサーに渡されます。今回stateは未定義ではないので(42!)、ES6のデフォルトの引数構文は効果がありません。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)
  };
}

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

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

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

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

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

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

// 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レデューサーが'horse'そのとして受け取ってstate喜んで返したのに、bレデューサーがundefinedそのとして受け取ったstateためにデフォルトのアイデアを返した理由ですstate(この例では'wat')。これが私たちの{ a: 'horse', b: 'wat' }見返りです。


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


3
非常に詳細に感謝します-まさに私が探していたものです。ここでのAPIの柔軟性は素晴らしいです。私が見ていなかった部分は、「子」レデューサーが、で使用されているキーに基づいて、初期状態のどの部分がそれに属しているかを理解することですcombineReducers。どうもありがとうございました。
カンテラ2015年

この答えをありがとう、ダン。私があなたの答えを正しく消化しているなら、あなたはレデューサーのアクションタイプを通して初期状態を設定するのが最善であると言っていますか?たとえば、GET_INITIAL_STATEを実行し、そのアクションタイプへのディスパッチを呼び出します。たとえば、componentDidMountコールバックですか。
コンアントナコス2016

@ConAntonakosいいえ、私はこれを意味するものではありません。function counter(state = 0, action)つまり、またはなど、レデューサーを定義する場所で初期状態を選択することを意味しますfunction visibleIds(state = [], action)
ダンアブラモフ2016

1
@DanAbramov:createstore()の2番目の引数を使用して、SPAページのリロード時に、初期値の代わりに最後に保存された状態をreduxで再水和するにはどうすればよいですか。ストア全体をキャッシュしてリロード時に保存し、createstore関数に渡す方法を尋ねていると思います。
jasan 2016

1
@jasan:localStorageを使用します。egghead.io/lessons/…–
ダンアブラモフ

5

一言で言えば、初期状態をレデューサーに渡すのはReduxであり、何もする必要はありません。

電話をかけるcreateStore(reducer, [initialState])と、最初のアクションが入ったときにレデューサーに渡される初期状態が何であるかをReduxに知らせています。

あなたが言及する2番目のオプションは、ストアの作成時に初期状態を渡さなかった場合にのみ適用されます。すなわち

function todoApp(state = initialState, action)

状態は、Reduxによって渡された状態がなかった場合にのみ初期化されます


1
応答していただきありがとうございます-レデューサー構成の場合、ストアに初期状態を適用した場合、初期状態ツリーのどの部分を所有しているかを子/サブレデューサーにどのように伝えますか?たとえば、がある場合menuState、メニューリデューサーにstate.menuストアからの値を読み取り、それを初期状態として使用するように指示するにはどうすればよいですか?
カンテラ2015年

ありがとう、でも私の質問ははっきりしないに違いない。編集する前に、他の人が応答するかどうかを確認するのを待ちます。
カンテラ2015年

1

どのようにしてストアからその状態を読み取り、それをレデューサーの最初の引数にしますか?

CombineReducers()があなたに代わって仕事をします。それを書く最初の方法は本当に役に立ちません:

const rootReducer = combineReducers({ todos, users })

しかし、もう1つ、それは同等です。

function rootReducer(state, action) {
   todos: todos(state.todos, action),
   users: users(state.users, action)
}

0

これがあなたの要求に答えることを願っています(initialStateを渡してその状態を返す間にレデューサーを初期化することとして理解しました)

これが私たちのやり方です(警告:Typescriptコードからコピーされました)。

その要点はif(!state)、mainReducer(factory)関数でのテストです。

function getInitialState(): MainState {

     return {
         prop1:                 'value1',
         prop1:                 'value2',
         ...        
     }
}



const reducer = combineReducers(
    {
        main:     mainReducer( getInitialState() ),
        ...
    }
)



const mainReducer = ( initialState: MainState ): Reducer => {

    return ( state: MainState, action: Action ): MainState => {

        if ( !state ) {
            return initialState
        }

        console.log( 'Main reducer action: ', action ) 

        switch ( action.type ) {
            ....
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.