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

如何为接收Alpha类型对象集合的TypeScript函数正确类型标注?

问题:TypeScript多对象场景下Alpha类型的foo与bar关联校验失效

我们定义了Alpha类型,其中bar字段的类型由foo字段的具体字符串值决定。单个对象通过echo函数可以正确校验这种关联,但接收多个键值对对象的bravo函数无法校验每个对象的foo与bar的对应关系,预期的类型错误无法触发。

原代码示例

// We have this Alpha type and echo function...

type NoInfer<T> = [T][T extends unknown ? 0 : never]

interface Alpha<Foo extends string> {
  foo: Foo
  bar: `Depends on ${NoInfer<Foo>}`
}

declare const echo: <T extends string>(x: Alpha<T>) => void

echo({ foo: 'beta', bar: 'Depends on beta'})

// @ts-expect-error Trailing 2 is wrong
echo({ foo: 'beta', bar: 'Depends on beta 2'})

// Now we want a function (bravo) that takes a keyed index of Alphas...

declare const bravo: <T extends { [k: string]: Alpha<string> }>(xs: T) => void

bravo({
  one:  { foo: `1`,  bar: `Depends on 1` },
  // @ts-expect-error 1 !== 1x           <-- fails
  oneX: { foo: `1x`, bar: `Depends on 1` },
  two:  { foo: `2`,  bar: `Depends on 2` },
  // @ts-expect-error 2 !== 2x           <-- fails
  twoX: { foo: `2x`, bar: `Depends on 2` },
})

// how could this work?

解决方案

问题出在原bravo函数的泛型约束上:T extends { [k: string]: Alpha<string> }会让所有Alpha实例的Foo类型被统一推断为string,而非每个对象的具体字符串字面量,导致bar的类型被放宽为Depends on string,无法进行精确校验。

我们需要让bravo的泛型能够捕获每个对象的foo具体值,从而约束对应的bar类型。以下是两种可行方案:

方案1:直接用映射类型约束每个属性

type NoInfer<T> = [T][T extends unknown ? 0 : never]

interface Alpha<Foo extends string> {
  foo: Foo
  bar: `Depends on ${NoInfer<Foo>}`
}

declare const echo: <T extends string>(x: Alpha<T>) => void

// 修改bravo的泛型定义
declare const bravo: <T extends Record<string, { foo: string }>>(
  xs: { [K in keyof T]: Alpha<T[K]["foo"]> }
) => void

bravo({
  one:  { foo: `1`,  bar: `Depends on 1` },
  // @ts-expect-error 正确触发:bar值不匹配foo的"1x"
  oneX: { foo: `1x`, bar: `Depends on 1` },
  two:  { foo: `2`,  bar: `Depends on 2` },
  // @ts-expect-error 正确触发:bar值不匹配foo的"2x"
  twoX: { foo: `2x`, bar: `Depends on 2` },
})

方案2:定义专用的映射类型(复用性更强)

如果需要多次使用这种多Alpha对象的类型,可以单独定义映射类型:

type NoInfer<T> = [T][T extends unknown ? 0 : never]

interface Alpha<Foo extends string> {
  foo: Foo
  bar: `Depends on ${NoInfer<Foo>}`
}

// 定义Alpha对象的映射类型
type AlphaRecord<T extends Record<string, string>> = {
  [K in keyof T]: Alpha<T[K]>
}

declare const echo: <T extends string>(x: Alpha<T>) => void
declare const bravo: <T extends Record<string, string>>(xs: AlphaRecord<T>) => void

bravo({
  one:  { foo: `1`,  bar: `Depends on 1` },
  // @ts-expect-error 正确触发
  oneX: { foo: `1x`, bar: `Depends on 1` },
  two:  { foo: `2`,  bar: `Depends on 2` },
  // @ts-expect-error 正确触发
  twoX: { foo: `2x`, bar: `Depends on 2` },
})

原理说明

修改后的泛型定义会为每个键值对单独推断foo的字符串字面量类型,再通过Alpha<T[K]["foo"]>约束对应的bar必须匹配Depends on ${具体foo值}的模板字符串类型,从而实现每个对象的foo与bar的关联校验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 12:07:50