typescript - 数组中可能有泛型约束吗?
是否可以让 TypeScript 使用第一个参数来限制第二个索引?
例如:
type Data = {
FOO: number
BAR: string
}
type DataKeys = keyof Data
type SpecialArray<T extends DataKeys = DataKeys> = [T, Data[T]]
// I want both of these array to fail because the 2nd argument's type
// is determined by the first argument
const arr1: SpecialArray = ['FOO', 'wrong'] // 2nd argument should be `number`
const arr2: SpecialArray = ['BAR', 666] // 2nd argument should be `string`
// This works well with functions
const specialFunction = <T extends DataKeys = DataKeys>(key: T, value: Data[T]) => {
return
}
specialFunction('FOO', 'wrong')
specialFunction('BAR', 666)
游乐场链接
答案#1
是也不是。
在这种情况下:
type SpecialArray<T extends DataKeys = DataKeys> = [T, Data[T]]
const arr1: SpecialArray = ['FOO', 'wrong'] // no error
你省略了通用参数,因此使用了默认值。由于默认类型 DataKeys 包含所有键,因此结果值类型是 number | string。
然而,所有泛型参数必须分配一个类型,所以如果你只是移除默认值,是行不通的:
type SpecialArray<T extends DataKeys> = [T, Data[T]]
const arr1: SpecialArray = ['FOO', 'wrong'] // error
// Generic type 'SpecialArray' requires 1 type argument(s).(2314)
如果你指定泛型参数,它确实有效:
// errors as expected
const arr1: SpecialArray<'FOO'> = ['FOO', 'wrong'] // 2nd argument should be `number`
const arr2: SpecialArray<'BAR'> = ['BAR', 666] // 2nd argument should be `string`
但那并不完全好看。
遗憾的是,你无法仅从 TypeScript 中的简单赋值推断出泛型参数。
对此的典型解决方法是使用一个函数来为你创建对象。正如你所注意到的,函数对此处理得更好。
function makeSpecialArray<
T extends DataKeys
>(key: T, value: Data[T]): SpecialArray<T> {
return [key, value]
}
// these are type errors, and the objects they create are SpecialArray<SomeKey>
const arr1 = makeSpecialArray('FOO', 'wrong') // 2nd argument should be `number`
const arr2 = makeSpecialArray('BAR', 666) // 2nd argument should be `string`
操场
答案#2
在这种情况下,你实际上只希望 SpecialArray 是一个特定的联合类型,形式为 ["FOO", number] | ["BAR", string]。 这满足了你所有的需求:
type SpecialArray = ["FOO", number] | ["BAR", string]
const arr1: SpecialArray = ['FOO', 'wrong'] // error
const arr2: SpecialArray = ['BAR', 666] // error
const arr3: SpecialArray = ['FOO', 123] // okay
const arr4: SpecialArray = ['BAR', 'right'] // okay
所以这里的问题只是“我如何从 Data 计算 SpecialArray?”
没有理由让 SpecialArray<T> 在 T 上保持泛型,但如果你确实让它在 T 上泛型化,你会希望将类型操作分布到 T 中的任何联合类型上。你当前的版本只是生成了一个单一的元组类型:
type SpecialArray<T extends DataKeys = DataKeys> = [T, Data[T]]
type Test = SpecialArray;
// type Test = [keyof Data, string | number]
你可以使用分配条件类型将类型操作分配到联合类型上:
type SpecialArray<T extends DataKeys = DataKeys> =
T extends any ? [T, Data[T]] : never;
type Test = SpecialArray;
// type Test = ["FOO", number] | ["BAR", string]
T extends any ? XXX : never 看起来像一个产生 XXX 的无操作,但它具有将 DataKeys 拆分为 "FOO" 和 "BAR" 的预期效果,分别计算每个元组类型,然后再将它们联合起来。
但正如我所说,你不需要让 SpecialArray 是泛型;你可以直接通过被称为“分配对象类型”(在 ms/TS#47109 中提出)的方法从 Data 计算它,在这种方法中,你对 DataKeys 中的每个键创建一个映射类型,其中对应的属性是你想要的元组类型,然后立即使用 DataKeys 对其进行索引,以获取所有属性的联合类型。像这样:
type SpecialArray = { [K in DataKeys]: [K, Data[K]] }[DataKeys];
// type SpecialArray = ["FOO", number] | ["BAR", string]
所以这可能是我在这里的建议;将 SpecialArray 制作成一个特定的(也就是说,非泛型的)分发对象类型。并且如果 Data 发生更新,SpecialArray 将会自动更新:
type Data = {
FOO: number
BAR: string
BAZ: boolean
}
// type SpecialArray = ["FOO", number] | ["BAR", string] | ["BAZ", boolean];
代码的 Playground 链接
