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

