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

TypeScript中如何将窄类型推断扩展到对象的每个属性?

问题描述

我们定义依赖函数(类型DepFunc):它有两种形式,要么是直接的number到number函数,要么是包含deps和build的对象。其中deps的键为依赖项名称,build函数接收与deps键匹配的参数对象,返回number到number的函数。类型定义如下:

type DepFunc<Keys extends string> 
  = { deps: {[K in Keys]: true}, 
      build: (dep: {[K in Keys]: number}) => (n: number) => number
    } | ((n: number) => number)

处理单个依赖函数的foo函数能正常提供类型检查:

function foo<Keys extends string>(arg: DepFunc<Keys>) {
   return arg
}

const r = foo({deps: {p: true}, build: dep => n => n + dep.p})
const s = foo(n => n + 1)

比如将dep.p改为dep.q时,编译器会提示dep不存在q属性,类型检查有效。

但当需要处理多个依赖函数组成的对象时,最初定义的bar函数失去了类型检查能力:

type DepFuncs<TopKeys extends string, SubKeys extends {[K in TopKeys]: string}> 
   = {[K in TopKeys]: DepFunc<SubKeys[K]>}

function bar<TopKeys extends string, SubKeys extends {[K in TopKeys]: string}>(
   par: DepFuncs<TopKeys, SubKeys>
) {
   return par
}

调用示例:

const t = bar({func: n => n + 1, 
               another: {deps: {p: true}, build: dep => n => n + dep.p}})

此时如果把dep.p改成dep.q,TypeScript不会报错——因为编译器将SubKeys的每个值都推断为宽泛的string,而非像foo那样窄化为具体的'p'。

需要重新参数化DepFuncs和/或bar,恢复每个属性的窄类型推断。


解决方案

核心问题在于原定义中用SubKeys统一管理所有顶层键的依赖键,导致TypeScript无法单独推断每个属性的具体依赖键。我们可以去掉SubKeys类型参数,让TypeScript为每个属性单独推断DepFunc的泛型参数:

type DepFunc<Keys extends string> 
  = { deps: {[K in Keys]: true}, 
      build: (dep: {[K in Keys]: number}) => (n: number) => number
    } | ((n: number) => number)

// 直接接收包含多个DepFunc的对象,让TypeScript自动推断每个属性的具体依赖键
function bar<T extends Record<string, DepFunc<any>>>(arg: T) {
  return arg;
}

// 测试用例
const t = bar({
  func: n => n + 1,
  another: {deps: {p: true}, build: dep => n => n + dep.p}
});

// 错误示例:将dep.p改为dep.q时,编译器会报错:属性'q'在类型'{ p: number; }'上不存在
// const t = bar({
//   func: n => n + 1,
//   another: {deps: {p: true}, build: dep => n => n + dep.q}
// });

这样修改后,TypeScript会为每个属性单独推断DepFunc的Keys参数,恢复了精确的类型检查:当build函数引用deps中不存在的键时,编译器会立即报错。

如果需要更严格的类型约束,可以添加辅助类型确保输入对象的合法性:

type DepFunc<Keys extends string> = 
  | ((n: number) => number)
  | {
      deps: Record<Keys, true>;
      build: (deps: Record<Keys, number>) => (n: number) => number;
    };

// 约束输入对象的每个属性都是合法的DepFunc
type ValidDepFuncs = Record<string, DepFunc<string>>;

function bar<T extends ValidDepFuncs>(arg: T) {
  return arg;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 02:25:54