定义State片段
在具备Action和Reducer以后就可以把它们组合起来创建Store了。在上一节的示例中,Action和Reducer都是分别定义的,所以实际上一个State片段的定义还是有进一步优化的空间的。为了简化定义Reducer的过程,Redux Toolkit也提供了创建State片段的快捷方法createSlice
。
使用createSlice
实际上就是结合使用createAction
和createRedcuer
两个函数。所以在绝大部分情况下斗建议直接使用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,这种使用方法可以参考下一章节。