如何在Immer的produce函数中更新泛型类型的嵌套属性?
在React TypeScript中使用Immer处理泛型状态的问题及修复
问题背景
在React TypeScript应用中使用Immer库时,遇到泛型状态更新的类型错误。场景是一个reducer需要处理带有泛型参数的状态,该泛型可支持任意类型(基础类型、数组、对象等),但在produce方法中更新泛型属性时出现类型不兼容报错。
原始代码
import { produce } from 'immer'; type SomeState<TResult> = { inner: TResult } type SetInner<TResult> = { type: 'SET_INNER'; newValue: TResult; } const reducer = <TResult>( state: SomeState<TResult>, action: SetInner<TResult> ): SomeState<TResult> => { return produce(state, draft => { switch (action.type) { case 'SET_INNER': draft.inner = action.newValue; break; } }); }
报错信息
Type 'TResult' is not assignable to type 'Draft
'.
报错原因
Immer的Draft<T>类型会对引用类型(如对象、数组)生成代理包装,对基础类型(如字符串、数字)则直接等同于原类型。由于泛型TResult可表示任意类型,TypeScript无法确定action.newValue的类型是否能直接赋值给Draft<TResult>类型的draft.inner,因此抛出类型不兼容错误。
修复方案
使用Immer官方提供的castDraft工具函数,将action.newValue转换为对应的Draft<TResult>类型,即可解决类型不匹配问题。
修复后的代码
import { produce, castDraft } from 'immer'; type SomeState<TResult> = { inner: TResult } type SetInner<TResult> = { type: 'SET_INNER'; newValue: TResult; } const reducer = <TResult>( state: SomeState<TResult>, action: SetInner<TResult> ): SomeState<TResult> => { return produce(state, draft => { switch (action.type) { case 'SET_INNER': draft.inner = castDraft(action.newValue); break; } }); }
如果不想引入额外工具函数,也可以直接使用类型断言强制转换:
draft.inner = action.newValue as Draft<TResult>;
不过castDraft是Immer专门设计的类型转换工具,更符合库的设计规范,推荐优先使用。
验证场景
修复后的代码可完美支持以下使用场景:
基础类型场景
// 基础类型 const initialState = { inner: "foo" }; const action: SetInner<string> = { type: 'SET_INNER', newValue: "bar" } const newState = reducer(initialState, action); console.log(newState); // 输出: { inner: "bar" }
对象类型场景
// 对象类型 type Point = { x: number, y: number } const initialState2 = { inner: { x: 10, y: 4 } }; const action2: SetInner<Point> = { type: 'SET_INNER', newValue: { x: -5, y: 14 } } const newState2 = reducer(initialState2, action2); console.log(newState2); // 输出: { inner: { x: -5, y: 14 } }
内容的提问来源于stack exchange,提问作者Steve B
相关产品推荐
相关产品推荐

