TypeScript 中的“keyof typeof”是啥意思?

Posted

技术标签:

【中文标题】TypeScript 中的“keyof typeof”是啥意思?【英文标题】:What does "keyof typeof" mean in TypeScript?TypeScript 中的“keyof typeof”是什么意思? 【发布时间】:2019-08-18 00:42:49 【问题描述】:

向我解释一下keyof typeof 在 TypeScript 中的含义

例子:

enum ColorsEnum 
    white = '#ffffff',
    black = '#000000',


type Colors = keyof typeof ColorsEnum;

最后一行相当于:

type Colors = "white" | "black"

但是它是如何工作的呢?

我希望 typeof ColorsEnum 返回类似 "Object" 的内容,然后 keyof "Object" 不会做任何有趣的事情。但我显然错了。

【问题讨论】:

【参考方案1】:

要了解 TypeScript 中 keyof typeof 的用法,首先需要了解什么是文字类型文字类型的联合。所以,我先解释一下这些概念,然后再详细解释keyoftypeof。之后,我会回到enum 回答问题中的问题。这是一个很长的答案,但例子很容易理解。


文字类型

TypeScript 中的文字类型是更具体的 stringnumberboolean 类型。例如,"Hello World"string,但 string 不是 "Hello World""Hello World" 是类型 string 的更具体的类型,所以它是一个字面量类型。

文字类型可以声明如下:

type Greeting = "Hello"

这意味着Greeting 类型的对象只能有一个string"Hello" 而没有其他string 值或任何其他类型的任何其他值,如以下代码所示:

let greeting: Greeting
greeting = "Hello" // OK
greeting = "Hi"    // Error: Type '"Hi"' is not assignable to type '"Hello"'

文字类型本身没有用,但是当与联合类型、类型别名和类型保护结合使用时,它们变得强大。

以下是文字类型联合的示例:

type Greeting = "Hello" | "Hi" | "Welcome"

现在Greeting 类型的对象可以具有"Hello""Hi""Welcome" 的值。

let greeting: Greeting
greeting = "Hello"       // OK
greeting = "Hi"          // OK
greeting = "Welcome"     // OK
greeting = "GoodEvening" // Error: Type '"GoodEvening"' is not assignable to type 'Greeting'

仅限keyof

keyof of some type T 为您提供了一个新类型,它是 文字类型的联合,这些文字类型是 T 的属性名称。结果类型是字符串的子类型。

例如,考虑以下interface

interface Person 
    name: string
    age: number
    location: string

Person 类型上使用keyof 运算符将为您提供一个新类型,如以下代码所示:

type SomeNewType = keyof Person

这个SomeNewType 是由Person 类型的属性组成的文字类型("name" | "age" | "location") 的联合。

现在您可以创建SomeNewType 类型的对象:

let newTypeObject: SomeNewType
newTypeObject = "name"           // OK
newTypeObject = "age"            // OK
newTypeObject = "location"       // OK
newTypeObject = "anyOtherValue"  // Error...

keyof typeof 一起在一个对象上

您可能已经知道,typeof 运算符为您提供对象的类型。 在上面的Person 接口示例中,我们已经知道类型,所以我们只需要对Person 类型使用keyof 运算符。

但是当我们不知道对象的类型或者我们只有一个值而不是像下面这样的那个值的类型时该怎么办?

const bmw =  name: "BMW", power: "1000hp" 

这是我们一起使用keyof typeof 的地方。

typeof bmw 为您提供类型: name: string, power: string

然后keyof 运算符为您提供文字类型联合,如以下代码所示:

type CarLiteralType = keyof typeof bmw

let carPropertyLiteral: CarLiteralType
carPropertyLiteral = "name"       // OK
carPropertyLiteral = "power"      // OK
carPropertyLiteral = "anyOther"   // Error...

keyof typeof enum

在 TypeScript 中,枚举在编译时用作类型,以实现常量的类型安全,但它们在运行时被视为对象。这是因为,一旦将 TypeScript 代码编译为 javascript,它们就会转换为普通对象。因此,上述对象的解释也适用于此。问题中OP给出的例子是:

enum ColorsEnum 
    white = '#ffffff',
    black = '#000000',

这里ColorsEnum 在运行时作为对象存在,而不是作为类型存在。所以,我们需要一起调用keyof typeof操作符,如下代码所示:

type Colors = keyof typeof ColorsEnum

let colorLiteral: Colors
colorLiteral = "white"  // OK
colorLiteral = "black"  // OK
colorLiteral = "red"    // Error...

就是这样!希望对您有所帮助。

【讨论】:

这是一个典型的 Stack Overflow 答案,应该用来向新用户解释如何回答问题。非常好,这应该是公认的答案。很棒。 我需要每月阅读这个答案。 这绝对是一个教科书式的回答,说明什么是好的回复。【参考方案2】:

keyof 接受一个对象类型并返回一个接受任何对象键的类型。

type Point =  x: number; y: number ;
type P = keyof Point; // type '"x" || "y"'

const coordinate: P = 'z' // Type '"z"' is not assignable to type '"x" | "y"'.

typeof 与 TypeScript 类型

typeof 在 javascript 对象上调用时的行为与在 typescript 类型上调用时的行为不同。

TypeScript 在运行时对 javascript 值调用时使用 javascript's typeof 并返回 "undefined", "object", "boolean", "number", "bigint", "string", "symbol", "function" 之一 TypeScript's typeof 在类型值上调用,但在类型表达式中也可以在 javascript 值上调用。它还可以推断 javascript 对象的类型,返回更详细的对象类型。
type Language = 'EN' | 'ES'; 
const userLanguage: Language = 'EN';
const preferences =  language: userLanguage, theme: 'light' ;

console.log(typeof preferences); // "object"
type Preferences = typeof preferences; // type 'language: 'EN''; theme: string; '

因为第二个 typeof preferences 在类型表达式中,它实际上是 TypeScript 自己的 typeof 被调用,而不是 javascript。

keyof 类型

因为keyof 是一个TypeScript 概念,我们将调用TypeScript 的typeof 版本。

keyof typeof 将推断 javascript 对象的类型并返回其键的联合类型。因为它可以推断出键的确切值,所以它可以返回它们的 literal types 的联合,而不仅仅是返回“字符串”。

type PreferenceKeys = keyof typeof preferences; // type '"language" | "theme"'

【讨论】:

【参考方案3】:

enum 创建一个实例化的object。使用typeof,我们得到了这个enum的自动生成类型。

现在我们可以使用keyof 获取所有索引,以确保Colors 只能包含其中一个。

【讨论】:

【参考方案4】:

关于 TypeScript 的常见误解

TypeScript 通常被描述为 JavaScript 运行时之上的类型层。好像类型和值存在于不同的平面上。然而,在 TypeScript 中,有些东西同时是类型 值。

这适用于:

类, 枚举, 命名空间。

什么时候可以使用keyof

keyof 关键字仅适用于类型级别。您不能将其应用于 JavaScript 值。

什么时候需要keyof typeof

当您同时处理类型和值(如类或枚举),但您特别感兴趣的是该值的类型时。

最简单的例子:

const foo =  bar: 42 ; // foo is a value
type Foo = typeof foo; // Foo is the type of foo

type KeyOfFoo = keyof Foo; // "keyof Foo" is the same as "keyof typeof foo", which is "bar"

一般来说,当你看到这个时:

type A = keyof typeof B;

typeof B 部分告诉 TypeScript 查看 B 的 type。您可以将其视为将 B 转换为它的类型。有点像将二维对象投射到一维空间。

由于typeof B 是一个类型,而不是一个值,我们现在可以在其上使用keyof

示例

类是类型和值。你可以打电话给他们,也可以在他们身上使用keyof

declare class Foo 
    static staticProperty: string;

    dynamicProperty: string;


type Constructor = typeof Foo;
type Instance = Foo;

type A = keyof Constructor; // "prototype" | "staticProperty"
type B = keyof Instance; // "dynamicProperty"

通过将typeofkeyof 一起使用,我们可以在针对instance 类型和constructor 类型使用keyof 之间切换。

【讨论】:

【参考方案5】:

为了查找任何值的类型,我们使用 typeof 操作。例如

const user = 
   getPersonalInfo(),
   getLocation()

这里的 user 是一个值,所以 typeof 操作符就派上用场了

type userType = typeof user

这里 userType 给出了用户是一个对象的类型信息,它有两个属性 getPersonalInfo 和 getLocation 并且都是返回 void 的函数

现在如果你想找到用户的密钥,你可以使用 keyof

type userKeys = keyof userType

上面写着 userKeys= 'getPersonalInfo'| 'getLocation'

请注意,如果您尝试获取用户的密钥,例如 type userKeys = keyof user,您将收到错误 'user' 引用了一个值,但在这里被用作类型。你是说'typeof user'吗?

【讨论】:

以上是关于TypeScript 中的“keyof typeof”是啥意思?的主要内容,如果未能解决你的问题,请参考以下文章

Typescript 入门手册之函数类型在 TypeScript 中的应用

Typescript 入门手册之函数类型在 TypeScript 中的应用

Typescript入门手册之引用类型在TypeScript中的应用

Typescript入门手册之引用类型在TypeScript中的应用

使用TypeScript中的TypeScript库

TypeScript中的privateprotected