以字幕脚本中的元组形式获取词典/对象键 [英] get dictionary/object keys as tuple in typescript

查看:8
本文介绍了以字幕脚本中的元组形式获取词典/对象键的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望从TS 3.1中的对象中获得具有正确类型文字的正确元组类型:

interface Person {
  name: string,
  age: number
}

// $ExpectType ['name','age']
type ObjectKeysTuple = ToTuple<keyof Person>

为什么?:

在使用Object.keys(dictionary)时获取正确的字符串文字元组

我找不到解决方案,因为keyof扩展为返回('name' | 'age')[]的Union,这绝对不是我们想要的。

type ObjectKeysTuple<T extends object> = [...Array<keyof T>]
type Test = ObjectKeysTuple<Person>
// no errors 🚨not good
const test: Test = ['age','name','name','name']

相关:

推荐答案

您提到的用例为Object.keys()提供了一个元组类型,它充满了危险,我建议不要使用它。

第一个问题是,类型脚本中的类型不是"exact"。也就是说,仅仅因为我有一个Person类型的值,并不意味着该值只包含nameage属性。想象一下:

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是具有额外superpowers属性的Person,而randomPerson是具有谁知道哪些额外属性的Person。您不能简单地说,作用于PersonObject.keys()将生成一个只有已知键的数组。这是such feature requests继续收到rejected的主要原因。

第二个问题与密钥排序有关。即使您知道您正在处理的确切类型包含接口中所有且仅已声明的属性,can't guaranteeObject.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引擎可能会按属性插入的顺序返回给您,但您不能指望这一点。当然,您也不能指望插入顺序与TypeScrip接口属性顺序相同。因此,您可能会将["age", "name"]视为["name", "age"]类型的对象,这可能不太好。


话虽如此,我喜欢处理类型系统,因此我决定编写代码,以类似于Matt McCutchen's answer to another question的方式将联合转换为元组。它也充满了危险,我建议不要这样做。以下是一些注意事项。这就是:

// 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"]

看起来它起作用了。

注意事项:对于任意大小的联合,您不能以编程方式完成此操作。TypeScrip不允许iterate over union types,所以这里的任何解决方案都是选择一些最大的联合大小(例如,六个协议)并处理最大大小的联合。我上面的代码有些迂回,您可以通过复制和粘贴来扩展这个最大大小。

另一个警告:它取决于编译器是否能够按顺序分析条件类型中的重载函数签名,以及编译器能否在保持顺序的同时将联合转换为重载函数。这两种行为都不一定能保证以相同的方式工作,因此每次有新版本的TypeScrip问世时,您都需要检查这一点。

最后警告:它没有经过太多测试,因此即使您保持打字版本不变,它也可能充满各种🐉有趣的陷阱。如果您对使用这样的代码是认真的,那么在考虑在生产代码中使用它之前,您需要对其进行大量测试。


总之,不要做我在这里展示的任何事情。好吧,希望这能帮上忙。祝你好运!

这篇关于以字幕脚本中的元组形式获取词典/对象键的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆