TypeScript Redux场景下动态key赋值无索引签名及泛型类型错误
第一类报错:字符串无法索引联合类型问题
报错原因
你最初定义的AssignValue接口中,key字段是宽泛的string类型,而CarFilters是仅包含arrayType、objectType两个固定键的接口,没有声明字符串索引签名。当你通过params.category取到的过滤器类型是CarFilters | ShipFilters联合类型,TS无法确定你传入的string类型的key一定存在于联合类型的所有成员上,因此会抛出索引不合法的报错。
你后续优化的代码通过泛型约束,将category和对应的key、value类型做了绑定,TS可以精准推导当前category对应的过滤器类型,以及key是该类型的合法属性,value类型也和属性类型匹配,因此不会报错。
第二类报错:联合类型赋值需要交叉类型问题
报错原因
你将参数类型单独提取后,在定义updateCategoryFilterV2时没有给函数声明泛型,直接使用了UpdateAppliedFilterType的默认泛型值,此时params.category的类型是'cars' | 'ships'联合类型,params.filter的类型是CarFilters | ShipFilters联合类型。TS无法保证category和filter是一一对应的(比如存在传入category: 'cars'但filter是ShipFilters类型的可能),为了保证赋值安全,TS会要求赋值的内容必须同时满足所有可能的目标类型,也就是CarFilters & ShipFilters交叉类型,和实际传入的联合类型不匹配,因此报错。
解决方案
给函数也添加对应的泛型约束,绑定category和对应filter的类型关系,让TS可以确认两者是匹配的,修改后的代码如下:
// 提取泛型参数类型,简化写法不需要额外PathValue type UpdateAppliedFilterType<Category extends keyof AppStateFilters> = { category: Category; filter: AppStateFilters[Category]; } // 函数声明泛型,绑定入参的类型对应关系 const updateCategoryFilterV2 = <Category extends keyof AppStateFilters>(params: UpdateAppliedFilterType<Category>) => { initialState.appliedFilters[params.category] = params.filter; }
如果要适配Redux Toolkit的reducer场景,可以直接在PayloadAction中使用该泛型类型:
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; const filtersSlice = createSlice({ name: 'filters', initialState, reducers: { updateCategoryFilter: <Category extends keyof AppStateFilters>( state, action: PayloadAction<UpdateAppliedFilterType<Category>> ) => { state.appliedFilters[action.payload.category] = action.payload.filter; }, // 之前的单属性更新action也可以按相同逻辑定义 updateFilterProperty: < Category extends keyof AppStateFilters, Key extends keyof AppStateFilters[Category] >( state, action: PayloadAction<{ category: Category; key: Key; value: AppStateFilters[Category][Key]; }> ) => { state.appliedFilters[action.payload.category][action.payload.key] = action.payload.value; } } });
如果确实不需要严格的类型校验(不推荐),也可以用类型断言跳过检查:
const updateCategoryFilterV2 = (params: UpdateAppliedFilterType) => { initialState.appliedFilters[params.category] = params.filter as never; }
内容的提问来源于stack exchange,提问作者Susitha Ravinda Senarath

