重用现有类型 -- Typescript类型编程篇(2)

608 阅读2分钟

key in

假如我们有一个非常复杂的API返回结果对象:

type APIResponse = {
  user: {
    userId: string;
    friendList: {
      count: number;
      friends: {
        firstName: string;
        lastName: string;
      }[];
    };
  };
};

我们希望使用friendList对象,我们可能会想将friendList抽离为一个单独的类型:

type FriendList = {
  count: number;
  friends: {
    firstName: string;
    lastName: string;
  }[];
};

type APIResponse = {
  user: {
    userId: string;
    friendList: FriendList;
  };
};

如果我们又想使用friends中的对象,我们可能又需要再单独抽离一个类型。这种方式就显得比较繁琐。

其实我们不需要一个个的抽离类型,通过使用key in的方式可以直接获取类型中某些成员类型:

type FriendList = APIResponse['user']['friendList']

如果我们想获取数组中的对象:

type Friend = FriendList['friends'][number]

number并不是唯一可用于获取数组子项类型的方式。还可以使用诸如01或者其他数值字面量。

key in的方式和我们编程中获取对象中属性的方式十分相似。但ts只支持数组形式获取成员类型。不支持.语法,如APIResponse.user是违法的。

Keyof

keyof用于获取对象属性名,并用这些属性名构成联合类型:

type ResponseKeys = keyof APIResponse;  // 'user'
type UserKeys = keyof APIResponse['user'];  // 'userId' | 'friendList'
type FriendListKeys = keyof APIResponse['user']['friendList'];  // 'count' | 'friends'

如果操作的对象是一个类,则会获取该类所有的public属性名:

class Person {
  name = "st";
  age = 12;
  publicMethod() {}
  private privateMethod() {}
  protected protectedMethod() {}
}

type Keys = keyof Person; //  "name" | "age" | "publicMethod"

通过key inkeyof,我们可以安全的获取对象中的某一个属性的值:


function get<O extends object, K extends keyof O>(o: O, k: K): O[K] {
  return o[k];
}

此时,第二个参数只能是name或者age字符串。

如果我们想更深层的获取对象属性值,可结合函数重载:

type Get = {
  <O extends object, K1 extends keyof O>(o: O, k1: K1): O[K1];
  <O extends object, K1 extends keyof O, K2 extends keyof O[K1]>(
    o: O,
    k1: K1,
    k2: K2
  ): O[K1][K2];
  <
    O extends object,
    K1 extends keyof O,
    K2 extends keyof O[K1],
    K3 extends keyof O[K1][K2]
  >(
    o: O,
    k1: K1,
    k2: K2,
    k3: K3
  ): O[K1][K2][K3];
};

let get: Get = (obj: any, ...keys: string[]) => {
  let result = obj;
  keys.forEach((k: string) => (result = result[key]));
  return result;
};


get({ events: { id: 1, info: { name: "name" } } }, "events", "info", "name");

如果keyof操作的类型中存在可索引属性,情况会稍有不同。此时keyof返回的属性名类型会进行范围扩张。

如果可索引属性名类型为string

interface Person {
  name: string;
  age: number;
  // key可以使用任意string或number类型扩张(number类型属性最后也会被转化为string使用)
  [key: string]: any; 
}

type Keys = keyof Person; // string | number

如果可索引属性名类型为number

interface Person {
  name: string;
  age: number;
  // key只能使用number类型属性扩张
  [key: number]: any; 
}

type Keys = keyof Person; // number | "name" | "age"

在ts中,对象或者数组的属性名可以为字符串,数值或者symbol。所以使用keyof最大会扩张为string | number | symbol。如果开启keyofStringsOnly则只会扩张为string

// 开启前
type Keys = keyof any; //  string | number | symbol

// 开启后
type Keys = keyof any; //  string