TypeScript Index Types and the keyof Keyword

Index types and keyof are powerful tools in TypeScript for manipulating object types.

They allow us to dynamically access object properties and create flexible type mappings.


SVG Diagram: How Index Types and keyof Work Background Title How Index Types and keyof Work The keyof Operator The keyof Operator type Keys = keyof User // "id" | "name" | "email" Arrow Index Access Types Index Access Types type Value = User["name"] // string Arrow Mapped Types Mapped Types type Partial<T> {[P in keyof T]?: T[P]} Lower half: Use cases Use Cases for Index Types Scenario 1 Dynamic property access Scenario 2 Type-safe object operations Scenario 3 Create generic utility types Arrow marker

Why Do We Need Index Types?

In JavaScript, we often need to dynamically access object properties.

For example, a function may need to get all the keys of an object, or access the corresponding value type based on a key.

Index types and keyof allow us to express this dynamism in the type system while maintaining type safety.

Concept:Index types are a class of types that allow dynamic access to object properties, and keyof is used to obtain a union type composed of all keys of an object type.


The keyof Operator

The keyof Operator

Example

// Define the user type
interface User {
    id: number;       // User ID
    name: string;     // Username
    email: string;    // Email
    age?: number;     // Age (optional)
}

// Use keyof to get the union type of all keys
// Result: "id" | "name" | "email" | "age"
type UserKeys = keyof User;

// Test keyof
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const user: User = {
    id: 1,
    name: "Alice",
    email: "alice@example.com"
};

// Get the name property
const userName: string = getProperty(user, "name");
console.log("Username: " + userName);

Output:

用户名: Alice

Explanation:keyof returns a union type of key name literals, not a string type.


Index Access Types

Using Index Access Types, you can get the type of an object property.

Example

// Define the user type
interface User {
    id: number;
    name: string;
    email: string;
}

// Use index access types to get property types
type UserId = User["id"];      // number
type UserName = User["name"];  // string

// You can use a union type to access multiple keys
type UserIdAndName = User["id" | "name"];  // number | string

// Use keyof to get the union of all property types
type AllUserValues = User[keyof User];  // number | string

// Practical usage example
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const user: User = { id: 1, name: "Bob", email: "bob@test.com" };

// Get the id type
const idValue: number = getValue(user, "id");
console.log("ID: " + idValue);

// Get the name type
const nameValue: string = getValue(user, "name");
console.log("Name: " + nameValue);

Index access:Using square brackets [] you can access the type of a specific property of an object type; it is very powerful and flexible.


Mapped Types Basics

Mapped types allow you to create new types based on existing types by iterating over all their keys.

Example

// Define the user type
interface User {
    id: number;
    name: string;
    email: string;
    age: number;
}

// Make all properties optional
type PartialUser = Partial<User>;

// Make all properties readonly
type ReadonlyUser = Readonly<User>;

// Custom mapped type: make all properties optional and stringify them
type Stringify<T> = {
    [P in keyof T]: string;
};

type StringifiedUser = Stringify<User>;

// Practical usage example
const partialUser: PartialUser = {
    id: 1,
    name: "Alice"
    // email and age are optional
};

const readonlyUser: ReadonlyUser = {
    id: 1,
    name: "Bob",
    email: "bob@test.com",
    age: 25
};
// readonlyUser.name = "Charlie"; // Error: readonly

console.log("Partial user: " + partialUser.name);
console.log("Readonly user: " + readonlyUser.name);

Mapped types:Using the [P in keyof T] syntax to iterate over all keys of a type is the foundation for creating utility types.


Constraining the Type of Keys

Use keyof and generic constraints to restrict the keys a function accepts.

Example

// Define a configuration type
interface Config {
    apiUrl: string;
    timeout: number;
    retry: boolean;
}

// Can only get existing keys
function getConfigValue<T, K extends keyof T>(
    config: T,
    key: K
): T[K] {
    return config[key];
}

// Define the configuration
const config: Config = {
    apiUrl: "https://api.example.com",
    timeout: 5000,
    retry: true
};

// Correct: the key exists
const url: string = getConfigValue(config, "apiUrl");
const timeoutVal: number = getConfigValue(config, "timeout");

// Error: the key does not exist (TypeScript will report an error)
// const invalid = getConfigValue(config, "unknown");

console.log("API URL: " + url);
console.log("Timeout: " + timeoutVal);

Constraint:K extends keyof T ensures that the passed key must be a key that exists on the object, preventing runtime errors.


Getting Only Properties of a Specific Type

Extract property keys of a specific type from an object type.

Example

// Define a mixed type
interface Mixed {
    id: number;
    name: string;
    age: number;
    email: string;
    active: boolean;
}

// Extract all keys of string type
type StringKeys<T> = {
    [K in keyof T]: T[K] extends string ? K : never;
}[keyof T];

// Extract all keys of number type
type NumberKeys<T> = {
    [K in keyof T]: T[K] extends number ? K : never;
}[keyof T];

// Test
type StringProps = StringKeys<Mixed>;  // "name" | "email"
type NumberProps = NumberKeys<Mixed>;  // "id" | "age"

// Practical application: get the value of a string property
function getStringProps<T, K extends StringKeys<T>>(
    obj: T,
    keys: K[]
): T[K][] {
    return keys.map(key => obj[key]);
}

const mixed: Mixed = {
    id: 1,
    name: "Alice",
    age: 25,
    email: "alice@test.com",
    active: true
};

const strings = getStringProps(mixed, ["name", "email"]);
console.log("String properties: " + strings.join(", "));

Conditional types:Combining conditional types and mapped types enables complex type filtering and extraction.


Iterating Over Array Types

Use index types to manipulate arrays and tuples.

Example

// Define a tuple type
type Tuple = [string, number, boolean];

// Get the tuple element type
type First = Tuple[0];   // string
type Second = Tuple[1];  // number
type Third = Tuple[2];   // boolean

// Use number to get the type of all elements
type AllElements = Tuple[number];  // string | number | boolean

// Get the array element type
type StringArray = string[];
type ArrayElement = StringArray[number];  // string

// Practical application: function overloads
function getElement<T extends any[]>(
    arr: T,
    index: number
): T[indexof T] | undefined {
    return index < arr.length ? arr[index] : undefined;
}

const tuple: Tuple = ["hello", 123, true];
const arr: string[] = ["a", "b", "c"];

console.log("Tuple element + getElement(tuple, 0));
console.log("Array element + getElement(arr, 1));

Tuple indexing:Tuples can use numeric indices, and each index corresponds to a specific element type, which arrays cannot do.


Notes

  • keyof returns a union type:keyof returns a union type of key name literals
  • Index access safety:Ensure the accessed key exists in the target type
  • Generic constraints:Use extends keyof to constrain generic parameters
  • Mapped types require the in keyword:Mapped types use the [P in keyof T] syntax

Best practices:Index types are the cornerstone of creating generic utility types; mastering them can greatly improve your type manipulation abilities.


Summary

Index types and keyof are important components of the TypeScript type system.

  • keyof:Get all keys of an object type
  • Index access:Get property types through keys
  • Mapped types:Create new types based on existing types
  • Type safety:Ensure type safety for dynamic property access

Recommendation:When you need to manipulate object properties, prefer using index types to maintain type safety.

Other extensions