跳至主要內容

combineSlices

概觀

將切片組合成單一 reducer 的函式,並可在初始化後注入更多 reducer。

// 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,
})
註解

combineSlices 的「切片」通常使用 createSlice 建立,但可以是任何具有 reducerPathreducer 屬性的「類似切片」物件(表示 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。

透過替換為無操作函式來「移除」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 類似,來自「注入」區塊實例的選擇器行為略有不同。

如果在傳遞的儲存狀態中區塊狀態未定義,則會使用區塊的初始狀態呼叫選擇器。

如果在注入期間變更 reducerPathselectors 也會反映變更。

console.log(
injectedCounterSlice.selectors.selectValue({}), // 0
injectedCounterSlice.selectors.selectValue({ counter: { value: 2 } }), // 2
aCounterSlice.selectors.selectValue({ aCounter: { value: 2 } }), // 2
)