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

TypeScript实现数组属性值约束另一属性取值并保留精确类型推导

实现带动态约束的精确类型构造函数

我们先明确基础前提:项目中已存在包含foo、bar、fizz三个属性的类型A,通常通过通用DeepReadonly<T>工具类型递归将A的所有属性标记为只读,原有基础类型定义如下:

type DeepReadonly<T> =
  T extends (infer R)[] ? DeepReadonlyArray<R> :
  T extends Function ? T :
  T extends object ? DeepReadonlyObject<T> :
  T

interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}

type DeepReadonlyObject<T> = {
  readonly [P in keyof T]: DeepReadonly<T[P]>
}

type MutableA<T extends number[] = number[]> = {
    foo: T
    bar: T[number]
    fizz: string
}

type A = DeepReadonly<MutableA>

常规实现中,一般通过createA<TA>这个TS通用变通方案创建完全显式类型的A实例,原有实现和类型测试代码如下:

const createA = <TA extends A>(a: TA): TA => a

const a = createA({
    foo: [1, 2, 3],
    bar: 1,
    fizz: 'buzz'
} as const)

// 测试createA的精确类型推导能力
type T0 = typeof a['foo'] // [1,2,3]
type T1 = typeof a['bar'] // 1
type T2 = typeof a['fizz'] // "buzz"

原有实现的缺陷是:MutableA的默认泛型参数为number[],TS做类型校验时会提前将foo的类型宽化为number[],导致bar仅被约束为number类型,不会校验bar取值是否属于foo数组的元素集合。


修改方案

不需要改动原有DeepReadonly系列工具类型和基础类型定义,仅需调整createA的泛型约束逻辑即可,核心改动点:

  • 单独为foo属性声明泛型参数,优先捕获传入的foo的精确只读元组类型,避免类型宽化
  • 用捕获到的元组成员类型T[number]直接约束bar的取值范围,实现动态校验和IDE智能提示
  • 返回值直接使用typeof a,完整保留入参的所有精确字面量类型,不丢失推导精度

修改后的完整代码如下:

// 原有工具类型和基础A类型定义保持不变
type DeepReadonly<T> =
  T extends (infer R)[] ? DeepReadonlyArray<R> :
  T extends Function ? T :
  T extends object ? DeepReadonlyObject<T> :
  T

interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}

type DeepReadonlyObject<T> = {
  readonly [P in keyof T]: DeepReadonly<T[P]>
}

type MutableA<T extends number[] = number[]> = {
    foo: T
    bar: T[number]
    fizz: string
}

type A = DeepReadonly<MutableA>

// 重写createA的泛型约束
const createA = <T extends readonly number[]>(
  a: {
    readonly foo: T
    readonly bar: T[number]
    readonly fizz: string
  }
): typeof a => a

// 合法用例:bar属于foo元素集合,类型推导正常
const a = createA({
    foo: [1, 2, 3],
    bar: 1,
    fizz: 'buzz'
} as const)

// 非法用例:bar值4不在foo元素集合中,TS会直接抛出类型错误
// const badCase = createA({
//   foo: [1,2,3],
//   bar: 4,
//   fizz: 'test'
// } as const)

// 验证类型推导结果,和原有能力完全一致
type T0 = typeof a['foo'] // readonly [1, 2, 3]
type T1 = typeof a['bar'] // 1
type T2 = typeof a['fizz'] // "buzz"

实现效果

  • 编辑bar属性时,TS智能提示会直接列出foo数组包含的所有字面量值作为可选选项
  • 传入的bar值不在foo元素集合时,TS会实时抛出类型错误,完成强制校验
  • 返回值完整保留传入对象的精确字面量类型,没有任何类型宽化,和原有实现的类型推导能力完全一致

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 08:45:32