如何在对象解构赋值中为...rest保留原对象类型并保持类型安全?
TypeScript中保留原运行时代码的Partial类型安全处理方案
问题场景
在TypeScript开发中,需要编写处理Partial<Record>类型(包含约10个属性)的方法,返回与输入同类型的新记录:部分属性被移除、修改,其余多数保持不变。
简化示例代码
type ABCD = Partial<{ a: number, b: number, c: number, d: number }>; /** 若a为正数则翻倍并移除b;若为负数则仅移除a */ function f(record: ABCD) { const {a, b, ...result} = record; if (a !== undefined && a > 0) { result.a = a * 2; // 类型错误:Property 'a' does not exist on type '{ c?: number | undefined; d?: number | undefined; }' } else { result.b = b; // 类型错误:Property 'b' does not exist on type '{ c?: number | undefined; d?: number | undefined; }' } return result; }
现有方案局限
两种常见方案均不满足需求:
- 移除类型安全:违背类型检查的初衷,不符合要求;
- 修改运行时代码(如使用
delete):但该函数是JS库的TS移植版本,必须保留原运行时代码以便后续同步更新。
解决方案:编译时类型处理(不改动运行时代码)
可以通过类型断言和自定义返回类型在编译期解决类型错误,同时完全保留原运行时代码逻辑。核心思路是先将解构后的result断言为更宽松的类型以允许添加属性,最后返回时约束为正确的类型。
基础实现(保证类型安全且保留原代码)
type ABCD = Partial<{ a: number, b: number, c: number, d: number }>; /** 若a为正数则翻倍并移除b;若为负数则仅移除a */ function f(record: ABCD): ABCD { const {a, b, ...result} = record; // 将result断言为Partial<ABCD>,允许添加a/b属性,编译后无额外代码 const mutableResult = result as Partial<ABCD>; if (a !== undefined && a > 0) { mutableResult.a = a * 2; } else { mutableResult.b = b; } // 返回时转为原类型,保证类型一致性 return mutableResult as ABCD; }
更精确的返回类型(可选)
如果需要更精确的返回类型约束(明确哪些属性会被保留/修改),可以通过条件类型定义返回类型:
type ABCD = Partial<{ a: number, b: number, c: number, d: number }>; // 定义返回类型:根据输入的a属性情况,返回对应子集类型 type FReturnType<T extends ABCD> = T extends {a: number} ? T['a'] extends number ? T['a'] > 0 extends true ? Omit<T, 'b'> & {a: number} : Omit<T, 'a'> : T : T; /** 若a为正数则翻倍并移除b;若为负数则仅移除a */ function f<T extends ABCD>(record: T): FReturnType<T> { const {a, b, ...result} = record; const mutableResult = result as Partial<FReturnType<T>>; if (a !== undefined && a > 0) { mutableResult.a = a * 2 as FReturnType<T>['a']; } else { mutableResult.b = b as FReturnType<T>['b']; } return mutableResult as FReturnType<T>; }
注:由于TypeScript静态类型系统无法精确判断运行时的数值正负,上述条件类型是近似约束,但已能满足大部分类型安全场景。
内容的提问来源于stack exchange,提问作者Justin Grant
相关产品推荐
相关产品推荐

