typescript手记

编程入门 行业动态 更新时间:2024-10-22 16:37:18

typescript<a href=https://www.elefans.com/category/jswz/34/1766042.html style=手记"/>

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手记

本文发布于:2023-12-04 11:43:04,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1660801.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:手记   typescript

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!