定义State片段

在具备Action和Reducer以后就可以把它们组合起来创建Store了。在上一节的示例中,Action和Reducer都是分别定义的,所以实际上一个State片段的定义还是有进一步优化的空间的。为了简化定义Reducer的过程,Redux Toolkit也提供了创建State片段的快捷方法createSlice

使用createSlice实际上就是结合使用createActioncreateRedcuer两个函数。所以在绝大部分情况下斗建议直接使用createSlice函数来直接创建State片段,而不是分别创建Action和Reducer。

现在把上一节中的示例再使用createSlice重写一下就是以下的样子。

import { createSlice, PayloadAction } from '@reduxjs/toolkit'; interface CounterState { value: number; } const counterSlice = createSlice({ name: 'counter', initialState: { value: 0 }, reducers: { // 将会创建一个能够处理action type为 'counter/increaseBy1' 的reducer。 increaseBy1(state) { state.value += 1; }, // 将会创建一个能够处理action type为 'counter/decreaseBy1' 的reducer。 decreaseBy1(state) { state.value -= 1; }, // 将会创建一个能够处理action type为 'counter/increaseByAmount' 的reducer。 increaseByAmount(state, action: PayloadAction<number>) { state.value += action.payload; }, // 将会创建一个能够处理action type为 'counter/decreaseByAmount' 的reducer。 decreaseByAmount(state, action: PayloadAction<number>) { state.value -= action.payload; } } }); export const { increaseBy1, decreaseBy1, increaseByAmount, decreaseByAmount } = counterSlice.actions; export default counterSlice.reducer;

从上面这个示例可以看出,使用createSlice创建State片段就不再需要详细定义Action了,只需要定义Reducer即可。而且Reducer的syyi也被简化成了普通的函数。createSlice在其实现中使用name作为action.type的前缀,Reducer的名称作为实际action.type的名称,来逐个定义各个Action。不过最推荐的办法还是如示例中所示的,直接从定义好的State片段的action属性中导出即可。

在之前的示例中,createAction函数提供了能够自由构建Action的方法,但是在上面的示例中,Action被简化成了一个函数名称,看起来并不能自由的定义构建Action的方法。事实上,createSlice方法已经提供了可以自由定义构建Action的途径。例如这里整合一下前面需要自定义Action的过程。

const counterSlice = createSlice({ name: 'counter', initialState: { value: 0 }, reducers: { increaseWithTime: { reducer: (state, action) => { state.value += action.payload.amount; }, prepare: (amount: number) => ({ payload: { amount, createAt: new Date() } }) } } });

在之前介绍的configureStore函数中,要形成一个Store需要在其配置对象的reducers属性中列举所甩开使用的Reducer。这里做需要使用的Reducer即是从State片段中导出的reducer属性。例如上面构建的State片段放入之前使用configureStore创建Store的示例中,就是以下的样子。

import { configureStore } from '@reduxjs/toolkit'; import CounterReducer from './counter_reducer'; export const store = configureStore({ reducer: { counter: CounterReducer } }); export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch;

除了可以直接在createSlice函数配置对象的reducers属性中定义Reducer以外,还可以在其extraReducers属性中使用更高级的Reducer定义方法,例如使用createReducer中的构造者模式。以下是createSlice的函数签名定义,通过这个函数签名可以看出createSlice都可以接受那些配置内容。

// CaseReducer代表一个Reducer Action,接受一个State对象和一个处理函数作为参数。 // 其中Draft<T>类型来自Immer库。 type CaseReducer<S = any, A extends Action = AnyAction> = ( state: Draft<S>, action: A ) => S | void | Draft<S>; function createSlice< State, CaseReducers extends SliceCaseReducers<State>, Name extends string = string >({ // name属性用于定义action type的前缀。 name: Name, // 用于定义redecer的初始状态。 initialState: State | (() => State), // 用于定义匹配分支条件的Reducer方法,其中Reducer中的键值将作为action type名称使用。 reducers: Object<string, CaseReducer | ReducerAndPrepareObject>, // 可以通过构造者模式来定义具有自定义分支条件的Reducer方法,这里也同样建议使用Reducer对应的键值参与action type名称的匹配。 extraReducers?: Object<string, CaseReducer> | ((builder: ActionReducerMapBuilder<State>) => void) })

另一种比较复杂的情况是需要在一个State片段中响应另一个State片段中定义的Action,这种使用方法在处理复杂数据结构的时候十分有用。这种情况也需要在extraReducers中定义,但是需要引用另一个State片段中定义的Action。这种用法可以参考以下示例。

const personStore = createSlice({ name: 'person', initialState: { name: '', age: 18 }, reducers: { setName: (state, action) => { state.name = action.payload; }, incrementAge: state => { state.age++; } } }); const yearCounterStore = createSlice({ name: 'year', initialState: { year: 0 }, reducers: {}, extraReducers: { // 在extraReducers中只需要直接定义响应其他State片段导出的Action即可。 [personStore.actions.incrementAge]: state => { state.year++; } } });

其实在属性extraReducers中还可以定义异步Action,这种使用方法可以参考下一章节。