Что означает keyof typeof в TypeScript?

Пример:

Объясните мне, что означает keyof typeof в TypeScript

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

type Colors = keyof typeof ColorsEnum;

Последняя строка эквивалентна:

type Colors = "white" | "black"

Но как это работает?

Я ожидал, что typeof ColorsEnum вернет что-то вроде "Object", а затем keyof "Object" не сделает ничего интересного. Но я явно ошибаюсь.


person user1283776    schedule 27.03.2019    source источник
comment
Было бы полезно для сообщества, если бы вы могли выделить несколько секунд, чтобы отметить ответ как правильный.   -  person rmcsharry    schedule 12.01.2021


Ответы (5)


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 использует typeof в javascript при вызове значений javascript. во время выполнения и возвращает одно из "undefined", "object", "boolean", "number", "bigint", "string", "symbol", "function"
  • typeof TypeScript вызывается для значений типа, но также может вызываться для значений 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 находится в выражении типа, на самом деле вызывается собственный typeof TypeScript, а не javascript.

ключ типа

Поскольку keyof является концепцией TypeScript, мы будем называть его версией typeof.

keyof typeof определит тип объекта javascript и вернет тип, являющийся объединением его ключей. Поскольку он может определять точное значение ключей, он может возвращать объединение их литеральных типов. вместо того, чтобы просто возвращать строку.

type PreferenceKeys = keyof typeof preferences; // type '"language" | "theme"'
person James EJ    schedule 29.01.2021

Чтобы понять, как keyof typeof используется в Typescript, сначала вам нужно понять, что такое буквальные типы и объединение буквальных типов. Итак, я сначала объясню эти концепции, а затем подробно объясню keyof и typeof по отдельности. После этого я вернусь к enum, чтобы ответить на вопросы, заданные в вопросе. Это длинный ответ, но примеры легко понять.


Буквальные типы

Типы литералов в Typescript - это более конкретные типы string, number или boolean. Например, "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 некоторого типа T дает вам новый тип, который является объединением литеральных типов, и эти литеральные типы являются именами свойств T. Результирующий тип - это подтип строки.

Например, рассмотрим следующий interface:

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

Использование оператора keyof для типа Person даст вам новый тип, как показано в следующем коде:

type SomeNewType = keyof Person

Этот SomeNewType представляет собой объединение литеральных типов ("name" | "age" | "location"), состоящее из свойств типа Person.

Теперь вы можете создавать объекты типа SomeNewType:

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

keyof typeof вместе на объекте

Как вы, возможно, уже знаете, оператор typeof указывает тип объекта. В приведенном выше примере интерфейса Person мы уже знали тип, поэтому нам просто нужно было использовать оператор keyof для типа Person.

Но что делать, если мы не знаем тип объекта или у нас просто есть значение, а не тип этого значения, как показано ниже?

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 on an 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...

Вот и все! Надеюсь, это поможет.

person Yogesh Umesh Vaity    schedule 06.07.2020
comment
Это типичный ответ на вопрос о переполнении стека, который следует использовать, чтобы объяснить новым пользователям, как отвечать на вопрос. Невероятно хорошо, и это должен быть принятый ответ. Превосходно. - person dudewad; 06.08.2020

enum создает экземпляр object. С typeof мы получаем автоматически сгенерированный тип этого enum.

Теперь мы можем получить все индексы с помощью keyof, чтобы убедиться, что Colors может содержать только один из них.

person Marvin Fischer    schedule 27.03.2019

Распространенное заблуждение о 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. Вы можете думать об этом как о приведении 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 для типа instance и типа конструктора.

person Karol Majewski    schedule 14.07.2020

Для нахождения типа любых значений мы используем операцию typeof. Например,

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

Здесь пользователь - это значение, поэтому здесь пригодится оператор typeof

type userType = typeof user

Здесь userType дает информацию о типе, что пользователь является объектом, который имеет два свойства getPersonalInfo и getLocation, и оба являются функциями, возвращающими void

Теперь, если вы хотите найти ключи пользователя, вы можете использовать keyof

type userKeys = keyof userType

в котором написано userKeys = 'getPersonalInfo' | getLocation

Будьте осторожны, если вы попытаетесь получить ключ пользователя, например type userKeys = keyof user, вы получите сообщение об ошибке «пользователь» относится к значению, но здесь используется как тип. Вы имели в виду "тип пользователя"?

person Akash Singh    schedule 14.09.2020