You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为Redux Toolkit的createSlice编写Flow类型声明的双向校验问题

如何为Redux Toolkit的createSlice编写同时支持Actions存在性和State属性校验的Flow类型声明?

看起来你卡在了Flow类型声明的两个需求平衡点上——既要让Flow准确识别createSlice生成的actions是否合法,又要让reducer里的state参数严格对应initialState的类型。先分析下你给出的两种方案的问题,再给你一个能同时满足两个需求的解决方案:

方案一的问题:Actions校验正常,但State属性无约束

你这个方案里,泛型a确实能让Flow根据reducers的键生成对应的actions,所以访问不存在的slice.doesNotExists会报错,但reducer函数的state参数没有和initialState的类型绑定,导致修改不存在的state.test时Flow无法检测到错误:

declare function createSlice<a = {| name: string, initialState: Object, reducers: {[reducerName: string]: () => void}, |}>(a: a): { actions: $ObjMap<$PropertyType<a, 'reducers'>, Function>, };
const slice = createSlice({ initialState: {}, reducers: { exists: (state) => { state.test = 1 // 无Flow错误 } } })
slice.exists() // 无错误
slice.doesNotExists // Flow报错(这部分正常)

方案二的问题:State属性校验正常,但Actions无约束

这个方案通过交叉类型让reducer的state参数关联到initialState的类型,所以修改state.test会触发Flow错误,但reducers的类型还是松散的任意字符串键函数,导致Flow无法识别哪些actions是真实存在的,访问不存在的slice.doesNotExists也不会报错:

declare function createSlice<a = {| name: string, initialState: Object, reducers: {[reducerName: string]: () => void}, |}>(b: a & { reducers: {[reducerName: string]: ($PropertyType<a, 'initialState'>) => void} }): { actions: $ObjMap<$PropertyType<a, 'reducers'>, Function>, };
const slice = createSlice({ initialState: {}, reducers: { exists: (state) => { state.test = 1 // Flow错误,这部分正常 } } })
slice.exists() // 无错误
slice.doesNotExists // 无错误(这里出问题)

解决方案:拆分泛型,同时约束State和Reducers

要同时满足两个需求,我们需要把initialState和reducers的类型用独立的泛型参数约束,并且让reducer函数的参数严格对应initialState的类型,同时让生成的actions完全映射reducers的键:

declare function createSlice<State: Object, Reducers: { [key: string]: (state: State) => void }>(
  options: {|
    name: string,
    initialState: State,
    reducers: Reducers,
  |}
): {|
  actions: $ObjMap<Reducers, <R>(r: R) => R>,
|};

用这个声明测试一下:

const slice = createSlice({
  name: 'counter',
  initialState: { count: 0 },
  reducers: {
    increment: (state) => {
      state.count += 1; // 正常,无错误
      state.test = 1; // Flow报错:属性`test`在`{ count: number }`中不存在
    },
    decrement: (state) => {
      state.count -= 1;
    }
  }
});

slice.actions.increment(); // 正常,无错误
slice.actions.decrement(); // 正常,无错误
slice.actions.nonexistent(); // Flow报错:属性`nonexistent`在`{ increment: ..., decrement: ... }`中不存在

这个方案的原理:

  • 用State泛型专门约束initialState的类型,确保reducer里的state参数完全匹配这个类型;
  • 用Reducers泛型约束reducers对象,其中每个reducer函数的参数必须是State类型;
  • 返回的actions通过$ObjMap把Reducers里的每个函数直接映射成对应的action creator,这样Flow就能精确识别哪些actions是合法的,访问不存在的actions会立刻报错。

这样就同时解决了两个问题——既校验了reducer中state的属性,又保证了actions的存在性校验。

内容的提问来源于stack exchange,提问作者LittleBro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:31:27