初始化狀態
初始化應用程式狀態有兩種主要方法。createStore
方法可以接受一個選用的 preloadedState
值作為其第二個參數。Reducer 也可以透過尋找一個為 undefined
的輸入狀態參數來指定一個初始值,並傳回他們想要用作預設值的值。這可以使用 reducer 內部的明確檢查來完成,或使用預設參數值語法:function myReducer(state = someDefaultValue, action)
。
這兩種方法如何互動並不總是立即可見。幸運的是,這個過程確實遵循一些可預測的規則。以下是各個部分如何組合在一起的說明。
摘要
沒有 combineReducers()
或類似的自訂程式碼,preloadedState
永遠會在 reducer 中勝過 state = ...
,因為傳遞給 reducer 的 state
就是 preloadedState
,不是 undefined
,因此參數語法不適用。
使用 combineReducers()
時,行為會更細緻。狀態在 preloadedState
中指定的 reducer 會收到該狀態。其他 reducer 會收到 undefined
,因此會回退到他們指定的 state = ...
預設參數。
一般而言,preloadedState
會勝過 reducer 指定的狀態。這讓 reducer 可以指定對他們而言有意義的初始資料作為預設參數,但也可以在從某些持久性儲存空間或伺服器補充資料庫時載入現有資料(全部或部分)。
注意:初始狀態使用 preloadedState
填入的 reducer 仍然需要提供一個預設值來處理傳遞 undefined
的 state
。所有 reducer 在初始化時都會傳遞 undefined
,因此應該撰寫這些 reducer,以便在給定 undefined
時,應該傳回某些值。這可以是任何非 undefined
值;不需要在此處複製 preloadedState
的區段作為預設值。
深入探討
單一簡單的 reducer
首先,讓我們考慮一個您有一個單一 reducer 的案例。假設您不使用 combineReducers()
。
那麼您的 reducer 可能會像這樣
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
的第二個參數是 undefined
。這是第一次傳遞給您的 reducer 的 state
。當 Redux 初始化時,它會發送一個「虛擬」動作來填入狀態。因此,您的 counter
reducer 被呼叫,而 state
等於 undefined
。這正是「啟動」預設參數的情況。因此,state
現在根據預設 state
值(state = 0
)為 0
。將傳回此狀態(0
)。
讓我們考慮一個不同的場景
import { createStore } from 'redux'
const store = createStore(counter, 42)
console.log(store.getState()) // 42
為什麼這次是 42
,而不是 0
?因為 createStore
被呼叫時,42
作為第二個參數。這個參數會變成傳給你的 reducer 的 state
,以及虛擬動作。這次,state
不是未定義(它是 42
!),所以預設參數語法沒有效果。state
是 42
,而 42
從 reducer 傳回。
合併的 Reducer
現在讓我們考慮一個你使用 combineReducers()
的案例。你有兩個 reducer
function a(state = 'lol', action) {
return state
}
function b(state = 'wat', action) {
return state
}
由 combineReducers({ a, b })
產生的 reducer 看起來像這樣
// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
return {
a: a(state.a, action),
b: b(state.b, action)
}
}
如果我們呼叫沒有 preloadedState
的 createStore
,它會將 state
初始化為 {}
。因此,當它呼叫 a
和 b
reducer 時,state.a
和 state.b
將會是 undefined
。a
和 b
reducer 都會收到 undefined
作為它們的 state
參數,如果它們指定預設的 state
值,這些值將會傳回。這就是合併的 reducer 在第一次呼叫時傳回 { 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' }
現在我指定 preloadedState
作為 createStore()
的參數。合併的 reducer 傳回的狀態會合併我為 a
reducer 指定的初始狀態和 b
reducer 自己選擇的 'wat'
預設參數。
讓我們回顧合併的 reducer 做了什麼
// 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
reducer 收到 'horse'
作為它的 state
並樂意傳回它,但 b
reducer 收到 undefined
作為它的 state
,因此傳回它認為的預設 state
(在我們的範例中,是 'wat'
)。這就是我們得到 { a: 'horse', b: 'wat' }
傳回值的方式。
回顧
總之,如果你堅持 Redux 慣例,並在 reducer 被呼叫時傳回初始狀態,而 state
參數是 undefined
(實現這個最簡單的方式是指定 state
預設參數值),你將會為合併的 reducer 有一個有用的好行為。它們會偏好你傳給 createStore()
函式的 preloadedState
物件中的對應值,但是如果你沒有傳遞任何值,或者對應的欄位沒有設定,則會選擇 reducer 指定的預設 state
參數。這種方法運作良好,因為它同時提供了初始化和現有資料的注入,但允許個別 reducer 在其資料未保留時重設其狀態。當然,你可以遞迴地套用這個模式,因為你可以在許多層級上使用 combineReducers()
,甚至可以透過呼叫 reducer 並給它們狀態樹的相關部分來手動組合 reducer。