To comprehend the utilization of keyof typeof
in TypeScript, it is crucial to grasp the concept of literal types and union of literal types first. Let's delve into these concepts before delving into a detailed explanation of keyof
and typeof
. Subsequently, we will revisit enum
to address the question at hand. Although lengthy, the examples provided are straightforward for better understanding.
Literal Types
In TypeScript, literal types are more specific variations of string
, number
, or boolean
. For instance, while "Hello World"
is a string
, a generic string
does not equate to "Hello World"
. Hence, "Hello World"
qualifies as a type of its own within the string
category—a literal type.
A declaration of a literal type looks like this:
type Greeting = "Hello"
This signifies that an object of type Greeting
can exclusively hold the value "Hello"
, rejecting any other string
value or values of different types, as demonstrated below:
let greeting: Greeting
greeting = "Hello" // Works fine
greeting = "Hi" // Error: Type '"Hi"' cannot be assigned to type '"Hello"'
While standalone, literal types may seem limited, their potency amplifies when merged with union types, type aliases, and type guards.
Below is an illustration of a union of literal types:
type Greeting = "Hello" | "Hi" | "Welcome"
Consequently, an object of type Greeting
embodies either "Hello"
, "Hi"
, or "Welcome"
.
let greeting: Greeting
greeting = "Hello" // Fine
greeting = "Hi" // Fine
greeting = "Welcome" // Fine
greeting = "GoodEvening" // Error: Type '"GoodEvening"' cannot be assigned to 'Greeting'
keyof
Only
The keyof
operator on a type T
produces a fresh type comprising a union of literal types, represented by the property names within T
. This resultant type is a subset of string.
Consider the subsequent interface
:
interface Person {
name: string
age: number
location: string
}
Employing the keyof
operator on the type
Person</code} yields the ensuing type:</p>
<pre><code>type SomeNewType = keyof Person
This newly formed SomeNewType
encapsulates a union of literal types (
"name" | "age" | "location"
) extracted from the properties of
Person
.
Objects conforming to
SomeNewType</code} can now be initiated:</p>
<pre><code>let newTypeObject: SomeNewType
newTypeObject = "name" // Enforced
newTypeObject = "age" // Enforced
newTypeObject = "location" // Enforced
newTypeObject = "anyOtherValue" // Rejected...
keyof typeof
Cohesion within an Object
The typeof
operator retrieves the type of an object.
In the aforementioned example featuring the Person
interface, knowing the type permitted us to employ the keyof
operator on Person
.
However, situations arise where an object's type is unknown or only a value is available without the corresponding type, exemplified here:
const bmw = { name: "BMW", power: "1000hp" }
This scenario necessitates the united application of keyof typeof
.
typeof bmw
identifies the type: { name: string, power: string }
Subsequently, employing the keyof
operator engenders a union of literal types, depicted in the ensuing code snippet:
type CarLiteralType = keyof typeof bmw
let carPropertyLiteral: CarLiteralType
carPropertyLiteral = "name" // Permissible
carPropertyLiteral = "power" // Permissible
carPropertyLiteral = "anyOther" // Rejected...
keyof typeof
Utilization on an enum
Within TypeScript, enums primarily serve as compile-time types to reinforce constant type integrity. Post compilation from TypeScript to JavaScript, they become basic objects. Consequently, similar principles apply to enum objects. The following excerpt mirrors the OP's query:
enum ColorsEnum {
white = '#ffffff',
black = '#000000',
}
Given that ColorsEnum
exists as a runtime object rather than merely a type, combining keyof typeof
is indispensable:
type Colors = keyof typeof ColorsEnum
let colorLiteral: Colors
colorLiteral = "white" // Valid
colorLiteral = "black" // Valid
colorLiteral = "red" // Rejected...