如何创建带有精确泛型类型的TypeScript Reducer并解决类型不兼容报错
解决TypeScript泛型Reducer中Partial类型不兼容问题
问题根源
你遇到的报错本质是联合类型的Partial推导逻辑导致的:当泛型T是A | B时,Partial<T>实际上等于Partial<A> & Partial<B>(因为联合类型的Partial要求属性对所有联合成员都可选),但你的action.payload.newValues是Partial<A> | Partial<B>,这两个类型无法直接赋值——Partial<A>并不满足Partial<A> & Partial<B>的约束(缺少B的可选属性),反之亦然。
另外,当前的泛型约束T extends A | B没有把T和传入的category参数绑定起来,TypeScript无法确定points的类型和action.payload的类型是否匹配,这加剧了类型不兼容问题。
修复方案
方案1:绑定泛型与Category的关联
通过泛型约束让T的category属性和传入的category参数严格对应,让TypeScript能精确推导类型:
enum Category { A, B } interface A { readonly category: Category.A; id: string; // 补充updateItemInArray需要的id属性 } interface B { readonly category: Category.B; id: string; } // 定义映射类型,让Category对应具体的Point类型 type PointByCategory<C extends Category> = C extends Category.A ? A : B; const genericReducer = <C extends Category>( state: MyState, action: Actions, points: PointByCategory<C>[], category: C ): PointByCategory<C>[] => { switch (action.type) { case POINT_UPDATED: { if (action.payload.category !== category) return points; // 用类型断言告诉TS:当前newValues和stpt的类型匹配 return updateItemInArray(points, action.payload.id, (stpt) => { return updateObject(stpt, action.payload.newValues as Partial<PointByCategory<C>>); }); } default: return points; } };
方案2:优化updateObject的类型定义
如果不想修改泛型结构,可以调整updateObject的类型,让它直接基于传入的oldObject推导newValues的类型,这样更灵活:
class ArrayUtils { static updateObject = <T>(oldObject: T, newValues: Partial<typeof oldObject>) => { return Object.assign({}, oldObject, newValues); }; static updateItemInArray = <T>(array: T[], itemId: string, updateItemCallback: (item: T) => T): T[] => { return array.map(item => item.id !== itemId ? item : updateItemCallback(item)); }; }
然后在genericReducer中,利用类型守卫让TypeScript明确stpt的类型:
const genericReducer = <T extends A | B>( state: MyState, action: Actions, points: T[], category: Category ): T[] => { switch (action.type) { case POINT_UPDATED: { if (action.payload.category !== category) return points; return updateItemInArray(points, action.payload.id, (stpt) => { // 通过类型守卫匹配stpt和newValues的类型 if (stpt.category === Category.A) { return updateObject(stpt, action.payload.newValues as Partial<A>); } else { return updateObject(stpt, action.payload.newValues as Partial<B>); } }); } default: return points; } };
方案3:让Actions类型与泛型关联(最严谨)
如果你的Actions类型可以泛型化,最好让POINT_UPDATED的payload类型和T绑定,从根源上避免类型不匹配:
type Actions<C extends Category = Category> = | { type: 'POINT_UPDATED'; payload: { category: C; id: string; newValues: Partial<PointByCategory<C>> } } | /* 其他action类型 */; const genericReducer = <C extends Category>( state: MyState, action: Actions<C>, points: PointByCategory<C>[], category: C ): PointByCategory<C>[] => { switch (action.type) { case POINT_UPDATED: { if (action.payload.category !== category) return points; // 此时newValues类型自动匹配Partial<PointByCategory<C>>,无需断言 return updateItemInArray(points, action.payload.id, (stpt) => { return updateObject(stpt, action.payload.newValues); }); } default: return points; } };
内容的提问来源于stack exchange,提问作者RouteMapper
相关产品推荐
相关产品推荐

