typescript如何推断嵌套对象的key类型

74 阅读2分钟

在使用typescript时,有时候我们需要知道一个对象的key值的类型

举个例子:有一个pack对象,getValue来根据传入key获取其值

const pack = {
  reset: {
    zh: '重置',
    en: 'Reset',
  },
  search: {
    zh: '查询',
    en: 'Search',
  },
};

const getValue = (key) => {
  return pack[key];
};

此时ts报错参数“key”隐式具有“any”类型。ts(7006)

image.png

意思是传入函数的key值没有限制其类型

那写上key可能的值就好了

const getValue = (key: 'reset' | 'search') => {
  return pack[key];
};

现在pack只有两个key,以后增加了怎么办呢?所以不能手动添加

我们需要使用ts推断出packkey的类型,key: keyof typeof pack

const getValue = (key: keyof typeof pack) => {
  return pack[key];
};

typeof

typeof是一个操作符,它用于获取一个变量、属性或表达式在运行时的类型。

上述示例中typeof pack得到为

const pack: {
  reset: {
    zh: string;
    en: string;
  };
  search: {
    zh: string;
    en: string;
  };
};

keyof

keyof 是一个操作符,它返回一个对象类型的所有公共属性名的联合类型。

上述示例中keyof typeof pack得到为

"search" | "reset"

到这里都比较好理解,但如果需要获取pack第二级key值的类型

const getValue = (key: keyof typeof pack, locale) => {
  return pack[key][locale];
};

增加了第二级key: locale,此时也是报错参数“locale”隐式具有“any”类型。ts(7006)

locale目测可以知道是'zh'|'en'。那如何用推断的方式得到locale的类型值呢?

extends

extends 关键字主要用于类继承,表示一个类继承自另一个类,还可以用于条件类型(Conditional Types),判断一个类是否属于另一个类

比如需要推断数组的元素类型

type Ids = number[];
type Names = string[];

// T extends Names? 可以解析为:如果泛型T属于string[]类型,则...
type Unpacked<T> = T extends Names ? string : T extends Ids ? number : never;

type idType = Unpacked<Ids>; // idType 类型为 number
type nameType = Unpacked<Names>; // nameType 类型为string

infer

infer 关键字通常与条件类型(Conditional Types)和映射类型(Mapped Types)一起使用,用于在类型推断过程中捕获类型信息。

同样是推断数组的元素类型,使用infer

// infer R 表示声明了一个类型R,用于捕获参数类型。如果传入T = string[],则 R = string
type Unpacked<T> = T extends (infer R)[] ? R : never;

type idType = Unpacked<Ids>; // idType 类型为 number
type nameType = Unpacked<Names>; // nameType 类型为string

回到pack例子。我们先不使用keyof,使用infer来推断一下pack的一级key类型

type GetKey<T> = T extends Record<infer R, any> ? R : never;

const getValue = (key: GetKey<typeof pack>) => {
  return pack[key];
};

GetKey<typeof pack> 效果等同于 keyof typeof pack

那如何用infer推断pack的二级key类型?

type GetKey<T> = T extends Record<string, infer R>
  ? R extends Record<infer L, string>
    ? L
    : never
  : never;

// locale被推断为"zh" | "en"
const getValue = (key: keyof typeof pack, locale: GetKey<typeof pack>) => {
  return pack[key][locale];
};

TypeScript中通过复杂的类型操作和转换,实现对类型的高级控制和计算,被称作为类型体操

关于keyoftypeofextendsinfer的更多用法请参阅其他资料