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
相关产品推荐
相关产品推荐

