阿里云主机折上折
  • 微信号
Current Site:Index > The `keyof` operator

The `keyof` operator

Author:Chuan Chen 阅读数:26679人阅读 分类: TypeScript

The keyof operator is a powerful type query tool in TypeScript that extracts the collection of key names from an object type, generating a union type of string or numeric literals. Through keyof, type relationships can be constrained more precisely, especially excelling in dynamic property access or generic scenarios.

Basic Usage of keyof

When the keyof operator acts on an object type, it returns a union type of all accessible key names of that object. For example:

interface Person {
  name: string;
  age: number;
  address: string;
}

type PersonKeys = keyof Person; 
// Equivalent to type PersonKeys = "name" | "age" | "address"

Here, the PersonKeys type contains a union of literals representing all key names in the Person interface. This feature is particularly useful when restricting variables to only specific object key names:

function getProperty(obj: Person, key: PersonKeys) {
  return obj[key]; // Type-safe
}

const person: Person = { name: "Alice", age: 30, address: "123 Main St" };
getProperty(person, "name"); // Valid
getProperty(person, "gender"); // Error: "gender" is not in PersonKeys

Advanced Applications with Generics

When combined with generics, keyof enables more flexible type constraints. Here’s a generic function for dynamically accessing object properties:

function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
  return items.map(item => item[key]);
}

const people: Person[] = [
  { name: "Alice", age: 30, address: "A" },
  { name: "Bob", age: 25, address: "B" }
];

const names = pluck(people, "name"); // string[]
const ages = pluck(people, "age"); // number[]

Here, K extends keyof T constrains the second parameter to be one of the keys of T, and the return type T[K][] automatically infers to an array of the corresponding property type.

Handling Complex Object Types

keyof also works with complex nested types. Consider the following scenario with nested objects:

interface Company {
  id: number;
  name: string;
  departments: {
    sales: number;
    it: number;
  };
}

type CompanyKeys = keyof Company;
// Equivalent to type CompanyKeys = "id" | "name" | "departments"

type DepartmentKeys = keyof Company["departments"];
// Equivalent to type DepartmentKeys = "sales" | "it"

Chained operations can be used to retrieve deep key name types, which is particularly useful when dealing with complex data structures.

Combining with Mapped Types

keyof is often used with mapped types to create type transformations. For example, the following code makes all properties of a type optional:

type Partial<T> = {
  [P in keyof T]?: T[P];
};

type PartialPerson = Partial<Person>;
// Equivalent to { name?: string; age?: number; address?: string; }

Another typical application is creating a read-only version of a type:

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type ReadonlyPerson = Readonly<Person>;
// All properties become read-only

Handling Class Types

keyof applies not only to interfaces but also to class types. However, note the distinction between class instances and class constructors:

class User {
  id: number = 0;
  username: string = "";

  login() {
    console.log("Logged in");
  }
}

type UserKeys = keyof User; // "id" | "username" | "login"
type UserInstanceKeys = keyof User["prototype"]; // "login"

Handling Special Types

When keyof acts on special types, its behavior may be unexpected:

  1. Array Types:

    type ArrayKeys = keyof []; // number | "length" | "toString" | ...
    

    This returns a union of all array methods and numeric indices.

  2. Literal Types:

    type LiteralKeys = keyof "hello"; // number | "toString" | "charAt" | ...
    

    Returns a union of string prototype methods.

Type Guards and keyof

keyof can be used to implement safer type guards:

function hasKey<T>(obj: T, key: keyof any): key is keyof T {
  return key in obj;
}

const obj = { a: 1, b: 2 };
if (hasKey(obj, "a")) {
  console.log(obj.a); // Type-safe
}

Applications in Conditional Types

Combined with conditional types, keyof enables more complex type operations:

type NonFunctionKeys<T> = {
  [K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];

type PersonNonFunctionKeys = NonFunctionKeys<Person>; // "name" | "age" | "address"

This type filters out all keys of function-type properties.

Handling Index Signatures

When a type includes an index signature, keyof behaves differently:

interface Dictionary {
  [key: string]: number;
}

type DictKeys = keyof Dictionary; // string | number

Note that this returns string | number because JavaScript object keys are always coerced into strings.

Practical Use Cases

  1. Type-Safe Redux Actions:

    interface State {
      loading: boolean;
      data: string[];
      error: Error | null;
    }
    
    type ActionType = `SET_${keyof State}`;
    // "SET_loading" | "SET_data" | "SET_error"
    
  2. Dynamic Form Validation:

    interface FormValues {
      email: string;
      password: string;
      remember: boolean;
    }
    
    type ValidationRules = {
      [K in keyof FormValues]: (value: FormValues[K]) => boolean;
    };
    
    const rules: ValidationRules = {
      email: (val) => val.includes("@"),
      password: (val) => val.length >= 8,
      remember: (val) => typeof val === "boolean"
    };
    

Combining with Template Literal Types

Template literal types introduced in TypeScript 4.1 can be combined with keyof for interesting results:

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type PersonGetters = Getters<Person>;
// {
//   getName: () => string;
//   getAge: () => number;
//   getAddress: () => string;
// }

Handling never and unknown Types

The behavior of keyof with special types is noteworthy:

type NeverKeys = keyof never; // never
type UnknownKeys = keyof unknown; // never

Combining with the typeof Operator

Runtime object key types can be obtained:

const config = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  retry: 3
};

type ConfigKeys = keyof typeof config; // "apiUrl" | "timeout" | "retry"

Performance Considerations

Although keyof completes all work at compile time, complex type operations may impact compilation speed. This is especially true when dealing with large or deeply nested types, making it important to organize type structures rationally.

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:类的类型检查

下一篇:typeof类型操作符

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.