打字稿:基于类型的属性键

时间:2021-04-08 16:28:39

标签: typescript typescript-generics

是否可以根据传递的泛型类型定义属性名称?

type Foo<T> = {
  // magic goes here
  someProp: boolean
};

enum MyEnum {
  red = 'red',
  blue= 'blue',
}

const d: Foo<MyEnum.blue> = null;
d.someProp
d.blue // Property 'blue' does not exist on type 'Foo<MyEnum.blue>'.ts(2339)

一直在查看文档,但我无法弄清楚。

1 个答案:

答案 0 :(得分:1)

您可以使用 mapped type 在给定其键和关联值的类型的情况下动态生成对象类型。例如,类型 {[P in "x" | "y"]: [P]} 产生类型 {x: ["x"], y: ["y"]}。对于值类型不依赖于键的情况,您可以使用内置的 Record<K, V> 实用程序类型,详细说明 here。所以 Record<"x" | "y", number> 等价于 {x: number, y: number}

在您的情况下,您希望 Foo<T> 拥有密钥 T,这意味着我们需要将 constrain T 设为 keylike 类型(即 {{1} },或者您可以使用 built in PropertyKey alias)。在这种情况下,string | number | symbol 是比 K 更传统的参数名称。让我重新开始:

您希望 TFoo<K extends PropertyKey> 作为键。我不确定你想要那个键的值类型;现在我只使用 any, the ultimate ?‍♂️ type。并且您还希望 KFoo<K> 键处有一个 boolean 属性。所以它应该是 both "someProp"and 一个 Record<K, any>。这意味着您希望它成为其中的 intersection

{someProp: boolean}

您可以验证这是否有效:

type Foo<K extends PropertyKey> = {
  someProp: boolean
} & Record<K, any>

请注意,您不能将 const d: Foo<MyEnum.blue> = { blue: 123, someProp: true } 变成 Foo。在 interfaceclass 类型中,编译器需要在使用之前静态地知道键:

interface

对于 interface Oops<K extends PropertyKey> extends Foo<K> { } // error! // -----> ~~~~~~ // An interface can only extend an object type or // intersection of object types with statically known members. 的特定规范,您可以创建一个接口:

K

Playground link to code