combineSlices
概觀
將切片組合成單一 reducer 的函式,並可在初始化後注入更多 reducer。
- TypeScript
- JavaScript
// file: slices/index.ts
import { combineSlices } from '@reduxjs/toolkit'
import { api } from './api'
import { userSlice } from './users'
export const rootReducer = combineSlices(api, userSlice)
// file: store.ts
import { configureStore } from '@reduxjs/toolkit'
import { rootReducer } from './slices'
export const store = configureStore({
reducer: rootReducer,
})
// file: slices/index.js
import { combineSlices } from '@reduxjs/toolkit'
import { api } from './api'
import { userSlice } from './users'
export const rootReducer = combineSlices(api, userSlice)
// file: store.js
import { configureStore } from '@reduxjs/toolkit'
import { rootReducer } from './slices'
export const store = configureStore({
reducer: rootReducer,
})
combineSlices
的「切片」通常使用 createSlice
建立,但可以是任何具有 reducerPath
和 reducer
屬性的「類似切片」物件(表示 RTK Query API 實例 也相容)。
const withUserReducer = rootReducer.inject({
reducerPath: 'user',
reducer: userReducer,
})
const withApiReducer = rootReducer.inject(fooApi)
為簡化起見,這些文件將此 { reducerPath, reducer }
形狀描述為「切片」。
參數
combineSlices
接受一組切片和/或簡化器對應物件,並將它們組合成一個簡化器。
切片會掛載在它們的 reducerPath
,而簡化器對應物件中的項目會掛載在其各自的鍵下。
const rootReducer = combineSlices(counterSlice, baseApi, {
user: userSlice.reducer,
auth: authSlice.reducer,
})
// is like
const rootReducer = combineReducers({
[counterSlice.reducerPath]: counterSlice.reducer,
[baseApi.reducerPath]: baseApi.reducer,
user: userSlice.reducer,
auth: authSlice.reducer,
})
如果多個切片/對應物件有相同的簡化器路徑,則引數中後提供的簡化器會覆寫先前的簡化器。
不過,輸入法無法考量這一點。最好確保所有簡化器都針對唯一位置。
傳回值
combineSlices
傳回一個簡化器函式,並附加方法。
interface CombinedSliceReducer<InitialState, DeclaredState = InitialState>
extends Reducer<DeclaredState, AnyAction, Partial<DeclaredState>> {
withLazyLoadedSlices<LazyLoadedSlices>(): CombinedSliceReducer<
InitialState,
DeclaredState & Partial<LazyLoadedSlices>
>
inject<Slice extends SliceLike>(
slice: Slice,
config?: InjectConfig
): CombinedSliceReducer<InitialState, DeclaredState & WithSlice<Slice>>
selector: {
(selectorFn: Selector, selectState?: SelectFromRootState) => WrappedSelector
original(state: DeclaredState) => InitialState & Partial<DeclaredState>
}
}
withLazyLoadedSlices
建議從儲存體推論 RootState 類型,而這會從簡化器推論。不過,如果切片是延遲載入,因此無法推論,這可能會產生問題。
withLazyLoadedSlices
允許您宣告稍後會新增至狀態的切片,這些切片會包含在最終狀態類型中。
管理此問題的一種可能模式是使用宣告合併
// file: slices/index.ts
import { combineSlices } from '@reduxjs/toolkit'
import { staticSlice } from './static'
export interface LazyLoadedSlices {}
export const rootReducer =
combineSlices(staticSlice).withLazyLoadedSlices<LazyLoadedSlices>()
// keys in LazyLoadedSlices are marked as optional
export type RootState = ReturnType<typeof rootReducer>
// file: slices/lazySlice.ts
import type { WithSlice } from '@reduxjs/toolkit'
import { rootReducer } from '.'
const lazySlice = createSlice({
/* ... */
})
declare module '.' {
export interface LazyLoadedSlices extends WithSlice<typeof lazySlice> {}
}
const injectedReducer = rootReducer.inject(lazySlice)
// and/or
const injectedSlice = lazySlice.injectInto(rootReducer)
上述範例使用 WithSlice
實用程式類型,用於掛載在 reducerPath
下的切片。如果切片掛載在不同的鍵下,您可以將其宣告為一般鍵。
// file: slices/lazySlice.ts
import { rootReducer } from '.'
const lazySlice = createSlice({
/* ... */
})
declare module '.' {
export interface LazyLoadedSlices {
customKey: LazyState
}
}
const injectedReducer = rootReducer.inject({
reducerPath: 'customKey',
reducer: lazySlice.reducer,
})
// and/or
const injectedSlice = lazySlice.injectInto(rootReducer, {
reducerPath: 'customKey',
})
inject
inject
允許您在初始化後將切片新增至簡化器組。它預期傳遞切片和選用組態,並傳回包含切片的簡化器更新版本。
這對於延遲載入 reducer 來說特別有用。
const reducerWithUser = rootReducer.inject(userSlice)
inject
會將區段加入原始 reducer 中的 reducer 映射,但不會發送動作。
這表示已加入的 reducer 狀態不會顯示在您的儲存區中,直到發送下一個動作為止。
Reducer 替換
預設情況下,不允許替換 reducer。在開發模式中,如果嘗試將新的 reducer 執行個體注入到已注入的 reducerPath
中,將會在主控台中記錄警告訊息(如果將同一個 reducer 執行個體注入到同一個位置兩次,則不會發出警告)。
如果您希望允許使用新的執行個體替換 reducer,則必須明確傳遞 overrideExisting: true
作為設定物件的一部分。
const reducerWithUser = rootReducer.inject(userSlice, {
overrideExisting: true,
})
這對於熱重新載入,或透過替換 reducer(該 reducer 始終傳回 null
)來「移除」reducer 可能很有用。請注意,為了獲得可預測的行為,您的類型應考量您打算佔用路徑的所有可能的 reducer。
declare module '.' {
export interface LazyLoadedSlices {
removable: RemovableState | null
}
}
const withInjected = rootReducer.inject(
{ reducerPath: 'removable', reducer: removableReducer },
{ overrideExisting: true },
)
const emptyReducer = () => null
const removeReducer = () =>
rootReducer.inject(
{ reducerPath: 'removable', reducer: emptyReducer },
{ overrideExisting: true },
)
selector
如前所述,如果尚未發送任何動作,已注入的 reducer 在狀態中仍可能是未定義的。
在撰寫選取器時,處理這種可能是選用的狀態可能會帶來不便,因為您可能會得到許多可能未定義或依賴明確預設值的結果。
selector
允許您透過將 reducer 狀態包裝在 Proxy
中來解決這個問題,該 Proxy
可確保當前注入的任何 reducer 都會評估為其初始狀態(如果它們目前在狀態中為 undefined
)。
declare module '.' {
export interface LazyLoadedSlices extends WithSlice<typeof counterSlice> {}
}
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
/* ... */
},
})
const withCounter = rootReducer.inject(counterSlice)
const selectCounterValue = (rootState: RootState) => rootState.counter?.value // number | undefined
const wrappedSelectCounterValue = withCounter.selector(
(rootState) => rootState.counter.value, // number
)
console.log(
selectCounterValue({}), // undefined
selectCounterValue({ counter: { value: 2 } }), // 2
wrappedSelectCounterValue({}), // 0
wrappedSelectCounterValue({ counter: { value: 2 } }), // 2
)
Proxy
會透過使用隨機產生的動作類型來呼叫 reducer 來擷取 reducer 的初始狀態 - 請勿嘗試在 reducer 內部將此視為特殊情況來處理。
巢狀組合 reducer
包裝後的選取器預期使用組合 reducer 傳回的狀態作為其第一個引數。
如果組合 reducer 巢狀進一步置於儲存區狀態內部,請將 selectState
回呼傳遞為 selector
的第二個引數
interface RootState {
innerCombined: ReturnType<typeof combinedReducer>
}
const selectCounterValue = withCounter.selector(
(combinedState) => combinedState.counter.value,
(rootState: RootState) => rootState.innerCombined,
)
console.log(
selectCounterValue({
innerCombined: {},
}), // 0
selectCounterValue({
innerCombined: {
counter: {
value: 2,
},
},
}), // 2
)
original
類似於 Immer 使用,會提供 original
函式來擷取提供給 Proxy
的原始狀態值。
這對於除錯/檢查特別有用,因為 Proxy
執行個體傾向以難以閱讀的格式顯示。
該函式附加為 selector
函式上的方法
const wrappedSelectCounterValue = withCounter.selector((rootState) => {
console.log(withCounter.selector.original(rootState))
return rootState.counter.value
})
區段整合
injectInto
由 createSlice
傳回的區段執行個體有一個附加的 injectInto
方法,該方法會接收來自 combineSlices
的可注入 reducer,並傳回該區段的「已注入」版本。
const injectedCounterSlice = counterSlice.injectInto(rootReducer)
可以傳遞一個選用的設定物件。這會遵循 inject
的選項,並新增一個 reducerPath
欄位,用於將區段注入到其目前的 reducerPath
屬性以外的路徑下。
const aCounterSlice = counterSlice.injectInto(rootReducer, {
reducerPath: 'aCounter',
})
selectors
/ getSelectors
與 selector
類似,來自「注入」區塊實例的選擇器行為略有不同。
如果在傳遞的儲存狀態中區塊狀態未定義,則會使用區塊的初始狀態呼叫選擇器。
如果在注入期間變更 reducerPath
,selectors
也會反映變更。
console.log(
injectedCounterSlice.selectors.selectValue({}), // 0
injectedCounterSlice.selectors.selectValue({ counter: { value: 2 } }), // 2
aCounterSlice.selectors.selectValue({ aCounter: { value: 2 } }), // 2
)