TypeScript 中比较重要的关键字讲解

380 阅读9分钟

使用 TS 进行项目开发时,写好类型是后续开发和迭代能更顺利进行的一个保障,很多时候会遇到比较复杂的类型,这时候往往需要用到关键字,而且很多时候不会单独使用某一个关键字,需要同时使用多个关键字,举个栗子,使用keyof 的场景中往往也需要用到 typeof,而使用 in 的场景也往往需要用到 keyof 和 typeof。 所以这里简单分享一下在开发过程中比较常用也比较重要的关键字。

这 6 个关键字的在 TS 中的优先级如下:

  1. typeof
  2. keyof
  3. in
  4. extends
  5. as
  6. infer

typeof

 

在 TS 和 JS 中都有 typeof 关键字,并且作用也大同小异,typeof 用于【转化数据】 ,操作数为【变量】 或者【常量】

 

转换结果不同:

  • 在 TS 中:是转化成 一个 TS 的类型定义,也就是 type,JS 数据 -> TS type
  • 在 JS 中:是转化成 一个 字符串,表示操作数的类型,JS 数据 -> JS 数据

 

基本用法

在 TS 中,typeof 只能对 数据 进行转化,不能转化 typeinterface

// JS 对象 
const JDog = { name: "旺财", age: 1, }; 
// TS 类型 type
type TDog= { name: string; age: number; }; 
// TS interface
interface IDog { name: string; age: number; } 
// 以下方式会被认为是 JS 的 typeof
const jKeys1 = typeof JDog; // const JsKeyword1 = "object"
const jKeys2 = typeof TDog; // 报错 'TDog' only refers to a type, but is being used as a value here.
const jKeys3 = typeof IDog; // 报错 'IDog' only refers to a type, but is being used as a value here.
// 以下方式会被认为是 TS 的 typeof 
type TType1 = typeof JDog; // type TsType1 = {name: string; age: number} 
type TType2 = typeof TDog; // 报错 'TDog' only refers to a type, but is being used as a value here.
type TType3 = typeof IDog; // 报错 'IDog' only refers to a type, but is being used as a value here.

 

转换枚举(enum)

// 纯数字枚举 
enum numberEnum {
 one, 
 two,
 three, 
} 
// 非纯数字枚举 
enum otherEnum {
 one = "1",
 two = "2",
 three = 3,
}

在 TS 中定义了枚举 enum,编译为 JS 时会编译为 JS 对象

"use strict";
// 纯数字枚举 
var numberEnum;
(function (numberEnum) {
    numberEnum[numberEnum["one"] = 0] = "one";
    numberEnum[numberEnum["two"] = 1] = "two";
    numberEnum[numberEnum["three"] = 2] = "three";
})(numberEnum || (numberEnum = {}));
// 非纯数字枚举 
var otherEnum;
(function (otherEnum) {
    otherEnum["one"] = "1";
    otherEnum["two"] = "2";
    otherEnum[otherEnum["three"] = 3] = "three";
})(otherEnum || (otherEnum = {}));

可以看出,枚举 enum 本质是 JS 对象,即 数据,所以可以用 typeof 转换枚举 enum

// type EumberEnumType = {one: number, two: number, three: number}
type EumberEnumType = typeof numberEnum;


// type OtherEnumType = {one: string, two: string, three: number}
type OtherEnumType = typeof otherEnum; 

keyof

keyof 的作用:将一个 对象类型 映射为它 所有成员名称联合类型

  • type -> 联合类型
  • interface -> 联合类型

基本用法

// 用 TS interface 描述对象
interface InterfaceObj {
  name: string;
  age: number;
}


// 用 TS type 描述对象
type TTypeObj = {
  name: string;
  age: number;
};


// 用 Ts type 描述基本类型别名
type TypeBase = string;


class TsClass {
  private priData: number;
  private priFunc() {}


  public pubData: number;
  public pubFunc1() {}
  public pubFunc2() {}
}


/**
 * 将对象中的所有 key 组合成一个联合类型
 * type UnionOfInterface = "name" | "age"
 */
type UnionOfInterface = keyof InterfaceObj;


/**
 * 将对象中的所有 key 组合成一个联合类型
 * type UnionOfTypeObj = "name" | "age"
 */
type UnionOfTypeObj = keyof TTypeObj;


/**
 * 对一个非对象类型使用 keyof 后,会返回其原型对象 prototype 上所有 key 组合成的一个联合类型
 * type UnionOfTypeBase = number | typeof Symbol.iterator | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | ... 30 more ... | "padEnd"
 */
type UnionOfTypeBase = keyof TypeBase;


/**
 * 对一个类使用 keyof,也是返回 prototype 上所有 key,但是因为 private 私有部分是放在实例化对象中,而非原型,所以不包含 private 字段
 * type UnionOfClass = "pubData" | "pubFunc1" | "pubFunc2"
 */
type UnionOfClass = keyof TsClass;


// keyof 是为了获取 对象类型的 key 组成的联合类型,any 代表任何值,不能具体到某一对象,所以 keyof any 表示了对象key值可能的取值类型,对象的 key 只能为 string、number、symbol,所以得到这两个类型组成的联合类型
// type UnionOfAny = string | number | symbol
type UnionOfAny = keyof any;

typeof + keyof

// JS 对象
const JsObj= {
  name: "旺财",
  age: 180,
};


// TS 枚举
enum tsEnum {
  one,
  two,
}
// typeof JsObj --> { name: string; age: number; }
// keyof typeof JsObj --> keyof { name: string; age: number; } --> 'name' | 'age'
// type UnionOfObj = "name" | "age"
type UnionOfObj = keyof typeof JsObj;
// typeof tsEnum --> { one: number; two: number; }
// keyof { one: number; two: number; } --> 'one' | 'two'
// type UnionOfEnum = "one" | "two"
type UnionOfEnum = keyof typeof tsEnum;

in

  • 作用一:用于判断某个 属性 是否在某个 对象 或者某个 枚举
  • 作用二:用于【遍历】【联合类型】

基本用法

// TS 联合类型
type TTypeUnion = "one" | "two";


// type TTypeObj1 = { one: string; two: string; }
type TTypeObj1 = {
  [P in TTypeUnion]: string;
};


// type TTypeObj2 = { one: "one"; two: "two"; }
type TTypeObj2 = {
  [P in TTypeUnion]: P;
};

in + keyof

  • keyof描述对象的类型 转化为 联合类型
  • in 联合类型 进行遍历
// TS 描述对象的类型
type TTypeObj = {
  name: string;
  age: number;
};
// keyof TTypeObj --> 'name' | 'age'
// type TTypeObj1 = { name: any, age: any }
type TTypeObj1 = {
  [key in keyof TTypeObj]: any;
};


// type TTypeObj2 = { name: string, age: number }
type TTypeObj2 = {
  [key in keyof TTypeObj]: TTypeObj[key];
};

in + keyof + typeof

  • typeof对象 转化为 描述对象的类型
  • keyof描述对象的类型 转化为 联合类型
  • in联合类型 进行遍历
// ---------------- 对象 -----------------
const JObj = {
  name: "旺财",
  age: 180,
};


// typeof JObj --> {name: string;age: number;}
// keyof typeof JObj --> keyof {name: string;age: number;} --> 'name' | 'age'


// type TTypeObj1 = { name: any; age: any; }
type TTypeObj1 = {
  [P in keyof typeof JObj]: any;
};


//当遍历到 name时,typeof JObj[P] = typeof JObj[name] = typeof "旺财" = string
// type TTypeObj2 = {name: string, age: number}
type TTypeObj2 = {
  [P in keyof typeof JObj]: typeof JObj[P];
};






// ---------------- 枚举 -----------------
// 数字枚举
enum numberEnum {
  one,
  two,
  three,
}


enum otherEnum {
  one = "1",
  two = "2",
  three = 3,
}


/**
 * type Type1 = {
 *   readonly [x: number]: any;
 *   readonly one: any;
 *   readonly two: any;
 *   readonly three: any;
 * }
 */
type Type1 = {
  [P in keyof typeof numberEnum]: any;
};


/**
 * type Type2 = {
 *   readonly [x: number]: string;
 *   readonly one: otherEnum.one;
 *   readonly two: otherEnum.two;
 *   readonly three: otherEnum.three;
 * }
 */
type Type2 = {
  [P in keyof typeof otherEnum]: typeof otherEnum[P];
};

extends

  • 类型继承:类型 A 去 继承 类型 B(注意:interface 才可以继承,type 不可以
  • 定义泛型:约束泛型必须是与目标类型【匹配
  • 条件匹配:判断类型 A 是否【匹配】类型 B

 

类型继承

interface IPerson {
  name: string;
  gender: "male" | "female";
  age: number;
}


interface IProgrammer {
  language: "TypeScript" | "Javascript" | "Java";
  desc: string;
}


/**
 * interface 的继承
 * 可以同时继承多个 interface,用逗号分隔
 */
interface IWebDeveloper extends IProgrammer, IPerson {
  skill: "vue" | "react" | 'Angular';
}


const Me: IWebDeveloper = {
  name: 'jasonwang',
  age: 180,
  gender: "male",
  skill: "react",
  language: "TypeScript",
  desc: "为前端事业添砖JAVA"
};

定义泛型

enum gender {
  male,
  female,
}
// 约束泛型 G 必须是 gender 的子类
interface IPerson<G extends gender> {
  name: string;
  gender: G;
  age: number;
}


enum language {
  TypeScript,
  JavaScript,
  Java
}
// 约束泛型 L 的类型必须是 language 的子类并且定义默认值
interface IProgrammer<L extends language = language.TypeScript> {
  language: L;
  desc: string;
}


interface IWebDeveloper extends IProgrammer, IPerson<gender.male> {
  skill: "Vue" | "React" | "Angular";
}

条件匹配

注意:如果 extends 左侧的类型为联合类型,联合类型会被拆解,就像数学中的分解因式一样 (a + b) * c => ac + bc,如果想【阻断】联合类型的拆解和分配,需要将泛型参数使用 [] 括起来,此时,传入参数T的类型将被当做一个整体,不再分配。即像这样:

type P = [T] extends ["a" | "b"] ? string : number;

 

// 判断范型 T 是否匹配于 number
type MustNumber<T> = T extends number ? number : never;


type Type1 = MustNumber<number>; // type Type1 = number
type Type2 = MustNumber<string>; // type Type2 = never




// 找差集
type Subtraction<T, U> = T extends U ? never : T; 
// 找交集
type Intersection<T, U> = T extends U ? T : never; 


type Type1 = "a" | "b" | "c";
type Type2 = "a" | "b";


// Subtraction<Type1, Type2> --> Subtraction<"a" | "b" | "c","a" | "b"> --> Subtraction<"a", "a" | "b"> | Subtraction<"b", "a" | "b"> | Subtraction<"c", "a" | "b">  --> never | never | "c" --> "c"
// type Sub1 = "c"
type Sub1 = Subtraction<Type1, Type2>;
// type Sub2 = "c"
type Sub2 =
  | ("a" extends Type2 ? never : "a")
  | ("b" extends Type2 ? never : "b")
  | ("c" extends Type2 ? never : "c");
  
// type Sub3 = "c"
type Sub3 = "c";


// type Int1 = "a" | "b"
type Int1 = Intersection<Type1, Type2>;
// type Int2 = "a" | "b"
type Int2 =
  | ("a" extends Type2 ? "a" : never)
  | ("b" extends Type2 ? "b" : never)
  | ("c" extends Type2 ? "c" : never);
// type Int3 = "a" | "b"
type Int3 = "a" | "b";

extends + in + keyof

type TObj = {
  a: string;
  b: number;
  c: boolean;
};


/**
 * keyof TObj --> "a" | "b" | "c"
 * TObj[P] extends string ? string : number: 判断 TObj 中 value 的类型,如果类型是 string 则返回 string,否则返回 number
 * type TType = { a: string; b: number; c: number; }
 */
type TType = {
  [P in keyof TObj]: TObj[P] extends string ? string : number;
};

as

运算符 as 的作用是【改变原有类型定义】,分为以下两种场景:

  • 断言
  • 转化

断言

const str: any = "tellyourmad";
// any 上没有 length 属性,使用 as 断言
const strLen: number = (str as string).length;

转化

type TUnionType = "a" | "b" | 1 | 2;


// type TType1 = { a: string; b: string; 1: string; 2: string; }
type TType1 = {
  [P in TUnionType]: string;
};


/**
 * 强制将 key 全都转化成 number
 * type TsType2 = { [x: number]: string; }
 */
type TsType2 = {
  [P in TUnionType as number]: string;
};

as + extends + in + keyof

// 排除特定的属性名
type OmitProp<T, R> = {
  [K in keyof T as K extends R ? never : K]: T[K];
};




// 排除特定的属性值
type OmitValue<T, R> = {
  [K in keyof T as T[K] extends R ? never : K]: T[K];
};


type TType = {
  a: string;
  b: number;
  c: boolean;
  d: string;
};



/**
 * keyof T --> "a" | "b" | "c" | "d"
 * K in keyof T --> 遍历 "a" | "b" | "c" | "d"
 * 开始遍历 "a", K 为 "a" --> K in keyof T as K --> "a" as "a"
 * K in keyof T as K extends R --> "a" as "a" extends "c" | "d" --> a extends c" | "d"
 * 排除属性名为 "c" | "d" 的属性
 * type TWithoutProp = { a: string; b: number; }
 */
type TWithoutProp = OmitProp<TType, "c" | "d">;


/**
 * 排除属性值的类型为 string | boolean 的属性
 * type TWithoutValue = { b: number; }
 */
type TWithoutValue = OmitValue<TType, string | boolean>;

infer

类型推断

infer 用于获取 extends 的推导过程中出现的某个类型,即表示在 extends 条件语句中待推断的 【类型

举个栗子:

type ParamType<T> = T extends (arg: infer P) => any ? P : T;


interface IPerson {
  name: string;
  age: number;
}
type Func = (p: IPerson) => void;
type Param1 = ParamType<Func>; // type Param1 = IPerson
type Param2 = ParamType<string>; // type Param2 = string

条件语句 T extends (arg: infer P) => any ? P : T 中:infer P 表示待推断的函数的【参数】 的类型,即 arg 的类型

则整个语句表示:如果类型 T 能赋值给函数类型 (arg: infer P) => any,则结果为函数类型 (arg: infer P) => any 中的参数 P,即参数 arg 的类型,否则返回类型 T

 

type ReturnType<T> = T extends (...args: any[]) => infer P ? P : any;


type Func = () => number;
type Type1 = ReturnType<Func>; // type Type1 = number

条件语句 T extends (...args: any[]) => infer P ? P : any 中:infer P 表示 extends 待推断的函数的 返回值 的类型。整个语句表示:如果类型 T 能赋值给函数类型 extends (...args: any[]) => infer P,则结果为函数的返回值的类型 P ,否则返回 any

 

infer 解包

type Unpack<T> = T extends (infer R)[] ? R : T;


type TNumbers = number[];
type TBooleans = boolean[];


type NumberType = Unpack<TNumbers>; // type NumberType = number
type BooleanType = Unpack<TBooleans>; // type BooleanType = boolean

条件语句 T extends (infer R)[] ? R : T 中:如果 T 是某个待推断类型的数组,则返回【这个推断的类型】,否则返回 T

type Unpack1<T> = T extends Promise<infer R> ? R : T;
type TPromise1 = Promise<{code: number, data: any}>;
/**
 * type PType1 = {
 *   code: number;
 *   data: any;
 * }
 */
type PType1 = Unpack1<TPromise1>;


// 递归
type TPromise2 = Promise<Promise<Promise<string[]>>>;
type Unpack2<T> = T extends Promise<infer R> ? Unpack2<R> : T;


type PType2 = Unpack2<TPromise2>; // type PType2 = string[]

 

infer 推断模版字符串

type PickValue<T> = T extends `${infer R}元` ? R : unknown;
// type Value = "99"
type Value = PickValue<"99元">;


// TrimLeft 去除左侧空串
type TrimLeft<T extends string> = T extends ` ${infer R}` ? TrimLeft<R> : T;
// type Value1 = "value1"
type Value1 = TrimLeft<"        value1">;


// TrimRight 去除右侧空串
type TrimRight<T extends string> = T extends `${infer R} ` ? TrimRight<R> : T;
// type Value2 = "value2"
type Value2 = TrimRight<"value2        ">;


// 去除左右两侧空串
// type value3 = "value3"
type value3 = TrimLeft<TrimRight<"        value3                ">>

 

infer推断联合类型

同一个类型在推断的值有【多种情况】的时候会推断为【联合类型】

type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;
// type F1 = string
type F1 = Foo<{ a: string; b: string }>; 
// a 为 string, b 为 number,这时推断的值 infer U 有多个值,则会将这多个值组合为联合类型,这个联合类型就是推断的结果
// type F2 = string | number
type F2 = Foo<{ a: string; b: number }>;


// 将元组转为联合类型
type ElementOf<T> = T extends (infer R)[] ? R : never;
type TTuple = [boolean, string, number, Promise<string[]>, Symbol];
// type Union = string | number | boolean | Promise<string[]> | Symbol
type Union = ElementOf<TTuple>;

使用 infer 递归遍历数组

type ReverseArray<T extends unknown[]> = T extends [infer First, ...infer Rest]  ? [...ReverseArray<Rest>, First]  : T  


// type Value1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
type Value1 = ReverseArray<[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]>
// type Value2 = ["i", "h", "g", "f", "e", "d", "c", "b", "a"]
type Value2 = ReverseArray<['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']>