您提到的用例,提出了一个元组类型Object.keys()
,充满危险,我建议不要使用它。
第一个问题是 TypeScript 中的类型不是"exact"。也就是说,只是因为我有一个 type 的值Person
,这并不意味着该值包含only the name
and age
特性。想象一下以下情况:
interface Superhero extends Person {
superpowers: string[]
}
const implausibleMan: Superhero = {
name: "Implausible Man",
age: 35,
superpowers: ["invincibility", "shape shifting", "knows where your keys are"]
}
declare const randomPerson: Person;
const people: Person[] = [implausibleMan, randomPerson];
Object.keys(people[0]); // what's this?
Object.keys(people[1]); // what's this?
注意如何implausibleMan
is a Person
额外superpowers
财产,以及randomPerson
is a Person
谁知道有什么额外的属性。你根本不能说Object.keys()
作用于Person
将产生一个数组only已知的键。这就是主要原因此类功能请求继续得到rejected.
第二个问题与键的顺序有关。即使您知道您正在处理包含所有内容的确切类型and only从接口声明的属性,你不能保证密钥将由以下方式返回Object.keys()
与接口的顺序相同。例如:
const personOne: Person = { name: "Nadia", age: 35 };
const personTwo: Person = { age: 53, name: "Aidan" };
Object.keys(personOne); // what's this?
Object.keys(personTwo); // what's this?
最合理的 JS 引擎将probably按照插入的顺序将属性交还给您,但您不能指望这一点。而且您当然不能指望插入顺序与 TypeScript 接口属性顺序相同。所以你很可能会治疗["age", "name"]
作为类型的对象["name", "age"]
,这可能不太好。
综上所述,我喜欢搞乱类型系统,所以我决定编写代码,以类似于以下的方式将联合转换为元组马特·麦卡琴对另一个问题的回答。它也充满危险,我建议不要这样做。以下注意事项。这里是:
// add an element to the end of a tuple
type Push<L extends any[], T> =
((r: any, ...x: L) => void) extends ((...x: infer L2) => void) ?
{ [K in keyof L2]-?: K extends keyof L ? L[K] : T } : never
// convert a union to an intersection: X | Y | Z ==> X & Y & Z
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
// convert a union to an overloaded function X | Y ==> ((x: X)=>void) & ((y:Y)=>void)
type UnionToOvlds<U> = UnionToIntersection<U extends any ? (f: U) => void : never>;
// convert a union to a tuple X | Y => [X, Y]
// a union of too many elements will become an array instead
type UnionToTuple<U> = UTT0<U> extends infer T ? T extends any[] ?
Exclude<U, T[number]> extends never ? T : U[] : never : never
// each type function below pulls the last element off the union and
// pushes it onto the list it builds
type UTT0<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT1<Exclude<U, A>>, A> : []
type UTT1<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT2<Exclude<U, A>>, A> : []
type UTT2<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT3<Exclude<U, A>>, A> : []
type UTT3<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT4<Exclude<U, A>>, A> : []
type UTT4<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT5<Exclude<U, A>>, A> : []
type UTT5<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTTX<Exclude<U, A>>, A> : []
type UTTX<U> = []; // bail out
让我们尝试一下:
type Test = UnionToTuple<keyof Person>; // ["name", "age"]
看起来有效。
注意事项:您不能以编程方式对任意大小的联合执行此操作。 TypeScript 不允许你迭代联合类型,因此这里的任何解决方案都将选择一些最大联合大小(例如,六个组成部分)并处理达到该大小的联合。我上面有些迂回的代码是为了让您可以通过复制和粘贴来扩展这个最大大小。
另一个警告:它取决于编译器能够按顺序分析条件类型中的重载函数签名,并且取决于编译器能够在保留顺序的同时将联合转换为重载函数。这些行为都不一定保证以相同的方式继续工作,因此每次新版本的 TypeScript 出现时,您都需要检查这一点。
最后的警告:它还没有经过太多测试,所以即使你保持 TypeScript 版本不变,它也可能充满各种有趣的陷阱。如果您真的想使用这样的代码,那么在考虑在生产代码中使用它之前,您需要对其进行大量测试。
总之,不要做我在这里展示的任何事情。好的,希望有帮助。祝你好运!