解释一下TypeScript中的keyof typeof是什么意思

例子:

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

type Colors = keyof typeof ColorsEnum;

最后一行相当于:

type Colors = "white" | "black"

但它是如何工作的呢?

我希望typeof ColorsEnum返回类似“对象”的东西,然后keyof“对象”不做任何有趣的事情。但我显然错了。


当前回答

关于TypeScript的常见误解

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

这适用于:

类, 枚举, 名称空间。

什么时候可以使用keyof?

关键字keyof仅在类型级别上有效。不能将其应用于JavaScript值。

你什么时候需要keyof typeof?

当你在处理一个同时是类型和值的东西(比如类或enum),但你特别感兴趣的是该值的类型是什么。

最简单的例子:

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的类型。你可以把它看作是将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"

通过同时使用typeof和keyof,我们可以在针对实例类型和构造函数类型使用keyof之间进行切换。

其他回答

枚举创建一个实例化的对象。使用typeof,我们得到这个枚举的自动生成类型。

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

要理解TypeScript中keyof typeof的用法,首先你需要理解什么是文字类型和文字类型的并集。因此,我将首先解释这些概念,然后分别详细解释keyof和typeof。在那之后,我将回到enum来回答问题中所问的问题。这是一个很长的回答,但是例子很容易理解。


文字类型

TypeScript中的文字类型是更具体的字符串、数字或布尔类型。例如,“Hello World”是一个字符串,但字符串不是“Hello World”。"Hello World"是一种更具体的类型字符串,所以它是一个文字类型。

文字类型可以这样声明:

type Greeting = "Hello"

这意味着Greeting类型的对象只能有一个字符串值“Hello”,而没有其他字符串值或任何其他类型的任何其他值,如下所示:

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只

某些类型T的keyof给出了一个新的类型,它是文字类型的联合,这些文字类型是T的属性名称。结果类型是string的子类型。

例如,考虑以下接口:

interface Person {
    name: string
    age: number
    location: string
}

在Person类型上使用keyof操作符会给你一个新的类型,如下所示:

type SomeNewType = keyof Person

这个someenewtype是由类型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类型

在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...

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类型

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

TypeScript在运行时对javascript值调用时使用javascript的typeof,并返回"undefined", "object", "boolean", "number", "bigint", "string", "symbol", "function" TypeScript的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首选项是在类型表达式中,所以实际上是TypeScript自己的typeof被调用,而不是javascript的typeof。

keyof typeof

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

Keyof typeof将推断javascript对象的类型,并返回其键的并集类型。因为它可以推断键的确切值,所以它可以返回它们的文字类型的并集,而不仅仅返回“字符串”。

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

关于TypeScript的常见误解

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

这适用于:

类, 枚举, 名称空间。

什么时候可以使用keyof?

关键字keyof仅在类型级别上有效。不能将其应用于JavaScript值。

你什么时候需要keyof typeof?

当你在处理一个同时是类型和值的东西(比如类或enum),但你特别感兴趣的是该值的类型是什么。

最简单的例子:

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的类型。你可以把它看作是将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"

通过同时使用typeof和keyof,我们可以在针对实例类型和构造函数类型使用keyof之间进行切换。

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

const user = {
   getPersonalInfo(){},
   getLocation(){}
}

这里user是一个值,所以这里typeof操作符很方便

type userType = typeof user

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

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

type userKeys = keyof userType

它说userKeys= 'getPersonalInfo'| 'getLocation'

注意,如果你试图获取用户的密钥类型userKeys = keyof user,你会得到一个错误'user'指的是一个值,但在这里被用作类型。你是指“类型用户”吗?