使用 TS 进行项目开发时,写好类型是后续开发和迭代能更顺利进行的一个保障,很多时候会遇到比较复杂的类型,这时候往往需要用到关键字,而且很多时候不会单独使用某一个关键字,需要同时使用多个关键字,举个栗子,使用keyof 的场景中往往也需要用到 typeof,而使用 in 的场景也往往需要用到 keyof 和 typeof。 所以这里简单分享一下在开发过程中比较常用也比较重要的关键字。
这 6 个关键字的在 TS 中的优先级如下:
typeofkeyofinextendsasinfer
typeof
在 TS 和 JS 中都有 typeof 关键字,并且作用也大同小异,typeof 用于【转化数据】 ,操作数为【变量】 或者【常量】
转换结果不同:
- 在 TS 中:是转化成 一个 TS 的类型定义,也就是 type,JS 数据 -> TS type
- 在 JS 中:是转化成 一个 字符串,表示操作数的类型,JS 数据 -> JS 数据
基本用法
在 TS 中,typeof 只能对 数据 进行转化,不能转化 type 和 interface
// 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']>