Redux与TypeScript中如何实现set action的键值严格类型约束
解决方案
你当前写法的核心问题是PayloadAction里的key和value类型没有做绑定:key: keyof MyState仅约束key是状态键之一,value: any跳过了所有类型校验,TS无法根据传入的key推导对应value的合法类型。
要实现严格的键值类型匹配,只需要构造一个键值绑定的联合类型作为payload类型即可,不需要改动reducer的运行逻辑。
方式1:默认对象传参调用
如果你保持Redux默认的action调用形式(传入单个payload对象),实现代码如下:
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; interface MyState { key1: string; key2: boolean; key3: number; } // 构造键值一一绑定的payload联合类型 type SetStatePayload<T> = { [K in keyof T]: { key: K; value: T[K]; }; }[keyof T]; const initialState: MyState = { key1: '', key2: false, key3: 0, }; const testSlice = createSlice({ name: 'test', initialState, reducers: { set: (state, action: PayloadAction<SetStatePayload<MyState>>) => { const { key, value } = action.payload; state[key] = value; }, }, }); const { set } = testSlice.actions; // 校验效果 set({ key: 'key1', value: 1 }); // 类型报错:value类型应为string set({ key: 'key1', value: 'my string value' }); // 校验通过 set({ key: 'key2', value: true }); // 校验通过 set({ key: 'key2', value: 'my string value' }); // 类型报错:value类型应为boolean
方式2:双参数调用(匹配你给出的示例写法)
如果你想直接传入key、value两个参数调用action(不需要包一层payload对象),可以通过prepare回调组装payload,同时保留类型校验:
const testSlice = createSlice({ name: 'test', initialState, reducers: { set: { reducer: (state, action: PayloadAction<SetStatePayload<MyState>>) => { const { key, value } = action.payload; state[key] = value; }, // 对入参做泛型约束,和你找到的setAttribute类型逻辑一致 prepare: <K extends keyof MyState>(key: K, value: MyState[K]) => { return { payload: { key, value } }; }, }, }, }); const { set } = testSlice.actions; // 校验效果和你预期完全一致 set('key1', 1); // 类型报错:value类型应为string set('key1', 'my string value'); // 校验通过 set('key2', true); // 校验通过(注:你示例中写的传入boolean是笔误,boolean是TS类型,运行时应传入true/false布尔值) set('key2', 'my string value'); // 类型报错:value类型应为boolean
实现原理
我们通过映射类型遍历MyState的所有键,生成每个键对应{key: 键名, value: 对应值类型}的结构,再通过索引访问[keyof T]把所有结构合并成联合类型。TS会根据你传入的key自动收窄联合类型的范围,匹配对应value的合法类型,实现严格校验。
你找到的setAttribute函数的泛型逻辑,本质和这里的类型约束思路一致,只是Redux的action payload需要先把键值组装成对象,所以我们提前把所有合法的键值组合做成了联合类型。
内容的提问来源于stack exchange,提问作者Daniel Aviv
相关产品推荐
相关产品推荐

