手记"/>
typescript手记
TypeScript 常用方法
never 与 void
在 js 中 void 是一个操作符,它可以让任何表达式返回 undefined, undefined 不是一个保留字
// 可以对undefined进行声明赋值
(function () {const undefined = 0;console.log(undefined);
})();
使用 void 0 则则表示 undefined
never: 一个函数如果抛出了一个错误,则它永远不会有返回值,则返回值类型为 never
// never
let error = () => {throw new Error("error");
};
对象类型接口
interface List {id: number;name: string;
}
interface Result {data: List[];
}function fn(data: Result) {}
const result = {data: [{ id: 1, name: "jj" },{ id: 2, name: "cc" },],
};
使用过程中返回的数据可能会增加一些属性,例如
const result = {data: [{ id: 1, name: "jj", desc: "test" },{ id: 2, name: "cc" },],
};
fn(result); // ts并没有报错
ts 并没有报错 ts 采取了一种鸭式变形法
如果传入对象字面量的话,ts 就会对额外的字段进行检查
fn({data: [{ id: 1, name: "jj", desc: "test" }, // 会报错 desc{ id: 2, name: "cc" },],
});
这种情况需要使用类型断言
例如 1 建议使用 1,因为下面(例 2)这种在 React 中可以产生歧义
fn({data: [{ id: 1, name: "jj", desc: "test" }, // 会报错 desc{ id: 2, name: "cc" },],
} as Result);
例如 2
fn(<Result>{data: [{ id: 1, name: "jj", desc: "test" }, // 会报错 desc{ id: 2, name: "cc" },],
});
索引签名
// 用任意字符串索引 得到字符串
interface Item {[index: string]: string;
}
这样声明之后,就不能再声明一个 number 类型的成员了,如
interface Item {[index: string]: string;key: number; //报错
}
两种签名是可以混用的,所以 还可以在 Item 中增加一个数字索引签名 如
interface Item {[index: string]: string;[key: number]: string; //如果是 number就会报错, 如果上面是any 那么number就可以
}
函数类型接口
function fn(x: number, y: number) {return x + y;
}
- 通过变量定义
let myFn = (x: number, y: number) => number;
- 通过类型别名定义
type MyFn = (x: number, y: number) => number;
- 通过接口定义
interface MyFn {(x: number, y: number): number;
}
混合类型接口
interface MixinFn {(): void; //表示这个类是个函数,返回值是voidversion: string;toDo(): void;
}
MixinFn 的实现
let lib:MixinFn = (()=>{}) as MixinFn
lib.version='1.0.0'
lib.toDo = ()=>
函数重载
函数重载指的是在同一个类中定义多个同名函数,但它们的参数列表不同(参数类型、参数个数或参数顺序)。编译器根据调用时提供的参数信息,来确定应该调用哪个函数。函数重载可以提供不同的方法签名,使得同一个函数名可以用于处理多个不同的情况。
class A {sum(a: number, b: number) {return a + b;}
}class B extends A {sub(a: number, b: number) {return a - b;}
}function maker(): B;
function maker(p: string): A;
function maker(p?: string) {if (p) {return new A();}return new B();
}
function maker(p?: string) {if (p) {return new A();}return new B();
}
断言
使用断言使编译器通过检查,不过不推荐这种写法,而应该使用重载
type NumGenerator1 = () => number;
function myFunc1(numGenerator1: NumGenerator1 | undefined) {const num1 = numGenerator1!();const num2 = numGenerator1!();
}
重载
type NumGenerator2 = () => number;
function myFunc2(): undefined;
function myFunc2(numGenerator2?: NumGenerator2) {if (numGenerator2) {const num1 = numGenerator2();const num2 = numGenerator2();}
}
可选项
type NumGenerator3 = () => number;
function myFunc3(numGenerator3: NumGenerator3 | undefined) {const num1 = numGenerator3?.();
}
类
ts 的类覆盖了 js 的类,同时加入了一些新特性
无论在 ts 中还是 es 中,类成员的属性,都是实例属性,而不是原型属性,类成员的方法,都是实例方法。
class Cat {constructor(name: string) {this.name = name;}name: string;run() {}
}
继承
// 构造函数中一定要调用super
class ChildCat extends Cat {constructor(name: string, public color: string) {super(name);}
}
修饰符
- public 默认都是 public,含义就是对所有人都是可见的
- private 私有成员,只能在类的本身调用,不能被实例调用,也不能被子类调用
- protected 受保护成员,只能在类或者子类中访问,而不能在类的实例中访问
- constructor 也可以添加 protected,表明这个类不能实例化,只能被继承
- readonly 一定要初始化,跟实例属性是一样
除了类成员可以添加修饰符之外,构造函数的参数也可以添加修饰符,它的作用就是将参数自动变成实例的属性,这样就省略在类中的定义了
class ChildCat extends Cat {constructor(name: string, public color: string) {super(name);}
}
static,类的静态修饰符,静态成员也可以被继承
抽象类
es 中并没有抽象类,这是 ts 对类的拓展,所谓抽象类,就是只能被继承而不能实例化的类。在抽象类中可以定义一个方法,它可以有具体的实现,这样子类就不用实现了,就实现了方法的复用。在抽象类中也可以不指定具体的方法实现,这就构成了抽象方法。抽象方法的好处就是你明知道子类中有其他的实现,那就没必要在父类中实现了。
abstract class Animal {eat() {console.log("eat");}abstract sleep(): void;
}
class Chicken extends Animal {sleep() {console.log("sleep");}
}
const chicken = new Chicken();
chicken.eat();
链式调用
链式调用的核心就在于调用完的方法将自身的实例返回
class WorkFlow {step1() {return this;}step2() {return this;}
}
new WorkFlow().step1().step2();class Myflow extends WorkFlow {next() {return this;}
}
new Myflow().next().step1().next().step2();
类型与接口的关
一个接口可以约束一个类成员有哪些属性以及他们的类型
interface Human {name: string;eat(): void;
}
类实现接口的时候必须实现接口中声明的所有属性和方法
接口只能约束类的公有成员
interface Human {name: string;eat(): void;
}
class Asian implements Human {constructor(name: string) {this.name = name;}name: string;eat() {}
}
接口的继承
接口可以像类一样相互继承,并且一个接口可以继承多个接口
// Human ==> name eat
interface Man extends Human {run(): void;
}
interface Child {cry(): void;
}
interface Boy extends Man, Child {}const boy: Boy = {name: "",run() {},eat() {},cry() {},
};
从接口的继承可以看出,接口的继承可以抽离出可重用的接口,也可以将多个接口合并成一个接口
接口继承 类
接口除了可以继承接口之外,还可以继承类,这就相当于接口把类的成员都抽象出来,也就是只有类的成员结构,而没有具体的实现
class Auto {state = 1;
}
interface AutoInterface extends Auto {}
这样 AutoInterface 接口就隐含了 state 属性,要想实现这个 AutoInterface 接口只要一个类的成员和 state 的属性就可以了
class C implements AutoInterface {state = 1;
}
class Bus extends Auto implements AutoInterface {}
// 在这个例子中我们就不必实现 state 属性了,因为 Bus 是 Auto 的子类自然继承了 state 属性 。这里需要额外注意的是,接口在抽离类的成员的时候,不仅抽离了公共成员,而且抽离了私有成员和受保护成员。
泛型函数与泛型接口
很多时候我们希望一个函数或者一个类可以支持多种数据类型,有很大的灵活性
function print(value: string): string {console.log(value);return value;
}
// 重载
function print(value: string): string;
function print(value: string[]): string[];
function print(value: any) {console.log(value);return value;
}
// 联合类型
function print(value: string | string[]): string | string[] {console.log(value);return value;
}
// any类型
function print(value: any) {console.log(value);return value;
}
泛型概念
不预先确定类型,在使用的时候再确定
function log<T>(value: T): T {console.log(value);return value;
}
log<string[]>(["a", "b"]);
log(["a", "b"]);
我们不仅可以用泛型来定义一个函数,也可以定义一个函数类型
type Log = <T>(value: T) => T;
const myLog: Log = log;
泛型接口
这个和类型别名的定义方式是等价的,但目前这个泛型仅约束了一个函数,也可以用泛型来约束其他成员
interface Log1 {<T>(value: T): T;
}
这样接口的所有成员都受到了泛型的约束
这里需要注意的是当泛型约束了整个接口之后,在实现的时候,我们必须指定一个类型
let myLog1: Log2<number> = log;
myLog1(1);
如果不指定类型也可以在接口的定义中指定一个默认类型
interface Log3<T = string> {(value: T): T;
}
let myLog2: Log3 = log;
myLog2("s");
泛型小结
把泛型变量和函数的参数等同对待,泛型只不过是另一个维度的参数,是代表类型而不是代表值的参数
函数和类可以轻松地支持多种类型,增强程序的拓展性
不必写多条函数重载,冗长的联合类型声明,增强代码可读性
灵活控制类型之间的约束
泛型类与泛型约束
- 泛型类
与泛型接口非常类似,泛型也可以约束类的成员,需要注意的是泛型不能应用于类的静态成员
class Ame<T> {run(value: T) {console.log(value);return value;}
}
const ame = new Ame<number>();
ame.run(1);
当不指定类型参数的时候,value 值就可以是任意的值
const ame1 = new Ame();
ame1.run({ a: 1 });
ame1.run("ss");
类型约束
interface Length {length: number;
}
function print<T extends Length>(value: T): T {console.log(value, value.length);return value;
}
// /参数需要具有length属性print([1]);
print("ss");
print({ length: 1 });
类型检查机制
TypeScript 编译器在做类型检查时,所秉承的一些原则,以及表现出的一些行为。
- 作用:辅助开发,提高开发效率
- 类型推断
- 类型兼容性
- 类型保护
类型推断
interface AmeFoo {name: string;
}
let ameFoo = {} as AmeFoo;
ameFoo.name = "coboy";
推荐在声明的时候就指定类型 即:
let ameFoo1: AmeFoo = { name: "coboy" };
类型断言可以增加我们代码的灵活性,在改造一些旧代码的时候非常有效,但使用类型断言,要注意避免滥用,要对上下文的环境要有充足的预判,没有任何根据的类型断言会带来安全的隐患,总之 TS 的类型推断可以为我们提供重要的辅助信息,应该善加利用
类型兼容性
当一个类型 Y 可以被赋值给另一个类型 X 时,我们就可以说类型 X 兼容类型 Y
X 兼容 Y:X(目标类型)= Y(源类型)
之所以我们要讨论类型兼容性问题,是因为 TS 允许我们把一些类型不同的变量相互赋值。类型兼容性的例子广泛存在于接口、函数和类中。
接口兼容性
interface AmeX {name: any;age: any;
}
interface AmeY {name: any;age: any;height: any;
}
let ameX: AmeX = { name: "coboy", age: 25 };
let ameY: AmeY = { name: "cobyte", age: 25, height: 180 };
ameX = ameY;
ameY = ameX; // Property 'height' is missing in type 'AmeX' but required in type 'AmeY'
这里再次体现了 TS 的检测原则,也就是鸭式变形法(一只鸟走起来像鸭子,游起来像鸭子,叫起来像鸭子,就可以被认为是鸭子)
源类型必须具备目标类型的必要属性,就可以进行赋值(成员少的会兼容成员多的)
函数兼容性
要判断两个函数是否兼容通常发生在相互赋值的情况下,也就是函数作为参数的情况下
type Handler = (x: number, y: number) => void;
function add(handler: Handler) {return handler;
}
参数个数
let handler1 = (x: number) => {};
add(handler1);
let handler2 = (x: number, y: number, z: number) => {};
add(handler2); // Argument of type '(x: number, y: number, z: number) => void' is not assignable to parameter of type 'Handler'
- 可选参数和剩余参数
let fun1 = (p1: number, p2: number) => {};
let fun2 = (p1?: number, p2?: number) => {};
let fun3 = (...args: number[]) => {};
- 固定参数可以兼容可选参数和剩余参数
fun1 = fun2;
fun1 = fun3;
- 可选参数是不兼容固定参数和剩余参数
fun2 = fun3; // error
fun2 = fun1; // error
// 可以将strictFunctionTypes设置为false
- 剩余参数可以兼容固定参数和可选参数
fun3 = fun1;
fun3 = fun2;
参数类型
let handler3 = (a: string) => {};
add(handler3); // error
interface Point3D {x: number;y: number;z: number;
}
interface Point2D {x: number;y: number;
}
let p3d = (point: Point3D) => {};
let p2d = (point: Point2D) => {};
p3d = p2d;
p2d = p3d; // error
// 可以将strictFunctionTypes设置为false
这种函数的参数之间可以赋值的情况,叫做函数参数的双向协变,这种情况允许我们把一个精确的类型赋值给一个不那么精确的类型,这样做我们就不需要把一个不精确的类型断言成一个精确的类型。
返回值类型
ts 要求我们目标的返回值类型必须与源函数的返回值类型相同,或者为其子类型
let fun4 = () => ({ name: "coboy" });
let fun5 = () => ({ name: "cobyte", age: 18 });
fun4 = fun5;
fun5 = fun4; // error
function overload(x: number, y: number): number;
function overload(x: string, y: string): string;
function overload(x: any, y: any): any {}
函数重载分为两部分,第一部分就是函数重载的列表,第二部分就是函数的具体实现,这里列表中的函数就是目标函数,而具体的实现函数就是源函数。程序在运行的时候,编译器会查找重载列表,然后使用第一个匹配的定义来执行下面的函数,所以在重载列表中,目标函数的参数要多于源函数的参数,而且返回值类型也要符合相应的要求
枚举兼容性
枚举类型和数字类型是可以完全相互兼容的
枚举之间是完全不兼容的
enum Fruit {Apple,Banana,
}
enum Color {Red,Yellow,
}
let fruit: Fruit.Apple = 3;
let no: number = Fruit.Apple;
类的兼容性
class AmeByte1 {constructor(x: number, y: number) {}id: number = 1;
}
class AmeByte2 {static x = 1;constructor(p: number) {}id: number = 2;
}
let amebyte1 = new AmeByte1(1, 2);
let amebyte2 = new AmeByte2(1);
amebyte1 = amebyte2;
amebyte2 = amebyte1;
类的兼容性和接口的比较相似,他们也只是比较结构。注意:在比较两个类是否兼容的时候,静态成员和构造函数是不参与比较的,如果两个类具有相同的实例成员,那么他们的实例就可以互相兼容。
如果两个类含有私有成员,那么这两个类就不兼容了,这个时候只有父类和子类之间是互相兼容的。
泛型兼容性
interface Empty1<T> {}
let obj1: Empty1<number> = {};
let obj2: Empty1<string> = {};
obj1 = obj2;interface Empty2<T> {value: T;
}
let obj3: Empty2<number> = {}; // error
let obj4: Empty2<string> = {}; // error
obj3 = obj4; // error
只有类型参数 T 被接口成员使用的时候,才会有影响泛型的兼容性
泛型函数
let ameT1 = <T>(x: T): T => {console.log("x");return x;
};
let ameT2 = <U>(y: U): U => {console.log("y");return y;
};
ameT1 = ameT2;
如果两个泛型函数的定义相同但没有指定类型参数,那么他们之间也是可以互相兼容的。
- 结构之间的兼容:成员少的兼容成员多的
- 函数之间的兼容:参数多的兼容参数少的
类型保护
TypeScript 能够在特定的区块中保证变量属于某种确定的类型,可以在此区块中放心地引用此类型的属性,或者调用此类型的方法。
enum Type {Strong,Week,
}class Java {helloJava() {console.log("hello Java");}java: any;
}class JavaScript {helloJavaScript() {console.log("hello JavaScript");}javascript: any;
}function isJava(lang: Java | JavaScript): lang is Java {return (lang as Java).helloJava !== undefined;
}
类型断言
function getLanguage(type: Type, x: string | number) {let lang = type === Type.Strong ? new Java() : new JavaScript();if ((lang as Java).helloJava) {(lang as Java).helloJava();} else {(lang as JavaScript).helloJavaScript();}return lang;
}
getLanguage(Type.Strong);
instanceof
function getLanguage(type: Type, x: string | number) {let lang = type === Type.Strong ? new Java() : new JavaScript();if (lang instanceof Java) {lang.helloJava();} else {lang.helloJavaScript();}return lang;
}
getLanguage(Type.Strong);
in
function getLanguage(type: Type, x: string | number) {let lang = type === Type.Strong ? new Java() : new JavaScript();if ("java" in lang) {lang.helloJava();} else {lang.helloJavaScript();}return lang;
}
getLanguage(Type.Strong);
typeof
function getLanguage(type: Type, x: string | number) {let lang = type === Type.Strong ? new Java() : new JavaScript();if (typeof x === "string") {x.length;} else {x.toFixed(2);}return lang;
}
getLanguage(Type.Strong);
类型谓词
function isJava(lang: Java | JavaScript): lang is Java {return (lang as Java).helloJava !== undefined;
}
function getLanguage(type: Type, x: string | number) {let lang = type === Type.Strong ? new Java() : new JavaScript();if (isJava(lang)) {lang.helloJava();} else {lang.helloJavaScript();}return lang;
}
getLanguage(Type.Strong);
高级类型
交叉类型
所谓交叉类型就是将多个类型合并为一个类型,新的类型具有所有类型的特性,所以交叉类型特别适合对象混入的场景。
interface DogInterface {run(): void;
}
interface CatInterface {jump(): void;
}
let pet: DogInterface & CatInterface = {run() {},jump() {},
};
需要注意的是交叉类型看名称给人的感觉是几个类型的交集,实际上是取所有类型的并集。
联合类型
所谓联合类型就是指声明的类型并不确定,可以为多个类型中的一个。
let ameType: number | string = "1"; // 可以等于数字也可以等于字符串
字面量类型
有的时候我们不仅需要限定一个变量的类型,而且要限定变量的取值在某一个特定的范围内。
let b: "a" | "b" | "c";
对象联合类型
如果一个对象是联合类型,那么在类型未确定的情况下,它就只能访问所有类型的共有成员。
class DogImpl implements DogInterface {run() {}eat() {}
}
class CatImpl implements CatInterface {jump() {}eat() {}
}
enum Master {Boy,Girl,
}
function getPet(master: Master) {let pet = master === Master.Boy ? new DogImpl() : new CatImpl();pet.eat(); // 如果一个对象是联合类型,那么在类型未确定的情况下,它就只能访问所有类型的共有成员pet.run(); // errorreturn pet;
}
这个时候有趣的事情发生了,从名称上看联合类型给人感觉是取所有类型的并集,而实际情况只能访问所有成员的交集。
可区分的联合类型
interface Square {kind: "square";size: number;
}
interface Rectangle {kind: "rectangle";width: number;height: number;
}
type Shape = Square | Rectangle;
function area(s: Shape) {switch (s.kind) {case "square":return s.size * s.size;case "rectangle":return s.height * s.width;}
}
上面的代码如果不去升级是不会有问题的,但如果我们想加一种新的模式,它就会有问题了。
interface Square {kind: "square";size: number;
}
interface Rectangle {kind: "rectangle";width: number;height: number;
}
interface Circle {kind: "circle";r: number;
}
type Shape = Square | Rectangle | Circle;
function area(s: Shape) {switch (s.kind) {case "square":return s.size * s.size;case "rectangle":return s.height * s.width;case "circle":return Math.PI * s.r ** 2;default:return ((e: never) => {throw new Error(e);})(s);}
}
((e: never) => {throw new Error(e);
})(s);
这段函数的作用是:检测 s 是不是 never 类型,如果 s 是 never 类型,就说明上面的分支都被覆盖了,这个分支永远不会执行,那么如果 s 不是 never 类型,就说明以前的分支有遗漏。
索引类型
我们有时候会遇到这样的一种场景,就是从对象中获取一些属性的值然后建立一个集合。
let obj = {a: 1,b: 2,c: 3,
};
function getValues(obj: any, keys: string[]) {return keys.map((key) => obj[key]);
}console.log(getValues(obj, ["a", "b"]));console.log(getValues(obj, ["e", "f"])); // 随意指定不存在的属性,但不报错
随意指定没有的属性,但 ts 编译器并没有报错,所以这个时候我们需要对类型进行约束,这个时候我们就需要用到了索引类型。
下面我们要先了解一下索引类型的几个必要概念
keyof T
表示泛型变量可以通过继承某个类型获得某些属性
改造上面的代码
let obj = {a: 1,b: 2,c: 3,
};
function getValues<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {return keys.map((key) => obj[key]);
}
console.log(getValues(obj, ["a", "b"]));console.log(getValues(obj, ["e", "f"])); // error 这个时候就报错了
映射类型
通过映射类型可以把一个旧的类型生成一个新的类型
将一个接口的所有属性映射为只读:
interface objMapping {a: string;b: number;c: boolean;
}type ReadonlyObj = Readonly<objMapping>;
ReadonlyObj 与 objMapping 成员完全相同,区别是 ReadonlyObj 中的成员属性均为只读
将一个接口的所有属性变成可选的 Partial 映射类型
type PartialObj = Partial<objMapping>;
可以抽取对象子集的 Pick 映射类型:
type PickObj = Pick<objMapping, "a" | "b">;
Readonly 的实现原理
从源码可以看出 Readonly 是一个可索引类型的泛型接口
/*** Make all properties in T readonly*/
type Readonly<T> = {readonly [P in keyof T]: T[P];
};
索引签名为 P in keyof T :
其中 keyof T 就是一个一个索引类型的查询操作符,表示类型 T 所有属性的联合类型
P in :
相当于执行了一个 for in 操作,会把变量 P 依次绑定到 T 的所有属性上
索引签名的返回值就是一个索引访问操作符 : T[P] 这里代表属性 P 所指定的类型
最后再加上 Readonly 就把所有的属性变成了只读,这就是 Readonly 的实现原理
Partial 的实现原理:
/*** Make all properties in T optional*/
type Partial<T> = {[P in keyof T]?: T[P];
};
Pick 映射类型有两个参数:
第一个参数 T,表示要抽取的目标对象
第二个参数 K,具有一个约束:K 一定要来自 T 所有属性字面量的联合类型,
即映射得到的新类型的属性一定要从 K 中选取
以上三种映射类型官方称为同态类型,意思是只作用于 obj 属性而不会引入新的属性
非同态类型
Record 是非同态类型
type RecordObj = Record<"m" | "n", objMapping>;
第一个参数是预定义的新属性,比如 m,n
第二个参数就是已知类型
映射出的新类型所具有的属性由 Record 的第一个属性指定,而这些属性类型为第二个参数指定的已知类型,这种类型就是一个非同态的类型
Record 映射类型源码:
/*** Construct a type with a set of properties K of type T*/
type Record<K extends keyof any, T> = {[P in K]: T;
};
非同态类型本质上会创建新的属性
Readonly, Partial 和 Pick 是同态的,但 Record 不是。 因为 Record 并不需要输入类型来拷贝属性,所以它不属于同态,非同态类型本质上会创建新的属性
映射类型本质上是一种预先定义的泛型接口,通常还会结合索引类型,获取对象的属性和属性值,从而像一个对象映射成我们想要的结构。
条件类型
条件类型是一种由条件表达式决定的类型
T extends U ? X : Y
意思是如果类型 T 可以赋值给类型 U,那么结果类型就是 X 类型,否则就是 Y 类型,条件类型使类型具有了不唯一性,同样增加了语言的灵活性
分步式条件类型
当类型 T 为联合类型时:
T 为类型 A 和类型 B 的联合类型,结果类型会变成多个条件类型的联合类型
(A | B) extends U ? X : Y
可以将 A 和 B 进行拆解:
(A extends U ? X : Y) | (B extends U ? X : Y)
这时定义的变量就会被推断为联合类型
type T3 = TypeName<string | string[]>;
可以看到,传入 string | string[]联合类型,被推断为 string|object 的联合类型
利用上边这个特性可以实现对类型的过滤
type Diff<T, U> = T extends U ? never : T;
如果 T 可以被赋值给 U,结果类型为 never 类型,否则为 T 类型
type T4 = Diff<"a" | "b" | "c", "a" | "e">; //通过拆解来分析
T4 的类型被推断为 b 和 c 的联合类型,过滤掉了第二个参数中已经含有类型 a
// Diff<'a', 'a' | 'e'> | Diff<'b', 'a' | 'e'> | Diff<'c', 'a' | 'e'>
// never | "b" | "c"
// "b" | "c"
先判断 a 是否可以被赋值给这个字面量联合类型’a’ | ‘e’,答案是可以的,所以返回 never
继续,因为 b 不可以被赋值给字面量联合类型’a’ | ‘e’,所以返回 b
继续,c 不可以被赋值给’a’ | ‘e’,所以返回 c
最后,never 和 b,c 的联合类型为’b’ | ‘c’
Diff 类型作用:
可以从类型 T 中过滤掉可以被赋值给类型 U 的类型
也可以实现从类型 T 中移除不需要的类型,如 undefined 和 null
定义一个 NotNull,从 T 中过滤掉 undefined 和 null
type NotNull<T> = Diff<T, undefined | null>;
type T5 = NotNull<string | number | undefined | null>;
过滤掉 undefined 和 null,T5 的类型就变成了 string 和 number
上述的 Diff 和 NotNull 类型,是已经在 TS 内置的类库中被实现的内置类型
-
Diff 的内置类型叫做 Exclude<T, U>
-
NotNull 的内置类型叫做 NonNullable
此外,官方还预置了一些条件类型,如:Extract 和 Exclude -
Extract 和 Exclude 相反
-
Exclude 作用是从类型 T 中过滤掉可以赋值给类型 U 的类型
-
Extract 作用是可以从类型 T 中抽取出可以赋值给 U 的类型
type T6 = Extract<"a" | "b" | "c", "a" | "e">;
type T7 = Exclude<"a" | "b" | "c", "a" | "e">;
T6 抽取了在类型 U 中存在的类型 a
T7 抽取了在类型 U 中不存在的类型 b 和 c
源码:
/*** Exclude from T those types that are assignable to U*/
type Exclude<T, U> = T extends U ? never : T;/*** Extract from T those types that are assignable to U*/
type Extract<T, U> = T extends U ? T : never;
ReturnType 源码:
/*** Obtain the return type of a function type*/
type ReturnType<T extends (...args: any) => any> = T extends (...args: any
) => infer R? R: any;
T extends (…args: any) => any:
ReturnType 要求参数 T 可以赋值给一个函数,这个函数有任意的参数,返回值类型也是任意的
由于函数返回值类型不确定,这里使用了 infer 关键字,表示待推断,延迟推断,需要根据实际的情况确定
infer R ? R : any:
如果实际类型是 R,那么结果类型就是 R,否则返回值类型就是 any
更多推荐
typescript手记
发布评论