定义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,这种使用方法可以参考下一章节。