为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
相关产品推荐
相关产品推荐

