You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为克隆方法正确声明TypeScript的RecursiveOmit与RecursivePick?

问题分析与修正

问题描述

试图为基于JSON.parse(JSON.stringify(obj, ['myProperty']))的克隆方法实现RecursivePick和RecursiveOmit类型,但类型校验不符合预期:

  • 调用clone(c, ['b'])后,cc.b.a、cc.a.c本应不存在却未触发错误
  • 调用cloneWithBlackList(c, ['b'])后,cb.b.b本应不存在却未触发错误

问题根源

  1. 类型定义逻辑错误:
    原代码中仅将不符合条件的属性类型设为never,但TypeScript不会自动移除never类型的属性,只会在赋值时报类型不匹配,而非提示属性不存在。正确的递归Pick/Omit需要完全移除不符合条件的属性,而非修改其类型。

  2. 递归处理的范围偏差:
    原类型对所有属性都进行递归处理,包括被标记为never的属性,导致类型系统仍认为这些属性存在。

修正后的代码

// 提取对象所有层级的key,用于约束传入的属性列表
type RecursiveKey<T> = T extends object 
  ? keyof T | RecursiveKey<T[keyof T]> 
  : never;

// 递归Pick:只保留所有层级中属于K的属性
type RecursivePick<T, K extends PropertyKey> = 
  T extends object 
    ? {
        [P in keyof T as P extends K ? P : never]: 
          T[P] extends object 
            ? RecursivePick<T[P], K> 
            : T[P]
      }
    : T;

// 递归Omit:移除所有层级中属于K的属性
type RecursiveOmit<T, K extends PropertyKey> = 
  T extends object 
    ? {
        [P in keyof T as P extends K ? never : P]: 
          T[P] extends object 
            ? RecursiveOmit<T[P], K> 
            : T[P]
      }
    : T;

const clone = <T, K extends RecursiveKey<T>>(object: T, whiteListedProperties: K[]): RecursivePick<T, K> => { 
    return JSON.parse(JSON.stringify(object, whiteListedProperties as (string | number)[])); 
};

const cloneWithBlackList = <T, K extends RecursiveKey<T>>(object: T, blackListedProperties: K[]): RecursiveOmit<T, K> => {
  return JSON.parse(JSON.stringify(object, (key: string, value: any): any => 
    blackListedProperties.includes(key as K) ? undefined : value
  ));
};

// 测试用例
const c = {
    a: {
        a: 1,
        b: 2,
        c: 3
    },
    b: {
        a: 1,
        b: 2
    }
};

const cc = clone(c, ['b']);
cc.b.a = 2; // ✅ 报错:属性'a'不存在
cc.b.b = 2; // ✅ 正常
cc.a.c = 2; // ✅ 报错:属性'c'不存在

const cb = cloneWithBlackList(c, ['b']);
cb.a.a = 2; // ✅ 正常
cb.b.b = 3; // ✅ 报错:属性'b'不存在
cb.a.c = 2; // ✅ 正常

关键修正点

  • 使用映射类型的条件过滤(as子句):通过P extends K ? P : never直接过滤掉不需要的属性,让类型系统认为这些属性不存在,而非仅修改其类型为never。
  • 递归逻辑仅针对保留下来的属性执行,确保所有层级的属性都严格遵循Pick/Omit规则。

内容的提问来源于stack exchange,提问作者Darkwander

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 07:35:00