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

TypeScript修改接口单个属性收窄类型的类型安全实现模式咨询

解决方案

核心思路通过类型安全的转换规则定义 + 对象展开语法自动拷贝其余属性,既避免手动写冗余的属性拷贝逻辑,又保证类型校验不丢失,新增待转换属性时可以触发TS报错。

通用工具实现

// 定义转换规则的类型:仅需要定义宽类型中需要收窄的属性的转换逻辑
type NarrowConverter<Wide, Narrow> = {
  [K in keyof Narrow as Narrow[K] extends Wide[K] ? never : K]: (value: Wide[K]) => Narrow[K]
}

// 生成narrow函数的工厂函数
function createNarrower<Wide, Narrow>(converter: NarrowConverter<Wide, Narrow>) {
  return function narrow(wideObj: Wide): Narrow {
    // 先展开原对象拷贝所有属性,再覆盖经过转换的属性
    return {
      ...wideObj,
      ...Object.fromEntries(
        Object.entries(converter).map(([key, convertFn]) => [key, convertFn(wideObj[key as keyof Wide])])
      )
    } as Narrow
    // 此处断言完全安全:converter的类型已经保证所有需要转换的属性都被正确转换,其余属性和Narrow类型天然兼容
  }
}

实际使用示例

interface WideFoo {
  x: string | RegExp;
  y: number;
  // 后续新增不需要转换的属性不需要改narrow逻辑
  extra: string;
}

interface NarrowFoo {
  x: RegExp;
  y: number;
  extra: string;
}

// 仅需要定义需要转换的属性的逻辑,不需要管其他属性
const narrowFoo = createNarrower<WideFoo, NarrowFoo>({
  x: (val) => val instanceof RegExp ? val : new RegExp(val)
})

// 使用方式和原来一致
declare const wf: WideFoo;
const nf = narrowFoo(wf);
// 类型正确:nf.x 是 RegExp 类型
nf.x.test("Hello")

方案优势

  • 无需手动拷贝无需转换的属性:对象展开语法自动复制所有原对象属性,仅覆盖需要转换的字段,接口新增普通属性时完全不需要修改narrow逻辑
  • 类型安全无遗漏:如果后续Wide接口新增了需要收窄的属性,只要在Narrow接口中定义了对应的窄类型,但是没有在转换规则中补充转换逻辑,TS会直接在createNarrower调用时报错,提示缺少对应属性的转换规则,不会出现漏转换的情况
  • 仅处理一层平面对象,符合需求
  • 转换逻辑集中维护,可扩展性高,即使接口有几十个属性,也只需要维护几个需要转换的字段的规则即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 04:06:08