当前位置: 首页 > news >正文

网站管理工作流程wordpress4.5.1

网站管理工作流程,wordpress4.5.1,网站备案中,iis 网站文件被占用Typescript进阶 基础知识 JavaScript 的核心特点就是灵活#xff0c;但随着项目规模的增大#xff0c;灵活反而增加开发者的心智负担。例如在代码中一个变量可以被赋予字符串、布尔、数字、甚至是函数#xff0c;这样就充满了不确定性。而且这些不确定性可能需要在代码运行…Typescript进阶 基础知识 JavaScript 的核心特点就是灵活但随着项目规模的增大灵活反而增加开发者的心智负担。例如在代码中一个变量可以被赋予字符串、布尔、数字、甚至是函数这样就充满了不确定性。而且这些不确定性可能需要在代码运行的时候才能被发现所以我们需要类型的约束。 当然不可否认的是有了类型的加持多少会影响开发效率但是可以让大型项目更加健壮 Typescript 更像后端 JAVA让JS可以开发大型企业应用TS 提供的类型系统可以帮助我们在写代码时提供丰富的语法提示在编写代码时会对代码进行类型检查从而避免很多线上错误 越来越多的项目开始拥抱 TS 了典型的 Vue3、Pinia、第三方工具库、后端 NodeJS 等。我们也经常为了让编辑器拥有更好的支持去编写**.d.ts 文件**。 什么是 Typescript TypeScript 是一门编程语言TypeScript是Javascript的超集任何的JS代码都可以看成TS代码同时Typescript扩展了Javascript语法添加了静态类型支持以及其他一些新特性。 TypeScript 代码最终会被编译成 JavaScript 代码以在各种不同的运行环境中执行 环境配置 全局编译 TS 文件 全局安装typescript对TS进行编译 npm install typescript -g tsc --init # 生成tsconfig.jsontsc # 可以将ts文件编译成js文件 tsc --watch # 监控ts文件变化生成js文件ts-node 执行 TS 文件 采用vscode code runner插件运行文件 npm install ts-node -g直接右键运行当前文件快速拿到执行结果 配置rollup开发环境 安装依赖 pnpm install rollup typescript rollup-plugin-typescript2 rollup/plugin-node-resolve rollup-plugin-serve -D初始化TS配置文件 npx tsc --initrollup配置操作rollup.config.mjs import ts from rollup-plugin-typescript2; import { nodeResolve } from rollup/plugin-node-resolve; import serve from rollup-plugin-serve; import path from path; import { fileURLToPath } from url; const __filename fileURLToPath(import.meta.url); const __dirname path.dirname(__filename); export default {input: src/index.ts,output: {format: iife,file: path.resolve(__dirname, dist/bundle.js),sourcemap: true,},plugins: [nodeResolve({extensions: [.js, .ts],}),ts({tsconfig: path.resolve(__dirname, tsconfig.json),}),serve({open: true,openPage: /public/index.html,port: 3000,}),], };package.json配置 scripts: {start: rollup -c -w }我们可以通过npm run start启动服务来使用 typescript 啦~ 常用插件 Error Lens 提示错误插件TypeScript 内置配置 code-首选项-settings根据需要打开设置即可 基础类型 TS 中有很多类型内置的类型 DOM、Promise 等都在 typescript 模块中 基础类型、高级类型、自定义类型。 TS 中冒号后面的都为类型标识等号后面的都是值。 ts 类型要考虑安全性一切从安全角度上触发。ts 在使用的时候程序还没有运行ts 中有类型推导 会自动根据赋予的值来返回类型只有无法推到或者把某个值赋予给某个变量的时候我们需要添加类型。通过export {}进行模块隔离 布尔、数字、字符串类型 let name: string hs; // 全局也有name属性需要采用模块化解决冲突问题 let age: number 30; let handsome: boolean true;我们标识类型的时候 原始数据类型全部用小写的类型如果描述实例类型则用大写类型大写类型就是装箱类型其中也包含拆箱类型例如下面的大写:String let s1: string abc; let s2: string new String(abc); // 不支持 let s3: String new String(abc); let s4: String abc;什么是包装对象 我们在使用原始数据类型时调用原始数据类型上的方法默认会将原始数据类型包装成对象类型。 数组 数组用于储存多个相同类型数据的集合。 TypeScript 中有两种方式来声明一个数组类型 let arr1: number[] [1, 2, 3]; let arr2: string[] [1, 2, 3]; let arr3: (number | string)[] [1, 2, 3]; // 联合类型 let arr4: Arraynumber | string [1, 2, 3]; // 后面讲泛型的时候 详细说为什么可以这样写元组类型 元组的特点就 固定长度 固定类型的一个数组规定长度和存储的类型 let tuple1: [string, number, boolean] [hh, 30, true]; tuple[3]; // 长度为 3 的元组类型 [string, number, boolean] 在索引 3 处没有元素。 let tuple2: [name: string, age: number, handsome?: boolean] [hh, 30, true]; // 具名元祖let tuple3: [string, number, boolean] [hh, 30, true]; tuple3.push(回龙观); // ✅ 像元组中增加数据只能增加元组中存放的类型但是为了安全依然无法取到新增的属性 // tuple3.push({ address: 回龙观 }); // ❎let tuple4: readonly [string, number, boolean] [hh, 30, true]; // 仅读元祖不能修改同时会禁用掉修改数组的相关方法我要求媳妇有车有房 满足即可底线 有可能我媳妇还有钱but 这个钱不能花 因为不知道有没有。 枚举类型 枚举可以看做是自带类型的对象枚举的值为数字时会自动根据第一个的值来递增 枚举中里面是数字的时候可以反举。 enum USER_ROLE {USER, // 默认从0开始ADMIN,MANAGER, } // {0: USER, 1: ADMIN, 2: MANAGER, USER: 0, ADMIN: 1, MANAGER: 2}可以枚举也可以反举 // 编译后的结果 (function (USER_ROLE) {USER_ROLE[(USER_ROLE[USER] 0)] USER;USER_ROLE[(USER_ROLE[ADMIN] 1)] ADMIN;USER_ROLE[(USER_ROLE[MANAGER] 2)] MANAGER; })(USER_ROLE || (USER_ROLE {}));异构枚举 既有数字也有字符串 enum USER_ROLE {USER user,ADMIN 1,MANAGER, // 2 }常量枚举 如果不需要对象如果只是使用值可以直接采用常量枚举否则用普通枚举 const enum USER_ROLE {USER,ADMIN,MANAGER, } console.log(USER_ROLE.USER); // console.log(0 /* USER */);null 和 undefined 任何类型的子类型如果TSconfig配置中strictNullChecks的值为 true则不能把 null 和 undefined 赋给其他类型。 let u1: undefined undefined; let n1: null null; // 默认情况下 只能null给null undefiend给undefiendlet name1: number | boolean; name1 null; name1 undefined; // 非严格模式void 类型 只能接受 nullundefined。void 表示的是空 (通常在函数的返回值中里来用)undefiend 也是空所以 undefiend 可以赋值给 void。严格模式下不能将 null 赋予给 void。 function fn1() {} function fn2() {return; } function fn3(): void {return undefined; }never 类型 任何类型的子类型never 代表不会出现的值这个类型不存在。不能把其他类型赋值给 never。 function fn(): never {// throw new Error();while (true) {} } let a: never fn(); // never只能赋予给never let b: number a; // never是任何类型的子类型可以赋值给任何类型never 实现完整性保护 function validate(type: never) {} // 类型“boolean”的参数不能赋给类型“never”的参数。 function getResult(strOrNumOrBool: string | number | boolean) {if (typeof strOrNumOrBool string) {return strOrNumOrBool.split();} else if (typeof strOrNumOrBool number) {return strOrNumOrBool.toFixed(2);}// 能将类型“boolean”分配给类型“never”。validate(strOrNumOrBool); }联合类型自动去除 never let noNever: string | number | boolean | never 1; // never自动过滤object 对象类型 object表示非原始类型 let create (obj: object) {}; create({}); create([]); create(function () {});这里要注意不能使用大写的 Object 或 {} 作为类型因为万物皆对象涵盖了原始数据类型。 object、Object、{} 的区别 object非原始类型Object所有值都可以赋予给这个包装类型大Object是类{}字面量对象类型 Symbol 类型 Symbol 表示独一无二 const s1 Symbol(key); const s2 Symbol(key); console.log(s1 s2); // 此条件将始终返回 false因为类型 typeof s11 和 typeof s12 没有重叠BigInt 类型 const num1 Number.MAX_SAFE_INTEGER 1; const num2 Number.MAX_SAFE_INTEGER 2; console.log(num1 num2); // truelet max: bigint BigInt(Number.MAX_SAFE_INTEGER); console.log(max BigInt(1) max BigInt(2));number类型和bigInt类型是不兼容的 any 类型 不进行类型检测一旦写了 any 之后任何的校验都会失效。声明变量没有赋值时默认为 any 类型写多了 any 就变成 AnyScript 了当然有些场景下 any 是必要的。 let arr: any [hh, true]; arr 回龙观;可以在 any 类型的变量上任意地进行操作包括赋值、访问、方法调用等等当然出了问题就要自己负责了。 变量类型推断 TypeScript 的类型推断是根据变量的初始化值来进行推断的。如果声明变量没有赋予值时默认变量是any类型。 let name; // 类型为any name hswen; name 30;声明变量赋值时则以赋值类型为准 let name hswen; // name被推导为字符串类型 name 30;联合类型 在使用联合类型时没有赋值只能访问联合类型中共有的方法和属性。 let name: string | number; // 联合类型 console.log(name.toString()); // 公共方法 name 30; console.log(name.toFixed(2)); // number方法 name hswen; console.log(name.toLowerCase()); // 字符串方法字面量联合类型 // 通常字面量类型与联合类型一同使用 type Direction Up | Down | Left | Right; let direction: Direction Down;可以用字面量当做类型同时也表明只能采用这几个值限定值。类似枚举。 对象的联合类型 type women | {wealthy: true;waste: string;}| {wealthy: false;morality: string;};let richWoman: women {wealthy: true,waste: 不停的购物,morality: 勤俭持家, // 对象类型的互斥 };可以实现对象中的属性互斥。 类型断言 将变量的已有类型更改为新指定的类型默认只能断言成包含的某个类型。 非空断言 let ele: HTMLElement | null document.getElementById(#app); console.log(ele?.style.color); // JS中链判断运算符 ele!.style.color red; // TS中非空断言ele元素一定有值可选链操作符 ?. 在访问对象的属性或方法时先检查目标对象及其属性是否存在。空值合并操作符 ?? 当左侧的表达式结果为 null 或 undefined 时会返回右侧的值。 类型断言 let name: string | number; (name! as number).toFixed(2); // 强制 (numbername!).toFixed(2);name as boolean; // 错误 类型 string | number 到类型 boolean 的转换可能是错误的尽量使用第一种类型断言因为在 React 中第二种方式会被认为是jsx语法 双重断言 let name: string | boolean; name! as any as string;尽量不要使用双重断言会破坏原有类型关系断言为 any 是因为 any 类型可以被赋值给其他类型。 函数类型 函数的类型就是描述了函数入参类型与函数返回值类型 函数的两种声明方式 通过 function 关键字来进行声明 function sum(a: string, b: string): string {return a b; } sum(a, b);可以用来限制函数的参数和返回值类型 通过表达式方式声明 type Sum (a1: string, b1: string) string; let sum: Sum (a: string, b: string) {return a b; };可选参数 let sum (a: string, b?: string): string {return a b || ; }; let sum (a: string, b: string b): string {return a b; }; sum(a);可选参数必须在其他参数的最后面。 剩余参数 const sum (...rest: string[]): string {return rest.reduce((memo, current) (memo current), ); }; sum(a, b, c, d);this 类型 this 类型要进行声明 type IThis typeof obj; function getName(this: IThis, key: keyof IThis) {return this[key]; } const obj { name: hh }; getName.call(obj, name);typeof 获取对应的类型keyof 获取类型对应的所有 key 类型 函数的重载 重载一般是有限的操作 重载指我们可以定义一些名称相同的方法通过定义不同的输入参数来区分这些方法。TypeScript 中的重载是伪重载只有一个具体实现是类型的重载而不是逻辑的重载 function toArray(value: number): number[]; function toArray(value: string): string[]; function toArray(value: number | string) {if (typeof value string) {return value.split();} else {return value.toString().split().map((item) Number(item));} } toArray(123); // 根据传入不同类型的数据 返回不同的结果 toArray(123);重载适合于已知有限数量类型的情况可以对不同类型的参数做出不同的处理。 类 类由三部分组成构造函数、属性实例属性、原型属性、方法实例方法、原型方法、访问器 TS 中定义类 class Circle {x!: number; // 实例上的属性必须先声明y!: number;constructor(x: number, y: number 0, ...args: number[]) {this.x x;this.y y;} } let p new Circle(100);实例上的属性需要先声明在使用构造函数中的参数可以使用可选参数和剩余参数。 类中的修饰符 public修饰符谁都可以访问到 class Animal {public name!: string; // 不写public默认也是公开的public age!: number;constructor(name: string, age: number) {this.name name;this.age age;} } class Cat extends Animal {constructor(name: string, age: number) {super(name, age);console.log(this.name, this.age);} } let p new Cat(Tom, 18); console.log(p.name, p.age); // 外层访问我们可以通过参数属性来简化父类中的代码。 class Animal {constructor(public name: string, public age: number) {this.name name;this.age age;} }protected修饰符 (自己和子类可以访问到) class Animal {constructor(protected name: string, protected age: number) {this.name name;this.age age;} } class Cat extends Animal {constructor(name: string, age: number) {super(name, age);console.log(this.name, this.age);} } let p new Cat(Tom, 18); console.log(p.name, p.age); // 属性“name”受保护只能在类“Animal”及其子类中访问。private修饰符 除了自己都访问不到 class Animal {constructor(private name: string, private age: number) {this.name name;this.age age;} } class Cat extends Animal {constructor(name: string, age: number) {super(name, age);console.log(this.name, this.age); // 无法访问} } let p new Cat(Tom, 18); console.log(p.name, p.age); // 无法访问 console.log(tom[name]) // 可以访问私有属性绕过ts检测readonly修饰符 仅读修饰符 reaonly 在构造函数中可以随意修改初始化 在其他的地方就不能再次修改了。 class Animal {constructor(public readonly name: string, public age: number) {this.name init;this.age age;}changeName(name: string) {this.name name; // 仅读属性只能在constructor中被赋值} } class Cat extends Animal {constructor(name: string, age: number) {super(name, age);} } let p new Cat(Tom, 18); p.changeName(Jerry);静态属性和方法 class Animal {static type 哺乳动物; // 静态属性static getName() {// 静态方法return 动物类;}private _name: string Tom;get name() {// 属性访问器return this._name;}set name(name: string) {this._name name;} } let animal new Animal(); console.log(animal.name);静态属性和静态方法是可以被子类所继承的。 Super 属性 class Animal {say(message: string) {console.log(message);}static getType() {return 动物;} } class Cat extends Animal {say() {// 原型方法中的super指代的是父类的原型super.say(猫猫叫);}static getType() {// 静态方法中的super指代的是父类return super.getType();} } let cat new Cat(); console.log(Cat.getType());这里要注意子类重写父类的方法类型需要兼容。 class Animal {say(message: string): void {// 这里的void表示不关心返回值console.log(message);} } class Cat extends Animal {say(message: string) {super.say(message);} } let cat new Cat(); cat.say(我要吃鱼);私有构造函数 class Singleton {private static instance new Singleton();private constructor() {/* 此类不能直接例化 */}public static getInstance() {return Singleton.instance;} } const instance1 Singleton.getInstance(); const instance2 Singleton.getInstance(); console.log(instance1 instance2);抽象类 抽象类描述了一个类中应当有哪些成员属性、方法等如果在父类中定义了抽象方法那么子类必须要实现。 抽象类中不能声明静态的抽象成员抽象类中可以包含具体的实现抽象类不能被new抽象类中可以创建抽象属性和方法让子类来实现但是静态方法、属性不可以 abstract class Animal {// abstract static type 哺乳动物 // “static”修饰符不能与“abstract”修饰符一起使用。// 可以在父类中定义抽象方法子类必须要实现abstract eat: () void; // 实例方法eatabstract play(): void; // 原型方法play// 提供的真实存在的方法drink() {return 喝水;} } class Tom extends Animal {eat!: () void;play() {} }重载 class ToArrayConverter {convert(value: number): number[];convert(value: string): string[];convert(value: number | string): number[] | string[] {if (typeof value string) {return value.split();} else {return value.toString().split().map((item) Number(item));}} } const converter new ToArrayConverter(); const result1: number[] converter.convert(123); const result2: string[] converter.convert(123);TS 中类型的使用 函数类型 函数的类型就是描述了函数入参类型与函数返回值类型 函数的两种声明方式 通过 function 关键字来进行声明 function sum(a: string, b: string): string {return a b; } sum(a, b);可以用来限制函数的参数和返回值类型 通过表达式方式声明 type Sum (a1: string, b1: string) string; let sum: Sum (a: string, b: string) {return a b; };类型推断 TypeScript 拥有类型推导能力根据用户的输入自动推导其类型。 赋值推断 赋值时推断类型从右像左流动,会根据赋值推断出变量类型 let name hswen; // string let age 30; // number let handsome true; // boolean返回值推断 自动推断函数返回值类型 function sum(a: string, b: string) {return a b; } sum(a, b); // string上下文类型 基于位置的类型推导反方向的类型推导 函数从左到右进行推断 type Sum (x: string, y: number) string; const sum: Sum (a, b) a b; // a string b number let result sum(hswen, 30); // result stringtype ICallback (a: string, b: number, c: boolean) void; function fn(callback: ICallback) {let result callback(1, 1, true); // result - void }// d类型无法正确推断因为上下文类型是基于位置推断的 // 这里的void表示不关心具体类型 fn((a, b, c, d) 100);这里再次强调为什么 void 代表不关心为什么这样设计呢 [1, 2, 3].forEach((item) item); // forEach回调没有返回值但是用户确可以随意返回内容you known接口 接口可以在面向对象编程中表示行为的抽象也可以描述对象的形状。 接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。 (接口中不能含有具体的实现逻辑) 用来描述数据形状的 对象、类、函数、混合类型接口中的内容都是抽象的 不能有具体的实现 函数接口参数 我们可以约束函数中的参数但是类型无法复用。 const fullName ({firstName,lastName, }: {firstName: string;lastName: string; }): string {return firstName lastName; };我们可以通过接口进行描述 interface IFullName {firstName: string;lastName: string; } const fullName ({ firstName, lastName }: IFullName): string {return firstName lastName; };函数类型接口 interface IFullName {firstName: string;lastName: string; } interface IFn {(obj: IFullName): string; } const fullName: IFn ({ firstName, lastName }) {return firstName lastName; };通过接口限制函数的参数类型和返回值类型。 type 与 interface 区别 一般场景下我们使用 interface 用来描述对象、类的结构。 使用类型别名来描述函数签名、联合类型、工具类型、 映射类型。 type 可以用联合类型 type xx string | number interface 不能用联合类型type 别名不能被扩展继承interface 可以被继承和实现type 不能重名 interface 可以重名会合并type 可以做循环和条件interface 不行函数类型一般采用type声明 其它场景下两者可以替换使用无伤大雅。 函数混合类型 interface ICounter {(): number; // 限制函数类型count: 0; // 限制函数上的属性 } const fn: ICounter () {// 这里需要使用const进行声明可能是因为防止fn被重新赋值因为用let修改了值可能属性就不存在了return fn.count; }; fn.count 0; let counter: ICounter fn; console.log(counter()); console.log(counter());对象接口 对象接口可以用来描述对象的形状结构 interface IVegetables {// 类型color: string;taste: string;size: number; } let veg1: IVegetables {// 定义color: red,taste: sweet,size: 10,a: 1, // 如何增添这个a属性呢 };方案 1直接采用断言的方式指定为当前赋值的类型方案 2在类型中通过?增添 a 属性为可选属性方案 3利用同名接口合并的特点方案 4通过接口继承的方式扩展属性方案 5通过任意接口来扩展类型兼容性、交叉类型等 标识的属性为可选属性, readOnly标识的属性则不能修改。多个同名的接口会自动合并 interface IVegetables {readonly color: string;size: string;taste: sour | sweet; } interface IVegetables {a?: number; } const tomato: IVegetables {color: red,size: 10,taste: sour, }; tomato.color green; // 仅读属性不能进行修改任意属性、可索引接口 interface Person {name: string;[key: string]: any; // 索引签名类型 } let p: Person {name: hswen,age: 30,[Symbol()]: 回龙观, };任意属性可以对某一部分必填属性做限制其余的可以随意增减。 interface IArr {[key: number]: any; } let p: IArr {0: 1,1: 2,3: 3, }; let arr: IArr [1, d, c];可索引接口可以用于标识数组 索引访问操作符 interface IPerson1 {name: string;age: number;[key: string]: any; } // 访问接口中的类型需要使用[], 不能使用. type PropType1 IPerson1[name]; type PropType2 IPerson1[string];interface IPerson2 {name: string;age: number; } type PropTypeUnion keyof IPerson2; // name | age type PropTypeValueUnion IPerson2[PropTypeUnion]; // string | number类接口 这里先来强调一下抽象类和接口的区别,抽象类中可以包含具体方法实现接口中不能包含实现。 interface Speakable {name: string;speak(): void; } // 这里不区分是实例的方法还是原型的方法 interface ChineseSpeakable {// speakChinese:()voidspeakChinese(): void; // 一般采用这种方式这种方式不进行逆变检测 } class Speak implements Speakable, ChineseSpeakable {name!: string;speak() {}speakChinese() {} }一个类可以实现多个接口在类中必须实现接口中的方法和属性。 接口继承 interface Speakable {speak(): void; } interface SpeakChinese extends Speakable {speakChinese(): void; } class Speak implements SpeakChinese {speakChinese(): void {throw new Error(Method not implemented.);}speak(): void {throw new Error(Method not implemented.);} }构造函数类型 interface Clazz {new (name: string): any; } // type IClazz new () any function createClass(target: Clazz, name: string) {return new target(name); // 传入的是一个构造函数 } class Animal {constructor(public name: string) {this.name name;} } let r createClass(Animal, Tom);这里无法标识返回值类型。 interface ClazzT {new (name: string): T; } function createClassT(target: ClazzT, name: string): T {return new target(name); } class Animal {constructor(public name: string) {this.name name;} } let r createClass(Animal, Tom);new() 表示当前是一个构造函数类型,这里捎带使用了下泛型。 在使用createClass时动态传入类型。 泛型 泛型就是在使用的时候确定类型泛型类似于函数的参数。泛型参数的名称通常我们使用大写的 T / K / U / V / M / O …这种形式。 指定函数参数类型 单个泛型 const getArray T(times: number, val: T): T[] {let result: T[] [];for (let i 0; i times; i) {result.push(val);}return result; }; getArray(3, 3); // 3 T number多个泛型 function swapT, K(tuple: [T, K]): [K, T] {return [tuple[1], tuple[0]]; } console.log(swap([hswen, 30]));函数标注的方式 类型别名 type TArray T, K(tuple: [T, K]) [K, T]; const swap: TArray T, K(tuple: [T, K]): [K, T] {return [tuple[1], tuple[0]]; };接口 interface IArray {T, K(typle: [T, K]): [K, T]; } const swap: IArray T, K(tuple: [T, K]): [K, T] {return [tuple[1], tuple[0]]; };两种标注方式均可但是对于函数而言我们通常采用类型别名的方式。 泛型使用的位置 实现一个数组循环函数 // type ICallback T(item: T, index: number) void; ❎错误写法这样写意味着调用函数的时候确定泛型 type ICallbackT (item: T, index: number) void; type IForEach T(arr: T[], callback: ICallbackT) void;const forEach: IForEach (arr, callback) {for (let i 0; i arr.length; i) {callback(arr[i], i); // ts 类型检测 此时不会执行代码。} }; forEach([1, 2, a, b], function (item) {console.log(item); });泛型T 写在前面就是表示使用类型的时候传参写到函数的前面意味着着调用函数的时候传递参数 默认泛型 在使用一些联合类型的时候会使用泛型 type UnionT string number | T; const u1: Union abc; const u2: Unionboolean true;可以指定泛型的默认类型让使用更方便。 泛型约束 使用 extends 关键字来约束传入的泛型参数必须符合要求。A extends B 意味着 A 是 B 的子类型 abc extends stringa extends a | b… 案例 1: function handleT extends string | number(input: T): T {return input; }案例 2: interface IWithLength {length: number; } function getLenT extends IWithLength(val: T) {return val.length; } getLen(hello);案例 3: const getVal T, K extends keyof T(obj: T, key: K): T[K] {return obj[key]; }; getVal({ name: hh }, name);泛型约束经常也配合着条件类型来使用后面讲到条件类型时在详细说明。 对象中的泛型 通过接口定义一个特定的响应类型结构 interface ApiResponseT any {code: number;data: T;message?: string; }调用接口时传入返回数据的结构类型 通泛型坑位来占位置 interface LoginRes {// 登录接口的返回值token: string;roles: number[]; }function toLogin(): ApiResponseLoginRes {return {code: 0,data: {token: Bear token,roles: [1, 2],},}; }类中的泛型 创建实例时提供类型 class MyArrayT {// T numberarr: T[] [];add(num: T) {this.arr.push(num);}getMaxNum(): T {let arr this.arr;let max arr[0];for (let i 1; i arr.length; i) {let current arr[i];current max ? (max current) : null;}return max;} } let myArr new MyArraynumber(); // 没有传递类型默认类型为unknown myArr.add(3); myArr.add(1); myArr.add(2); console.log(myArr.getMaxNum());交叉类型 交叉类型(Intersection Types)是将多个类型合并为一个类型 联合类型的符号是|类似按位或。只需要符合联合类型中的一个类型即可。 并集交叉类型的符号是类似按位与。需同时满足类型。 交集 interface Person1 {handsome: string; } interface Person2 {high: string; } type P1P2 Person1 Person2; let p: P1P2 { handsome: 帅, high: 高 };举例我们提供两拨人一拨人都很帅、另一拨人很高。我们希望找到他们的交叉部分 又高又帅的人。 interface Person1 {handsome: string;address: {pos: string;}; } interface Person2 {high: string;address: {pos: number;}; } type P1P2 Person1 Person2; // address 内部也会进行交叉类型 type POS P1P2[address][pos]; // never string number交叉类型 function mixinT, K(a: T, b: K) {return { ...a, ...b }; } const x mixin({ name: hs, age: 30 }, { age: 20 });这里返回值默认会被识别成交叉类型但是如果两个对象中有相同属性类型不同则默认推导会出现问题后续我们再来解决这个问题。 unknown unknown类型任何类型都可以赋值为unknown类型。 它是 any 类型对应的安全类型。any 叫做不检测了 unknown 要进行类型检测 不能访问 unknown 类型上的属性不能作为函数、类来使用 // 类型检查后使用 function processInput(input: unknown) {if (typeof input string) {console.log(input.toUpperCase());} else if (typeof input number) {console.log(input.toFixed(2));} else {console.log(input); // unknown} } // 类型断言后使用 let name: unknown hswen; (name as string).toUpperCase();使用 unknown 类型需要进行类型检查或类型断言后再进行使用。 unknown 特性 联合类型中的unknown type UnionUnknown unknown | null | string | number;联合类型与unknown都是unknown类型 交叉类型中的unknown unknown 表示类型未知 null 是一种具体的值结果会受到 null 的限制最终结果会变成 null 类型而不是保持 unknown 类型。 type inter unknown null; // null type inter any null; // any交叉类型与unknown都是其他类型 keyof unknown 是 never type key keyof unknown; // never // type key keyof any; // string | number | symbol条件类型 条件类型的语法类似于三元表达式 条件类型基本使用 可以使用extends关键字和三元表达式实现条件判断。条件类型大部分场景是和泛型一起使用的 type ResStatusMessageS extends number S extends 200 | 201 | 204? success: fail; type Message ResStatusMessage300; // 传入要判断的类型type ConditionalT, C T extends C ? true : false; type R1 Conditionalhswen, string; // true type R2 Conditionalhswen, number; // false 条件也可以通过泛型传入interface Fish {name: 鱼; } interface Water {type: 水; } interface Bird {name: 鸟; } interface Sky {type: 天空; } type ConditionT T extends Fish ? Water : Sky; // 类型相同也可以使用extends let con1: ConditionFish { type: 水 };多条件类 type FormatReturnTypeT T extends string // 可以编写多条件类型? string: T extends number? number: never;function sumT extends string | number(x: T, y: T): FormatReturnTypeT {// 泛型不能做数学运算return x (y as any); } sum(abc, abc); // string sum(123, 123); // number类型兼容性问题 extends 本质上是判断类型的兼容性只需要兼容则条件即可成立 基本数据类型的兼容性 type R1 abc extends string ? true : false; // true type R2 123 extends number ? true : false; // true type R3 true extends boolean ? true : false; // true// so~~~ let r1: string abc; let r2: number 123; let r3: boolean true;字面量类型可以赋予给原始数据类型。 联合类型的兼容性 在联合类型中只需要符合其中一个类型即是兼容从安全角度来看就是你赋值的类型我这里支持。 type R4 a extends a | b | c ? true : false; // true type R5 123 extends 123 | 456 | 789 ? true : false; // true type R6 string extends boolean | string | number ? true : false;// so~~~ let r4: a | b | c a; let r5: 123 | 456 | 789 123; let temp hello; let r6: boolean | string | number temp;联合类型中所有成员在另一个联合类型中都能找到就是兼容 原始类型与装箱类型兼容性 大写的就是装箱类型 type R7 string extends String ? true : false; // true type R8 number extends Number ? true : false; // true type R9 object extends Object ? true : false; // true type R10 String extends Object ? true : false; // true// so~~~ let r7: String abc; let r8: Number 123; let r9: Object {}; let r10: Object new String(abc);原始类型可以赋予给装箱类型最终可以赋予给 Object 类型。 any 及 unknown type R11 Object extends any ? true : false; // true type R12 Object extends unknown ? true : false; // true// so~~~ let tempObj: Object {}; let r11: any tempObj; let r12: unknown tempObj;any 和 unkown 即为顶级类型。 其它类型的兼容性 never 是任何类型的子类型也就是最底端的类型null 和 undefiend 在严格模式下不能赋予给其他类型。undefined 可以赋予给 void 类型 type R13 never extends abc ? true : false; // true type R14 undefined extends undefined ? true : false; // true type R15 null extends null ? true : false; // true type R16 undefined extends void ? true : false; // truenever 为最底端类型。 Never《 字面量《 字面量联合类 型| 字面量类型《 原始数据类型《 包装类型 《 Object any|unnknown 类型层级 根据类型兼容性我们可以得出以下结论 never 字面量类型字面量类型 字面量类型的联合类型原始类型 原始类型的联合类型原始类型 装箱类型 Object 类型Object any | unknown unknown any 特殊情况 type R17 unknown extends 1 ? true : false; // 不能赋予给除unknown之外的类型 type R18 any extends 1 ? true : false; // boolean type R19 any extends any ? true : false; // 条件是 any依然会进行判断any可以分解成条件满足、和不满足两部分则返回条件类型结果组成的联合类型。但是与any 进行判断时依然会进行正常判断。 {} | object | Object 特殊情况 type R20 {} extends object ? true : false; // true type R21 {} extends Object ? true : false; // true// 鸭子类型检测可以看出对象是基于{}扩展出来的 type R22 Object extends {} ? true : false; // true type R23 object extends {} ? true : false; // true// 以下两种情况均默认成立 type R24 Object extends object ? true : false; // true type R25 object extends Object ? true : false; // true条件类型与映射类型 条件类型分发 出现条件分发的场景 类型参数需要是一个联合类型。类型参数需要通过泛型参数的方式传入条件类型中的泛型参数是否完全裸露只有裸类型才可以被分发。 type Condition1 Fish | Bird extends Fish ? Water : Sky; // sky type Condition2T T extends Fish ? Water : Sky; type R1 Condition2Fish | Bird; // water | sky这里会用每一项依次进行分发,最终采用联合类型作为结果,等价于: type c1 Condition2Fish; type c2 Condition2Bird; type c c1 | c2;禁用分发 默认情况下有些时候我们需要关闭这种分发能力会造成判断不准确 // type unionAssetsT, U T extends U ? true : false; type R1 unionAssets1 | 2, 1 | 2 | 3; // true 看似正常 type R2 unionAssets1 | 2, 1; // boolean (开启分发类型结果为boolean)// 禁用分发 type unionAssetsT, U [T] extends [U] ? true : false; type NoDistributeT T {}; // 这种情况会返回一个新类型从而阻止分发 type unionAssetsT, U NoDistributeT extends U ? true : false;特殊问题 通过泛型传入的参数为 never则会直接返回 never。 type isNever1T T extends never ? true : false; type isNever2T [T] extends [never] ? true : false; // 包裹后不在是never type R4 isNever1never; // 返回never type R5 isNever2never; // 返回true内置条件类型 Extract抽取类型(交集) type ExtractT, U T extends U ? T : never; type MyExtract Extract1 | 2 | 3, 1 | 2;Exclude排除类型(差集) type ExcludeT, U T extends U ? never : T; type MyExclude Exclude1 | 2 | 3, 1 | 2;补集如何实现呢约束 U 是 T 的子集求出来的就是补集了。 type ComplementT, U extends T T extends U ? never : T; type MyComplement Complement1 | 2 | 3, 1 | 2; // 补集NoNullable 非空检测 type NonNullableT T extends null | undefined ? never : T; type NonNullableT T {}; // 保留联合类型中非空的值 type MyNone NonNullablea | null | undefined;infer 类型推断 TypeScript 中通过 inferinference关键字在条件类型中提取类型的某一部分信息。根据 infer 的位置不同我们就能够获取到不同位置的类型。 基于 infer 的内置类型 使用 infer 需要先创造一个条件才可以 ReturnType 返回值类型 function getUser(a: number, b: number) {return { name: hswen, age: 30 }; } type ReturnTypeT T extends (...args: any) infer R ? R : never; type MyReturn ReturnTypetypeof getUser;Parameters 参数类型 type ParametersT T extends (...args: infer R) any ? R : any; type MyParams Parameterstypeof getUser;ConstructorParameters 构造函数参数类型 class Person {constructor(name: string, age: number) {} } type ConstructorParametersT T extends { new (...args: infer R): any }? R: never; type MyConstructor ConstructorParameterstypeof Person;InstanceType 实例类型 type InstanceTypeT T extends { new (...args: any): infer R } ? R : any; type MyInstance InstanceTypetypeof Person;内置类型的使用 function createInstanceT extends new (...args: any[]) any(Ctor: T,...args: ConstructorParametersT ): InstanceTypeT {return new Ctor(...args); } class Animal {constructor(public name: string) {} } const animal createInstance(Animal, 动物);infer 实践 类型交换 type SwapT T extends [infer A, infer B] ? [B, A] : T; type SwapS1 Swap[hh, 30]; // [30, hh] type SwapS2 Swap[1, 2, 3]; // [1, 2, 3]type TailToHeadT T extends [infer A, ...infer Args, infer B]? [B, A, ...Args]: T; type R100 TailToHead[hh, 30, 回龙观]; // [回龙观, hh, 30]递归推断 type PromiseValT T extends Promiseinfer V ? PromiseValV : T; type PromiseResult PromiseValPromisePromisenumber; // number将数组类型转化为联合类型 type ElementOfT T extends Arrayinfer E ? E : never; type TupleToUnion ElementOf[string, number, boolean]; type TupleToUnion [string, number, boolean][number];映射类型 所谓的映射类型类似于 map 方法。核心就是基于键名映射到键值类型 使用的是 in 关键字 type A1 { name: string }; type A2 { age: number };type ComputeT {// 映射类型 索引类型查询 索引类型访问[K in keyof T]: T[K]; }; type A1A2 ComputeA1 A2; // {name:string,age:number}Partial 转化可选属性 interface Company {num: number; } interface Person {name: string;age: string;company: Company; } // type PartialT { [K in keyof T]?: T[K] }; 实现原理 type PartialPerson PartialPerson;遍历所有的属性将属性设置为可选属性,但是无法实现深度转化! type DeepPartialT {[K in keyof T]?: T[K] extends object ? DeepPartialT[K] : T[K]; }; type DeepPartialPerson DeepPartialPerson;我们可以实现深度转化,如果值是对象继续深度转化。 Required type PartialPerson PartialPerson; type RequiredT { [K in keyof T]-?: T[K] }; type RequiredPerson RequiredPartialPerson;将所有的属性转化成必填属性 Readonly 转化仅读属性 type ReadonlyT { readonly [K in keyof T]: T[K] }; type ReadonlyPerson ReadonlyPartialPerson;将所有属性变为仅读状态。 type MutableT { -readonly [K in keyof T]: T[K] }; // 所有属性变成可变属性 type MutablePerson MutableReadonlyPerson;结构类型 Pick 挑选所需的属性 type PickT, U extends keyof T { [P in U]: T[P] }; type PickPerson PickPerson, name | age;在已有类型中挑选所需属性。 Omit 忽略属性 let person {name: hs,age: 11,address: 回龙观, }; type OmitT, K extends keyof T PickT, Excludekeyof T, K; type OmitAddress Omittypeof person, address;忽略 person 中的 address 属性 (先排除掉不需要的 key在通过 key 选出需要的属性) function mixinT, K(a: T, b: K): OmitT, keyof K K {return { ...a, ...b }; } const x mixin({ name: hs, age: 30 }, { age: 20 });Record 记录类型 只想要 key- value 的格式可以采用 Record 类型 record 通常用来代替 object 。 type RecordK extends keyof any, T { [P in K]: T }; let person: Recordstring, any { name: hswen, age: 30 };实现 map 方法我们经常用 record 类型表示映射类型 function mapT extends keyof any, K, U(obj: RecordT, K,callback: (item: K, key: T) U ) {let result {} as RecordT, U;for (let key in obj) {result[key] callback(obj[key], key);}return result; } const r map({ name: hswen, age: 30 }, (item, key) {return item; });兼容性 TypeScript 的类型系统特性结构化类型系统(鸭子类型检测)TypeScript 比较两个类型不是通过类型的名称而是比较这两个类型上的属性与方法。 基本数据类型的兼容性 你要的我有就可以 let obj: {toString(): string; }; let str: string hh; obj str; // 字符串中具备toString()方法所以可以进行兼容string 可以看成基于对象 toString 进行扩展的子集从安全度考虑因为在最后使用 obj 时只允许调用 toString 方法 接口兼容性 interface IAnimal {name: string;age: number; } interface IPerson {name: string;age: number;address: string; } let animal: IAnimal; let person: IPerson {name: hh,age: 30,address: 回龙观, }; animal person;接口的兼容性只要满足接口中所需要的类型即可 函数的兼容性 函数的兼容性主要是比较参数和返回值 参数 let sum1 (a: string, b: string) a b; let sum2 (a: string) a; sum1 sum2;赋值函数的参数要少于等于被赋值的函数与对象相反,例如: type FuncT (item: T, index: number) void; function forEachT(arr: T[], cb: FuncT) {for (let i 0; i arr.length; i) {cb(arr[i], i);} } forEach([1, 2, 3], (item) {console.log(item); });返回值 type sum1 () string | number; type sum2 () string;let fn1: sum1; let fn2!: sum2; fn1 fn2;类的兼容性 class ClassA {name: string hh;age: number 30; } class ClassB {name: string hh;age: number 30;address: string 回龙观; } let parent: ClassA new ClassB(); // 可以看成ClassB是继承于ClassA的子类子类赋予给父类兼容这里要注意的是只要有 private 或者 protected 关键字类型就会不一致 class ClassA {private name: string hh;age: number 30; } class ClassB {private name: string hh;age: number 30; } let clazz: ClassA new ClassB(); // 不能将类型“ClassB”分配给类型“ClassA”。这也做到了模拟标称类型系统结构化类型导致的问题 type BTC number; // 无法区分两个类型 type USDT number;let btc: BTC 1000; let usdt: USDT 1000; // 要求传入btc function getCount(count: BTC) {return count as BTC; } let count getCount(usdt); // 实际传入usdttype NominalT, U extends string T { __tag: U }; type BTC Nominalnumber, btc; type USDT Nominalnumber, usdt; // 标称类型let btc: BTC 1000 as BTC; let usdt: USDT 1000 as USDT; function getCount(count: BTC) {// 获取BTC的数量return count; } let count getCount(usdt); // 报错无法传入usdt函数的逆变与协变 函数的参数是逆变的返回值是协变的 在非严格模式下 StrictFunctionTypes:false 函数的参数是双向协变的 class Parent {house() {} } class Child extends Parent {car() {} } class Grandson extends Child {sleep() {} } function fn(callback: (instance: Child) Child) {// 在使用此回调方法时可以传递 自己、或者子类型callback(new Child());let ins callback(new Grandson()); // 如果传递的是子类型在使用的时候无法使用多出来的属性// ins是Child类型我可以将Grandson类型传入。用的时候我只会调用Child类型的方法。因为安全所以兼容 } fn((instance: Parent) {// instance.sleep() 这个不安全。因为如果传递的是Child 他不具备。// 但是如果这里标识Parent 是可以的。因为调用instance.house() 是安全的。return new Grandson(); });通过这个案例可以说明函数签名类型中参数是逆变的返回值可以返回子类型所以称之为协变的。 随着某一个量的变化而变化一致的即称为协变而变化相反的即称为逆变。但是参数逆变也会带来一些问题。 传递的函数传父参数是逆变的返子返回值是协变的) 由此可得: type ArgT (arg: T) void; type ReturnT (arg: any) T; type ArgReturn ArgParent extends ArgChild ? true : false; // 基于函数参数的逆变 type ReturnReturn ReturnGrandson extends ReturnChild ? true : false; // 返回值是协变的逆变带来的问题: interface ArrayT {// concat: (...args: T[]) T[]; // 严格检参数测逆 Child 无法 赋予给 Parentconcat(...args: T[]): T[]; // 不进行参数逆变检测[key: number]: T; } let parentArr!: ArrayParent; let childArr!: ArrayChild;parentArr childArr; // 子应该可以赋予给父的~~~泛型的兼容性 interface ITT {} let obj1: ITstring; let obj2!: ITnumber; obj1 obj2;枚举的兼容性 enum USER1 {role 1, } enum USER2 {role 1, } let user1!: USER1; let user2!: USER2; user1 user2; // 错误语法不同的枚举类型不兼容。 类型保护 通过判断、识别所执行的代码块自动识别变量属性和方法。将类型范围缩小。 typeof类型保护 function double(val: number | string) {if (typeof val number) {val.toFixed();} else {val.charAt(0);} }instanceof类型保护 class Cat {} class Dog {}const getInstance (clazz: { new (): Cat | Dog }) {return new clazz(); }; let r getInstance(Cat); if (r instanceof Cat) {r; } else {r; }in类型保护 interface Fish {swiming: string; } interface Bird {fly: string;leg: number; } function getType(animal: Fish | Bird) {if (swiming in animal) {animal; // Fish} else {animal; // Bird} }可辨识联合类型 interface WarningButton {class: warning; } interface DangerButton {class: danger; } function createButton(button: WarningButton | DangerButton) {if (button.class warning) {button; // WarningButton} else {button; // DangerButton} } // -----------类型中有独一无二的特性--------------- function ensureArrayT(input: T | T[]): T[] {return Array.isArray(input) ? input : [input]; }null 保护 const addPrefix (num?: number) {num num || 1.1;function prefix(fix: string) {return fix num?.toFixed();}return prefix($); }; console.log(addPrefix());这里要注意的是 ts 无法检测内部函数变量类型。 自定义类型保护 interface Fish {swiming: string; } interface Bird {fly: string;leg: number; } function isBird(animal: Fish | Bird): animal is Bird {return swiming in animal; } function getAniaml(animal: Fish | Bird) {if (isBird(animal)) {animal;} else {animal;} }自定义类型 内置类型可以分为以下几种类别: Partial、Required、Readonly 起到修饰的作用Pick Omit 处理数据结构Exclude、Extract 处理集合类型Parameters ReturnType 等 模式匹配类型 部分属性可选修饰类型 // 解题思路将对应的属性挑选出来变为可选项 忽略掉对应的属性 type PartialPropsOptionalT extends object, K extends keyof T PartialPickT, KOmitT, K;interface Person {name: string;age: number;address: string; } type ComputeT {[K in keyof T]: T[K]; }; type t1 ComputePartialPropsOptionalPerson, age | address;根据值类型挑选/忽略对象类型的属性 结构类型 // 解题思路先找出类型相等的key在通过Pick/Omit进行筛选// 1判断两个类型是否相等 type IsEqualT, U, Success, Fail [T] extends [U]? [U] extends [T]? Success: Fail: Fail;// 2) 如果相等则返回对应的key。再取其联合类型 type ExtractKeysByValueTypeT extends object, U {[K in keyof T]: IsEqualT[K], U, K, never; }[keyof T];// 3 通过联合类型挑选出所需的类型 type PickKeysByValueT extends object, U PickT,ExtractKeysByValueTypeT, U ;type t2 PickKeysByValuePerson, string; // {name:string,address:string}// 在来实现Omit编写Omit逻辑应到正好相反 type ExtractKeysByValueTypeT extends object, U, O false {[K in keyof T]: IsEqualT[K],U,IsEqualO, true, never, K, // 是Omit 则为neverIsEqualO, true, K, never // 不是Omit 就返回key; }[keyof T]; type OmitKeysByValueT extends object, U PickT,ExtractKeysByValueTypeT, U, true // 增加类型来判断是否是Omit ;type t3 OmitKeysByValuePerson, string;// 重映射实现 type PickKeysByValueT extends object, U {[K in keyof T as T[K] extends U ? K : never]: T[K]; };子类型互斥集合类型 interface Man1 {fortune: string; } interface Man2 {funny: string; } interface Man3 {foreign: string; } // type ManType Man1 | Man2 | Man3; // 我希望MainType只能是其中的一种类型 // let man: ManType { // fortune: 富有, // funny: 风趣, // foreign: 洋派, // };// 1将对象的差集标记为never type DiscardTypeT, U { [K in Excludekeyof T, keyof U]?: never };// 2) 差集never 另一半 // (man1 - man2) 这里的属性标记为never man2 // (man2 - man1) 这里的属性标记为never man1 type OrTypeT, U (DiscardTypeT, U U) | (DiscardTypeU, T T); // type ManType OrTypeMan1, Man2; type ManType OrTypeMan1, OrTypeMan2, Man3;对象的交、差、并、补 (集合类型) type A {name: string;age: number;address: string; };type B {name: string;male: boolean;address: number; };交集 type ObjectInterT extends object, U extends object PickT,Extractkeyof T, keyof U ;差集 type ObjectDiffT extends object, U extends object PickT,Excludekeyof T, keyof U ;补集 // T多U少 type ObjectCompT extends U, U extends object PickT,Excludekeyof T, keyof U ;重写 以后面的类型为准取交集在加上以前比现在多的类型。 // 取出覆盖的类型 加上差集 type OverwriteT extends object, U extends object ObjectInterU, T ObjectDiffT, U;模式匹配类型 // 推断函数类型中参数的最后一个参数类型 type LastParameterT extends (...args: any[]) any T extends (...arg: infer P ) any? P extends [...any, infer L]? L: never: never;借助 Parameters 类型简化 type LastParameterT extends (...args: any[]) any ParametersT extends [...any,infer Q ]? Q: never;模块及命名空间使用 模块和命名空间 默认情况下 ,我们编写的代码处于全局命名空间中 模块 文件模块 如果在你的 TypeScript 文件的根级别位置含有 import 或者 export那么它会在这个文件中创建一个本地的作用域。 // a.ts导出 export default hh;// index.ts导入 import name from ./a;ESM 可以打包成Commonjs规范以及AMD规范但是commonjs规范无法打包成AMD规范。 如果一个模块是用commonjs规范来编写的那么也无法采用 ES 模块方式来导入 TS 模块语法 // a.ts导出 export hh;// index.ts导入 import name require(./a); // 也可以采用ES的方式导入同时也可以打包成Commonjs或者AMD模块命名空间 命名空间可以用于组织代码避免文件内命名冲突内部模块。想要被外界使用也可以通过 export 导出命名空间。 命名空间的使用 // a.ts导出 export namespace Zoo {export class Dog {eat() {console.log(zoo dog);}} } export namespace Home {export class Dog {eat() {console.log(home dog);}} } // index.ts导入 import { Zoo, Home } from ./a; let dog_of_zoo new Zoo.Dog(); dog_of_zoo.eat(); let dog_of_home new Home.Dog(); dog_of_home.eat();命名空间嵌套使用 export namespace Earth {export namespace Contry {export class China {}export class America {}} } Earth.Contry.China; Earth.Contry.America;命名空间中导出的变量可以通过命名空间使用。 命名空间合并 同名的命名空间可以自动合并 如果命名空间散落到多个文件中想要被合并可以采用后面要学的三斜线指令。 export namespace Zoo {export class Dog {eat() {console.log(zoo dog);}} } export namespace Zoo {export class Monkey {eat() {console.log(zoo monkey);}} }命名空间也可用于扩展类、扩展方法、扩展枚举类型。 class A {static b hello b; } namespace A {export let a hello a; }function counter(): number {return counter.count; } namespace counter {export let count 0; }enum ROLE {user 0, } namespace ROLE {export let admin 1; }类型声明 声明全局变量 普通类型声明 declare let age: number; declare function sum(a: string, b: string): void; declare class Animal {} declare const enum Seaons {Spring,Summer,Autumn,Winter, } declare interface Person {name: string;age: number; }一般情况下我们会将 declare 声明的内容放置到类型声明文件中即.d.ts中这样不会影响核心代码并且统一管理。默认项目编译时会查找所有以.d.ts结尾的文件。 练习: 声明 jQuery 类型 jquery 通过外部 CDN 方式引入想在代码中直接使用 interface JQuery {height(num?: number): this;width(num?: number): this;extend(obj: object): this; }// $(.box).height(100).width(100); // $.fn.extend({});声明模块 // declare.d.ts declare module mitt {type Type string | symbol;type Listener (...args: any[]) void;const on: (type: Type, listener: Listener) this;const emit: (type: Type, ...args: any[]) boolean;const off: (type: Type, listener: Listener) Listener; } declare module *.jpg {const str: string;export default str; }// index.ts import mitt from mitt; import type { Listener } from mitt; // 仅导入类型 import url from a.jpg; let listener: Listener function (data) {console.log(data); }; mitt.on(data, listener); mitt.emit(data, this is data); mitt.off(data, listener);第三方声明文件 types 是一个约定的前缀所有的第三方声明的类型库都会带有这样的前缀 npm install types/jquery -S当使用 jquery 时默认会查找 node_modules/types/jquery/index.d.ts 文件 查找规范 node_modules/jquery/package.json 中的 types 字段node_modules/jquery/index.d.tsnode_modules/types/jquery/index.d.ts 自己编写的声明文件放到目录中types/lodash // lodash.d.ts /// reference path./lodash_a.d.ts / export _; // 将_当做模块导出 export as namespace _; // 将这个模块作为全局变量使用不需要导入在不是作用域的文件中可以直接使用umd 模块declare namespace _ {function a(): void;function b(): void;function c(): void; }// lodash_a.d.ts import _ require(./lodash); declare module ./lodash {// 对模块进行扩展function x(): void;function y(): void;function z(): void; }namespace表示一个全局变量包含很多子属性 , 命名空间内部不需要使用 declare 声明属性或方法 /// reference path./lodash_a.d.ts / export _; export as namespace _; declare const _: _.ILodash; // 通过接口的方式导出 declare namespace _ {interface ILodash {// 将模块内的属性全部放到接口中a(): void;b(): void;c(): void;} }import _ require(./lodash); declare module ./lodash {interface ILodash {// 采用接口合并的特性进行扩展x(): void;y(): void;z(): void;} }三斜线指令 三斜线指令就是声明文件中的导入语句用于声明当前的文件依赖的其他类型声明。 三斜线指令必须被放置在文件的顶部才有效 /// reference path./lodash_a.d.ts / // 依赖的某个声明 /// reference typesnode / // 依赖的某个包 /// reference libdom / // 依赖的内置声明我们一般只使用第一种方式来进行声明的整合。 扩展全局变量类型 可以直接使用接口对已有类型进行扩展 interface String {double(): string; } String.prototype.double function () {return (this as string) this; };interface Window {mynane: string; } console.log(window.mynane);模块内全局扩展 declare global {interface String {double(): string;}interface Window {myname: string;} }声明全局表示对全局进行扩展。 TS 注释 ts-ignore 忽略下一行的检测不管是否有错误。 // ts-ignore let name: string 30;ts-expect-error 下一行代码真的存在错误时才能被使用。 // ts-expect-error const age: number 30;ts-nocheck 忽略整个文件的类型检测 // ts-nocheck const age: number 30; const name: string 30;ts-check 用于为 JavaScript 文件进行类型检查 需要配合 JSDoc // ts-check /**param {string} aparam {string} breturns {string} */ function getType(a, b) {return a b; } getType(1, 2);/** type {string} */ const age 30;类型体操 基于字符串 CapitalizeString 首字母大写 // 默认情况下同时进行推断左边只有一个字母 export type CapitalizeStringT T extends ${infer L}${infer R}? ${CapitalizeL}${R} // 左边大写 右边剩下的: T; // 不是字符串则直接返回// ---------------------------------type a1 CapitalizeStringhandler; // Handler type a2 CapitalizeStringparent; // Parent type a3 CapitalizeString233; // 233FirstChar 获取字符串字面量中的第一个字符 export type FirstCharT T extends ${infer L}${infer R} ? L : never;// ---------------------------------type A FirstCharBFE; // B type B FirstChardev; // d type C FirstChar; // neverLastChar 获取字符串字面量中的最后一个字符 // 拆分左右两边类型将右边递归拆分通过泛型保留拆分后的结果 export type LastCharT, F never T extends ${infer L}${infer R}? LastCharR, L // 递归拆分右侧内容L为上一次的左侧最后不能拆分则返回LL就位最后一个字符: F;// ---------------------------------type A LastCharBFE; // E type B LastChardev; // v type C LastChar; // neverStringToTuple 字符串转换为元组类型 export type StringToTupleT,F extends any[] []T extends ${infer L}${infer R} ? StringToTupleR, [...F, L] : F;type A StringToTupleBFE.dev; // [B, F, E, ., d, e,v] type B StringToTuple; // []TupleToString 将字符串类型的元素转换为字符串字面量类型 // 拆分左右两边类型将右边递归拆分通过泛型保留拆分后的结果. export type TupleToStringT, F extends string T extends [infer L,...infer R ]? TupleToStringR, ${F}${L string} // 递归数组右侧的部分每次拿到的左侧结果累加: F;// --------------------------------- type A TupleToString[a, b, c]; // abc type B TupleToString[a]; // a type C TupleToString[]; // RepeatString 复制字符 T 为字符串类型长度为 C export type RepeatStringT extends string, // 要循环的字符串C, // 循环的次数A extends any[] [], // 采用数组记录循环的次数F extends string // 最终结果// 如果满足长度返回最终结果不满足则累加数组长度并且拼接最终结果C extends A[length] ? F : RepeatStringT, C, [...A, T], ${F}${T}; // ---------------------------------type A RepeatStringa, 3; // aaa type B RepeatStringa, 0; // SplitString 将字符串字面量类型按照指定字符分割为元组。无法分割则返回原字符串字面量 export type SplitStringT, // 要拆分的内容S extends string, // 分隔符A extends any[] [] // 存放拆分后的结果T extends ${infer L}${S}${infer R}? SplitStringR, S, [...A, L] // 递归拆分右边并且将左边放到数组中: [...A, T]; // 不包含则直接将T 放到数组中// ---------------------------------type A1 SplitStringhandle-open-flag, -; // [handle, open, flag] type A2 SplitStringopen-flag, -; // [open, flag] type A3 SplitStringhandle.open.flag, .; // [handle, open, flag] type A4 SplitStringopen.flag, .; // [open, flag] type A5 SplitStringopen.flag, -; // [open.flag]LengthOfString 计算字符串字面量类型的长度 export type LengthOfStringT,A extends any[] [] // 用于计算字符串的长度T extends ${infer L}${infer R}? LengthOfStringR, [...A, L]: A[length];// ---------------------------------type A LengthOfStringBFE.dev; // 7 type B LengthOfString; // 0KebabCase 驼峰命名转横杠命名 type RemoveFirstT T extends -${infer R} ? R : T; // 删除首字母是-的export type KebabCaseT,F extends string T extends ${infer L}${infer R}? // 看当前字母是否是大写如果是 则转化成 -小写 H - -hKebabCaseR, ${F}${CapitalizeL extends L ? -${LowercaseL} : L}: RemoveFirstF;// ---------------------------------type a1 KebabCaseHandleOpenFlag; // handle-open-flag type a2 KebabCaseOpenFlag; // open-flagCamelCase 横杠命名转化为驼峰命名 type CamelCaseT extends string,S extends string T extends ${infer L}-${infer R1}${infer R2} // 匹配 xx-x xxX? CamelCaseR2, ${S}${L}${UppercaseR1} // 累加-左边: Capitalize${S}${T};// ---------------------------------type a1 CamelCasehandle-open-flag; // HandleOpenFlag type a2 CamelCaseopen-flag; // OpenFlagObjectAccessPaths 得到对象中的值访问字符串 type RemoveFirstT T extends .${infer L} ? L : T; export type ObjectAccessPathsT,F extends string ,K keyof TK extends keyof T // 产生一次分发操作? T[K] extends object // 不能T[K]联合类型会出现never? ObjectAccessPathsT[K], ${F}.${K string}: RemoveFirst${F}.${K string}: never;// ---------------------------------function createI18nSchema(schema: Schema ): (path: ObjectAccessPathsSchema) void {return (path) {}; }const i18n createI18n({home: {topBar: {title: 顶部标题,welcome: 欢迎登录,},bottomBar: {notes: XXX备案归XXX所有,},},login: {username: 用户名,password: 密码,}, });i18n(home.topBar.title); // correct i18n(home.topBar.welcome); // correct i18n(home.bottomBar.notes); // correct// i18n(home.login.abc) // error不存在的属性 // i18n(home.topBar) // error没有到最后一个属性Include 判断传入的字符串字面量类型中是否含有某个字符串 type IncludeT extends string, C extends string T extends ? C extends ? true: false: T extends ${infer L}${C}${infer R} // 可以实现 startsWith、endsWith? true: false;// ---------------------------------type a1 Includehs, J; // true type a2 Includehs, J; // true type a3 Include, ; // true 空字符串时需要特殊处理Trim type TrimLeftT extends string T extends ${infer R} ? TrimLeftR : T; // 去左空格 type TrimRightT extends string T extends ${infer L} ? TrimLeftL : T; // 去右空格 type TrimT extends string TrimRightTrimLeftT;// ---------------------------------type a1 Trim .hs ;Replace export type ReplaceT extends string,C extends string,RC extends string,F extends string C extends ? T extends ? RC // 两方都是空直接返回替换后的结果: ${RC}${T} // 如果被替换值为空则把替换的结果换到前面: T extends ${infer L}${C}${infer R}? ReplaceR, C, RC, ${F}${L}${RC}: F;// ---------------------------------type a1 Replaceha ha ha, ha, he; type a2 Replacehh, hh, hswen; type a3 Replacea, , hswen; type a4 Replace, , hswen;ComponentEmitsType 定义组件的监听事件类型 import { CamelCase } from ./10.CamelCase;// 实现 ComponentEmitsTypeEmits 类型将 type a1 {handle-open: (flag: boolean) true;preview-item: (data: { item: any; index: number }) true;close-item: (data: { item: any; index: number }) true; };type ComponentEmitsTypeT {[K in keyof T as on${CamelCaseK string}]?: T[K] extends (...args: infer R) any? (...args: R) void: T[K]; };type a2 ComponentEmitsTypea1; // 转化为类型 /* {onHandleOpen?: (flag: boolean) void,onPreviewItem?: (data: { item: any, index: number }) void,onCloseItem?: (data: { item: any, index: number }) void, } */基于数组 LengthOfTuple 计算元组类型的长度 export type LengthOfTupleT extends any[] T[length];// -----------------------type A LengthOfTuple[B, F, E]; // 3 type B LengthOfTuple[]; // 0FirstItem 得到元组类型中的第一个元素 export type FirstItemT extends any[] T[0];// -----------------------type A FirstItem[string, number, boolean]; // string type B FirstItem[B, F, E]; // BLastItem 得到元组类型中的最后一个元素 export type LastItemT extends any[] T extends [...infer L, infer R]? R: never;// -----------------------type A LastItem[string, number, boolean]; // boolean type B LastItem[B, F, E]; // E type C LastItem[]; // neverShift 移除元组类型中的第一个类型 export type ShiftT extends any[] T extends [infer L, ...infer R] ? R : [];// -----------------------type A Shift[1, 2, 3]; // [2,3] type B Shift[1]; // [] type C Shift[]; // []Push 在元组类型 T 中添加新的类型 I export type PushT extends any[], I [...T, I];// -----------------------type A Push[1, 2, 3], 4; // [1,2,3,4] type B Push[1], 2; // [1, 2]ReverseTuple 反转元组 export type ReverseTupleT extends any[], F extends any[] [] T extends [infer L,...infer R ]? ReverseTupleR, [L, ...F]: F;// -----------------------type A ReverseTuple[string, number, boolean]; // [boolean, number, string] type B ReverseTuple[1, 2, 3]; // [3,2,1] type C ReverseTuple[]; // []Flat 拍平元组 export type FlatT T extends [infer L, ...infer R]? [...(L extends any[] ? FlatL : [L]), ...FlatR]: T; // -----------------------type A Flat[1, 2, 3]; // [1,2,3] type B Flat[1, [2, 3], [4, [5, [6]]]]; // [1,2,3,4,5,6] type C Flat[]; // [] type D Flat[1]; // [1]Repeat 复制类型 T 为 C 个元素的元组类型 export type RepeatT, C, F extends any[] [] C extends F[length]? F: RepeatT, C, [...F, T];// -----------------------type A Repeatnumber, 3; // [number, number, number] type B Repeatstring, 2; // [string, string] type C Repeat1, 1; // [1] type D Repeat0, 0; // []Filter 保留元组类型 T 中的 A 类型 export type FilterT extends any[], A, F extends any[] [] T extends [infer L,...infer R ]? FilterR, A, [L] extends [A] ? [...F, L] : F: F;// -----------------------type A Filter[1, BFE, 2, true, dev], number; // [1, 2] type B Filter[1, BFE, 2, true, dev], string; // [BFE, dev] type C Filter[1, BFE, 2, any, dev], string; // [BFE, any, dev]FindIndex 找出 E 类型在元组类型 T 中的下标 export type IsEqualT, U, Success, Fail [T] extends [U]? [U] extends [T]? keyof T extends keyof U? keyof U extends keyof T // 解决结构比较问题? Success: Fail: Fail: Fail: Fail; // IsEqual1, any, true, false; any判断问题export type FindIndexT extends any[], A, F extends any[] [] T extends [infer L,...infer R ]? IsEqualL, A, F[length], FindIndexR, A, [...F, null]: never;// -----------------------type a1 [any, never, 1, 2, true]; type a2 FindIndexa1, 1; // 2 type a3 FindIndexa1, 3; // neverTupleToEnum 元组类型转换为枚举类型 import { FindIndex } from ./25.findIndex;type TupleToEnumT extends any[], C false {[K in T[number]]: C extends true ? FindIndexT, K : K; };// -----------------------// 默认情况下枚举对象中的值就是元素中某个类型的字面量类型 type a1 TupleToEnum[MacOS, Windows, Linux]; // - { readonly MacOS: MacOS, readonly Windows: Windows, readonly Linux: Linux }// 如果传递了第二个参数为true则枚举对象中值的类型就是元素类型中某个元素在元组中的index索引也就是数字字面量类型 type a2 TupleToEnum[MacOS, Windows, Linux], true; // - { readonly MacOS: 0, readonly Windows: 1, readonly Linux: 2 }Slice 截取元组中的部分元素 export type SliceT extends any[],S extends number,E extends number T[length],SA extends any[] [],EA extends any[] [],F extends any[] []T extends [infer L, ...infer R]? SA[length] extends S // 如果数组满足开头? EA[length] extends E? [...F, L] // 如果满足结尾则结束: SliceR, S, E, SA, [...EA, null], [...F, L] // 满足开头则放入数组: SliceR, S, E, [...SA, null], [...EA, null], F // 不满足开头则累加长度: F;// -----------------------type A1 Slice[any, never, 1, 2, true, boolean], 0, 2; // [any,never,1] 从第0个位置开始保留到第2个位置的元素类型 type A2 Slice[any, never, 1, 2, true, boolean], 1, 3; // [never,1,2] 从第1个位置开始保留到第3个位置的元素类型 type A3 Slice[any, never, 1, 2, true, boolean], 1, 2; // [never,1] 从第1个位置开始保留到第2个位置的元素类型 type A4 Slice[any, never, 1, 2, true, boolean], 2; // [1,2,true,boolean] 从第2个位置开始保留后面所有元素类型 type A5 Slice[any], 2; // [] 从第2个位置开始保留后面所有元素类型 type A6 Slice[], 0; // [] 从第0个位置开始保留后面所有元素类型Splice 删除并且替换部分元素 export type SpliceT extends any[],S extends number,E,I extends any[] [],SA extends any[] [],EA extends any[] [],F extends any[] []T extends [infer L, ...infer R]? SA[length] extends S // 如果数组满足开头? EA[length] extends E? [...F, ...I, ...T] // 如果满足结尾则,后面的都要 保留的 插入的 剩余的: SpliceR, S, E, I, SA, [...EA, null], F // 满足开头计算删除个数: SpliceR, S, E, I, [...SA, null], EA, [...F, L] // 不满足开头保留内容,并且累加开头长度: F;// -----------------------type A1 Splice[string, number, boolean, null, undefined, never], 0, 2; // [boolean,null,undefined,never] 从第0开始删除删除2个元素 type A2 Splice[string, number, boolean, null, undefined, never], 1, 3; // [string,undefined,never] 从第1开始删除删除3个元素 type A3 Splice[string, number, boolean, null, undefined, never],1,2,[1, 2, 3] ; // [string,1,2,3,null,undefined,never] 从第1开始删除删除2个元素替换为另外三个元素1,2,3 从第0个位置开始保留后面所有元素类型基于结构 OptionalKeys 获取对象类型中的可选属性的联合类型 // 拿出每一个key 来看下忽略掉后是否能赋予给原来的类型如果可以则说明此属性是可选属性 export type OptionalKeysT, K keyof T K extends keyof T? OmitT, K extends T? K: never: never;// -------------------------type a1 OptionalKeys{foo: number | undefined;bar?: string;flag: boolean; }; // bar type a2 OptionalKeys{ foo: number; bar?: string }; // bar type a3 OptionalKeys{ foo: number; flag: boolean }; // never type a4 OptionalKeys{ foo?: number; flag?: boolean }; // foo|flag type a5 OptionalKeys{}; // neverPickOptional 保留一个对象中的可选属性类型 export type PickOptionalT PickT, OptionalKeysT;// ------------------------- type a1 PickOptional{foo: number | undefined;bar?: string;flag: boolean; }; // {bar?:string|undefined} type a2 PickOptional{ foo: number; bar?: string }; // {bar?:string} type a3 PickOptional{ foo: number; flag: boolean }; // {} type a4 PickOptional{ foo?: number; flag?: boolean }; // {foo?:number,flag?:boolean} type a5 PickOptional{}; // {}RequiredKeys 获取对象类型中的必须属性的联合类型 export type RequiredKeysT Excludekeyof T, OptionalKeysT;// ------------------------------type a1 RequiredKeys{foo: number | undefined;bar?: string;flag: boolean; }; // foo|flag type a2 RequiredKeys{ foo: number; bar?: string }; // foo type a3 RequiredKeys{ foo: number; flag: boolean }; // foo|flag type a4 RequiredKeys{ foo?: number; flag?: boolean }; // never type a5 RequiredKeys{}; // neverPickRequired 保留一个对象中的必须属性 import { RequiredKeys } from ./3.requirredKeys; export type PickRequiredT PickT, RequiredKeysT;// ----------------------------type a1 PickRequired{foo: number | undefined;bar?: string;flag: boolean; }; // {foo:number|undefined,flag:boolean} type a2 PickRequired{ foo: number; bar?: string }; // {foo:number} type a3 PickRequired{ foo: number; flag: boolean }; // {foo:number,flag:boolean} type a4 PickRequired{ foo?: number; flag?: boolean }; // {} type a5 PickRequired{}; // {}IsNever 判断是否为 never 类型 export type IsNeverT [T] extends [never] ? true : false;// ----------------------type A IsNevernever; // true type B IsNeverstring; // false type C IsNeverundefined; // false type D IsNeverany; // falseIsEmptyType 判断是否为没有属性的对象类型{} export type IsEmptyTypeT [keyof T] extends [never]? unknown extends T? false: boolean extends T // 排除object的情况? true: false: false;type x1 keyof {}; // never type x2 keyof object; // never 不能把基础类型赋予给object type x4 keyof unknown; // never unknown类型只能赋予给unknown type x3 keyof Object; // toString | valueOf// ----------------------type A IsEmptyTypestring; // false type B IsEmptyType{ a: 3 }; // false type C IsEmptyType{}; // true type D IsEmptyTypeany; // false type E IsEmptyTypeobject; // false type F IsEmptyTypeObject; // false type G IsEmptyTypeunknown; // falseIsAny type IsAnyT 0 extends 1 T ? true : false;// 先过滤出 any 和 unknown来 // any 可以赋予给任何类型,unknown 不可以 export type IsAnyT unknown extends T? [T] extends [boolean]? true: false: false;// ----------------------type A IsAnystring; // false type B IsAnyany; // true type C IsAnyunknown; // false type D IsAnynever; // falseRedux Connect type transformT T extends (input: Promiseinfer U ) PromiseActioninfer S? (input: U) ActionS: T extends (aciton: Actioninfer U) Actioninfer S? (action: U) ActionS: never;type ConnectT {[K in keyof T as T[K] extends (...args: any[]) any ? K : never]: transformT[K]; }; type F ConnectModule;// ---------------------- interface Module {count: number;message: string;asyncMethodT, U(input: PromiseT): PromiseActionU;syncMethodT, U(action: ActionT): ActionU; }interface ActionT {payload?: T;type: string; }// 这个要求的结果 type Result {asyncMethodT, U(input: T): ActionU;syncMethodT, U(action: T): ActionU; };// 实现类型Connect要求 ConnectModule 的结果为上面的 Result // 只要函数类型的属性 // 如果函数是异步函数要求自动解析出来Promise中的类型action 的定义方式为了测试使用 class Module {count 1;message hello!;asyncMethod(input: Promisenumber) {return input.then((i) ({payload: i,type: asyncMethod,}));}syncMethod(action: Actionstring) {return {payload: action.payload,type: syncMethod,};} }UnionToIntersection 逆变参数可以传父亲 将联合类型转换为交叉类型 // 先映射成函数 得到函数的联合类型 // 在extends 推断参数即可 export type UnionToIntersectionT (T extends any ? (p: T) any : never ) extends (p: infer R) any? R: never;type FuncType | ((p: { a: string }) 人)| ((p: { b: boolean }) 狗)| ((p: { c: number }) 猪);type T1 { name: string }; type T2 { age: number }; type ToIntersectionT T extends [(x: infer U) any, (x: infer U) any]? U: never; type t3 ToIntersection[(x: T1) an y, (x: T2) any];// ----------------------// type A UnionToIntersection{ a: string } | { b: string } | { c: string }; // {a: string} {b: string} {c: string}UnionToTuple 联合类型转换为元组类型 type X ((p: string) { a: string }) ((p: number) { b: string }) ((p: boolean) { c: number });function a(a: string): { a: string }; function a(a: number): { b: string }; function a(a: boolean): { c: string }; function a(a: string | number | boolean): { a: string; b: string; c: string } {return { a: 123, b: 123, c: 123 }; }type ParamaterTypeT T extends (value: infer R) any ? R : never; type R ParamaterTypeX;// 先变成函数的联合类型 type FindUnionOneT IsAnyT extends true? any: boolean extends T? boolean: (T extends any ? (a: (p: T) any) any : never) extends (a: infer R) any? R extends (a: infer R1) any? R1: void: never;// 1先转换成交函数叉类型 // 2推断函数的参数,利用特性随机返回一个 // 3) 排除boolean类型 boolean 会发生分发type UnionToTupleU, Last FindUnionOneU [U] extends [never]? []: [...UnionToTupleExcludeU, Last, Last];type a1 UnionToTuple1 | 2 | boolean | string;// ----------------------type a UnionToTuple1 | 2 | 3; // [1,2,3]模板字符串以及装饰器 模板字符串类型 模板字符串类型就是将两个字符串类型值组装在一起返回。使用方式类似于 ES6 中的模板字符串。 基本使用 type name hswen; type sayHello hello, ${name}; // hello, hswen// 类型分发机制 1) type Direction left | right | top | bottom; type AllMargin marigin-${Direction}; // marigin-left | marigin-right | marigin-top | marigin-bottom// 类型分发机制 2) type IColor red | yellow | green; type ICount 100 | 200 | 300; type BookSKU ${IColor}-${ICount}; // red-100 | red-200 | red-300 | yellow-100 | yellow-200 | yellow-300 | green-100 | green-200 | green-300通过泛传入类型 type sayHelloT extends string | number | bigint | boolean | null | undefined hello, ${T}; // 泛型要求string | number | bigint | boolean| null | undefiendtype V1 sayHellohs; // Hello, hs type V2 sayHello30; // Hello, 30 type V3 sayHello123n; // Hello, 123 type V4 sayHellotrue; // Hello, true type V5 sayHellonull; // Hello, null type V6 sayHelloundefined; // Hello, undefined type v7 sayHellostring; // hello, ${string} type v8 sayHellonumber; // hello, ${number}// 传入类型不会被解析为所有hello, 开头的父类型 type isChild V1 extends v7 ? true : false;映射类型中使用模板字符串 对key进行重命名 type Person { name: string; age: number; address: string }; type RenamePersonT {[K in keyof T as re_${K string}]: T[K]; // K string 保证K为string类型 }; let person: RenamePersonPerson {re_name: hs,re_age: 30,re_address: 回龙观, };专用工具类型 Uppercase、Lowercase、Capitalize 、Uncapitalize type Person { name: string; age: number; address: string }; type PersonWithGetterT {[K in keyof T as get${Capitalizestring K}]?: () T[K]; }; let person: Person { name: hs, age: 39, address: 回龙观 }; let personGetter: PersonWithGetterPerson {getName() {return person.name;}, };模式匹配 type GetFristNameS extends string S extends ${infer F} ${infer O} ? F : S; type x GetFristNamehs wen; // hs装饰器 装饰器本质就是一个函数只能在类以及类成员上使用。TypeScript 中的装饰器可以分为类装饰器、方法装饰器、访问符装饰器、属性装饰器以及参数装饰器 类装饰器 类装饰器是直接作用在类上的装饰器它在执行时的入参只有一个即是这个类本身。如果装饰器函数中返回一个新的类那么即是这个类的子类这个子类可以用于重写父类。 const Decorator T extends { new (...args: any[]): {} }(target: T) {(target as any).type 动物;(target as any).getType function () {return this.type;};Object.assign(target.prototype, {eat() {console.log(eat);},drink() {console.log(drink);},}); }; interface Animal {eat(): void;drink(): void; } Decorator class Animal {} const animal new Animal();// 原型方法 animal.eat(); animal.drink(); // 静态方法 console.log((Animal as any).getType());通过返回子类的方式进行扩展 const OverrideAnimal (target: any) {return class extends target {// 通过返回子类的方式对父类进行装饰。 最终会用子类替代targeteat() {super.eat();console.log(Override eat);}drink() {console.log(Overrided drink);}}; };OverrideAnimal class Animal {eat() {console.log(eat);} } const animal new Animal(); animal.eat(); (animal as any).drink();方法装饰器 方法装饰器的入参包括类的原型、方法名以及方法的属性描述符PropertyDescriptor。 function Enum(isEnum: boolean) {// 类的原型、方法名、方法属性描述符return function (target: any, key: string, descriptor: PropertyDescriptor) {// descriptor.enumerable 是否可枚举// descriptor.writable 是否可写// descriptor.configurable 是否能被删除// descriptor.value 原来的值descriptor.enumerable isEnum; // 更改属性描述符let originalEat descriptor.value;descriptor.value function (...args: any[]) {console.log(prev-eat);originalEat.call(this, ...args);console.log(next-eat);};}; }class Animal {Enum(true)eat() {console.log(eat);} } const animal new Animal(); animal.eat();访问符装饰器 访问符装饰器本质上仍然是方法装饰器它们使用的类型定义相同。访问符装饰器只能应用在 getter / setter 的其中一个装饰器入参中的属性描述符都会包括 getter 与 setter 方法。 function ValueToUpper(target: any,key: string,descriptor: PropertyDescriptor ) {const original descriptor.set;descriptor.set function (newValue: string) {original?.call(this, newValue.toUpperCase());}; }class Animal {private _value!: string;ValueToUpper //将设置的值转换成大写get value() {return this._value;}set value(newValue: string) {this._value newValue;} } const animal new Animal(); animal.value ok; console.log(animal.value);属性装饰器 属性装饰器在独立使用时能力非常有限可以在类的原型上赋值来修改属性。 function ToUpper(target: any, key: string) {let val ;// target: ES2015 可以进行劫持 ESNext访问时无法劫持Object.defineProperty(target, key, {// 给原型上添加了个属性enumerable: true,get() {return val.toUpperCase();},set(newValue) {val newValue;},}); }class Animal {ToUpperpublic name: string Animal; // 触发原型属性上的set方法 } const animal new Animal(); console.log(animal);参数装饰器 参数装饰器包括了构造函数的参数装饰器与方法的参数装饰器它的入参包括类的原型、参数所在的方法名与参数在函数参数中的索引值独立使用能力依旧有限。 function Params(target: any, key: string, index: number) {// 类的原型、 参数名、参数索引console.log(target, key, index); } class Animal {public name: string Animal; // 触发原型属性上的set方法play(Params val: string) {console.log(val);} }装饰器执行流程 function Echo(val: string): any {return () {console.log(val);}; } Echo(类装饰器1) // 类装饰器是兜底执行 Echo(类装饰器2) // 类装饰器是兜底执行 Echo(类装饰器3) // 类装饰器是兜底执行 Echo(类装饰器4) // 类装饰器是兜底执行 class Flow {constructor(Echo(构造函数参数装饰器) str: string) {}Echo(静态方法装饰器)static getType(Echo(静态方法参数装饰器) str: string) {return this.type;}Echo(静态属性装饰器)static type hello;Echo(实例方法装饰器)handler(Echo(实例方法参数装饰器) str: string) {}Echo(实例属性装饰器)name!: string;Echo(属性访问装饰器)get value() {return hello;} }// [实例属性、方法优先执行参数装饰器、属性访问]、[静态属性、静态方法]、构造函数参数装饰器、类装饰器 (同时使用多个装饰器的执行流程“洋葱模型”)// 实例方法参数装饰器 // 实例方法装饰器 // 实例属性装饰器 // 属性访问装饰器 // 静态方法参数装饰器 // 静态方法装饰器 // 静态属性装饰器 // 构造函数参数装饰器 // 类装饰器4 // 类装饰器3 // 类装饰器2 // 类装饰器1方法装饰器我们通常进行方法执行前后的逻辑注入。属性、参数装饰器我们通常只进行信息注册委托别人处理。 反射元数据 Reflect Metadata 元数据用于描述数据的数据将信息存到 map 表中 最终统一操作。 反射的核心是在程序运行时去检查以及修改程序行为允许程序在运行时获取自身的信息。 元数据命令式定义 import reflect-metadata; class Animal {static type 哺乳类;eat() {} } Reflect.defineMetadata(Class, Animal metadata, Animal); Reflect.defineMetadata(Class property, type metadata, Animal, type); Reflect.defineMetadata(proto method, eat metadata, Animal.prototype, eat); /* WeakMap {Animal:{undefined:{Class Animal metadata},type:{Class property type metadata}},Animal.prototype:{eat:{proto method eat metadata},}} */ // 取data console.log(Reflect.getMetadata(Class, Animal)); console.log(Reflect.getMetadata(Class property, Animal, type)); console.log(Reflect.getMetadata(proto method, Animal.prototype, eat));// 取key console.log(Reflect.getMetadataKeys(Animal)); console.log(Reflect.getMetadataKeys(Animal, type)); console.log(Reflect.getMetadataKeys(Animal.prototype, eat));元数据声明式定义 Reflect.metadata(Class, Animal metadata) class Animal {Reflect.metadata(Class property, type metadata)static type 哺乳类;Reflect.metadata(proto method, eat metadata)eat() {} }// 在类装饰器中进行数据的消费 console.log(Reflect.getMetadata(Class, Animal)); console.log(Reflect.getMetadata(Class property, Animal, type)); console.log(Reflect.getMetadata(proto method, Animal.prototype, eat));生成额外的metadata 开启emitDecoratorMetadata: true后自动生成基于类型的元数据。 // 通过原型 console.log(Reflect.getMetadata(design:type, Animal.prototype, eat)); console.log(Reflect.getMetadata(design:paramtypes, Animal.prototype, eat)); console.log(Reflect.getMetadata(design:returntype, Animal.prototype, eat));// 通过实例 console.log(Reflect.getMetadata(design:type, new Animal(), eat)); console.log(Reflect.getMetadata(design:paramtypes, new Animal(), eat)); console.log(Reflect.getMetadata(design:returntype, new Animal(), eat));Required必填属性实战 import reflect-metadata;const REQUIRED_KEY Symbol(required_key); function Required(): PropertyDecorator {return (target, prop) {const requiredkeys: string[] Reflect.getMetadata(REQUIRED_KEY, target) || [];// 设置元数据Reflect.defineMetadata(REQUIRED_KEY, [...requiredkeys, prop], target);}; } class Person {Required()name!: string;Required()age!: number; } function validate(instance: any) {let exisitsKeys Reflect.ownKeys(instance); // 获取已经存在的属性let requiredKeys Reflect.getMetadata(REQUIRED_KEY, instance) || [];for (const key of requiredKeys) {if (!exisitsKeys.includes(key)) {throw new Error(key is required);}} }// 1先记录哪些属性为必填属性 // 2) 在查询实例上哪个属性没有 const person new Person(); person.name hh; person.age 30;validate(person); // 校验属性TypeValidation类型校验 const VALIDATION_KEY Symbol(VALIDATION_KEY); enum Type {String string,Number number, } function ValueType(type: Type) {return (target: any, prop: string) {// 给某个属性添加元数据Reflect.defineMetadata(VALIDATION_KEY, type, target, prop);}; } class Person {ValueType(Type.Number) // 值的类型应为numberRequired()age!: number; } const instance new Person(); // ts-ignore instance.age 18;function validate(instance: any) {let exisitsKeys Reflect.ownKeys(instance); // 获取已经存在的属性let requiredKeys Reflect.getMetadata(REQUIRED_KEY, instance) || [];for (let key of exisitsKeys) {let validations Reflect.getMetadata(VALIDATION_KEY, instance, key);if (validations) {// 看存在的类型是否满足if (typeof instance[key] ! validations) {throw new Error(${String(key)} expect ${validations});}}}// 校验必填属性看实例上是否存在需要的必填属性for (const key of requiredKeys) {if (!exisitsKeys.includes(key)) {throw new Error(key is required);}} } validate(instance);控制反转 控制正转我们去超市购物结账时我们需要一个个自己扫描商品条形码填写数量进行付款。整个过程由我自己控制控制反转我门去超市购物把车推到收款区收银员去识别条形码最后我来付款。 控制权就被反转了。 失去了控制权 IoCInversion of Control即控制反转在开发中是一种设计思想。传统编程中我们自己在对象内部创建依赖对象即正向控制。而在 IoC 中我们将对象的创建交给容器来控制对象被动接受依赖从而反转了控制关系。(解决问题类之间的耦合度高难以测试和重用依赖关系的问题) interface Monitor {} interface Host {} class Monitor27inch implements Monitor {} class AppleHost implements Host {} class Computer {public monitor: Monitor;public host: Host;constructor() {this.monitor new Monitor27inch();this.host new AppleHost();}bootstrap() {console.log(启动电脑);} } const computer new Computer(); computer.bootstrap(); // 组装电脑时想使用不同的零件如何实现根据需要手动创建并且传入零件 手工维护依赖关系 interface Monitor {} interface Host {} class Monitor27inch implements Monitor {} class AppleHost implements Host {} class Computer {constructor(public monitor: Monitor, public host: Host) {}bootstrap() {console.log(启动电脑);} } const monitor27 new Monitor27inch(); const appleHost new AppleHost(); const computer new Computer(monitor27, appleHost); computer.bootstrap();模拟容器 interface Monitor {} interface Host {}class Monitor27inch implements Monitor {} class AppleHost implements Host {} class Computer {constructor(public monitor: Monitor, public host: Host) {}bootstrap() {console.log(启动电脑);} } class Container {private instances new Map();bindT(key: string, creator: () T) {if (!this.instances.has(key)) {this.instances.set(key, creator());}return this.instances.get(key) as T;}resolveT(key: string): T {return this.instances.get(key) as T;} } const container new Container(); container.bindMonitor(Monitor, () new Monitor27inch()); container.bindHost(Host, () new AppleHost()); const computer container.bindComputer(Computer,() new Computer(container.resolve(Monitor), container.resolve(Host)) ); computer.bootstrap();依赖注入 DI 是 IoC 的具体体现它是一种模式它通过容器动态地将某个组件所需的依赖注入到组件中而无需硬编码在组件内部。 如果代码是这个样子的那就非常完美了 Provide(Monitor) class Monitor27inch {} Provide(Host) class AppleHost {}Provide(Computer) class Computer {Inject(Monitor)monitor!: Monitor27inch;Inject(Host)host!: AppleHost;bootstrap() {console.log(启动电脑);} }这种模式让我们可以专注于组件自身的逻辑而不需要关心具体的依赖资源如何创建和提供。容器负责在运行时解决依赖关系从而使代码更具可维护性和灵活性。 class Container {private instances new Map(); // 存储类 和 类的创造器public properties new Map(); // 存储属性bindT(key: string, creator: () T) {if (!this.instances.has(key)) {this.instances.set(key, creator());}return this.instances.get(key) as T;}resolveT(key: string): T {let instance this.instances.get(key);for (let property of this.properties) {// 循环所有的属性let [key, ServiceKey] property;let [classKey, propKey] key.split(-); // 类的名字和属性名if (instance.constructor.name ! classKey) {// 如果不是当前类的continue;}const target this.resolve(ServiceKey); // 解析依赖instance[propKey] target;}return instance as T;} } const container new Container();Provide(Monitor) class Monitor27inch {} Provide(Host) class AppleHost {}Provide(Computer) class Computer {Inject(Monitor)monitor!: Monitor27inch;Inject(Host)host!: AppleHost;bootstrap() {console.log(启动电脑);} } // 注册到容器中 function Provide(key: string) {return function (Target: any) {// 保存类的名字和类的创建器container.bind(key ?? Target.name, () new Target());}; } // 注入到当前类中 function Inject(InjectKey: string) {return function (target: any, key: string) {// 保存注入的属性信息container.properties.set(${target.constructor.name}-${key}, InjectKey);}; } const computer container.resolveComputer(Computer); computer.bootstrap();依赖注入实战 import reflect-metadata; function methodDecorator(method: string) {return function (path: string) {return function (target: any, key: string, descriptor: PropertyDescriptor) {Reflect.defineMetadata(method, method, descriptor.value);Reflect.defineMetadata(path, path, descriptor.value);};}; } export const Controller (path?: string) {return function (target: any) {Reflect.defineMetadata(path, path ?? , target);}; }; export const Get methodDecorator(get); export const Post methodDecorator(post); Controller(/article) class ArticleController {Get(/detail)getDetail() {return get detail;}Post(/add)addArticle() {return post add;} } function createRoutes(instance: any) {const prototype Reflect.getPrototypeOf(instance)!;const rootPath Reflect.getMetadata(path, prototype.constructor);const methods Reflect.ownKeys(prototype).filter((item) item ! constructor);const routes methods.map((method) {const requestHandler (prototype as any)[method];const requestPath Reflect.getMetadata(path, requestHandler); // 获得路径const requestMethod Reflect.getMetadata(method, requestHandler);return {requestPath: ${rootPath}${requestPath},requestHandler,requestMethod,};});return routes; } const routes createRoutes(new ArticleController()); console.log(routes);axios 核心实现 axios import axios, { AxiosRequestConfig, AxiosResponse } from axios;const baseURL http://localhost:8080;// 1.定义”传递数据“和”返回数据“的接口 interface Person {name: string;age: number; } let person: Person { name: hswen, age: 30 };// 2.配置请求参数 let requestConfig: AxiosRequestConfig {method: get,url: baseURL /get,params: person, };// 3.发送请求并且限制接口返回值类型 axios(requestConfig).then((response: AxiosResponse) {return response.data;}).catch((error: any) {console.log(error);});创建 axios 基本结构 axios/index.ts class Axios {request() {} } function createInstance() {// 1.创建axios实例const context new Axios();// 2.获取request方法并且绑定thisconst instance Axios.prototype.request.bind(context);return instance; }// 我们真实调用的就是axios.request方法 const axios createInstance(); export default axios;为了编写代码方便我们将 Axios 类单独拿出去定义 axios/Axios.ts class Axios {request() {} } export default Axios;创建请求及响应类型 axios/types.ts AxiosRequestConfig export type Methods | get| GET| post| POST| put| PUT| delete| DELETE| options| OPTIONS;export interface AxiosRequestConfig {url?: string;method?: Methods;params?: any; }AxiosResponse export interface AxiosResponseT any {data: T;status: number;statusText: string;headers: Recordstring, any;config: AxiosRequestConfig;request?: XMLHttpRequest; }在入口文件中导出所有类型export * from ./types; 编写请求方法 编写 request export interface AxiosInstance {T any(config: AxiosRequestConfig): PromiseAxiosResponseT; }用于描述 request 方法 const instance: AxiosInstance Axios.prototype.request.bind(context);编写请求逻辑 import { AxiosRequestConfig, AxiosResponse } from ./types; import qs from qs; import parseHeader from parse-headers; class Axios {requestT(config: AxiosRequestConfig): PromiseAxiosResponseT {// 在请求前还要实现拦截器的功能所以先专门提供一个用于请求的方法。// todo...return this.dipsatchRequest(config);}dipsatchRequestT(config: AxiosRequestConfig): PromiseAxiosResponseT {return new Promise(function (resolve, reject) {let { method, url, params } config;const request new XMLHttpRequest();// get请求参数if (params) {if (typeof params object) {params qs.stringify(params);}url (url!.indexOf(?) -1 ? : ?) params;}request.open(method!, url!, true);request.responseType json;request.onreadystatechange function () {if (request.readyState 4 request.status ! 0) {if (request.status 200 request.status 300) {let response: AxiosResponseT {data: request.response ? request.response : request.responseText,status: request.status,statusText: request.statusText,headers: parseHeader(request.getAllResponseHeaders()),config,request,};resolve(response);} else {reject(请求失败~~~);}}};request.send();});} } export default Axios;处理 Post 请求 请求参数 let requestConfig: AxiosRequestConfig {method: post,url: baseURL /post,data: person,headers: {content-type: application/json,}, };修改配置接口 export interface AxiosRequestConfig {url?: string;method?: Methods;params?: any;headers?: Recordstring, any;data?: Recordstring, any; }修改发送逻辑 if (headers) {for (let key in headers) {request.setRequestHeader(key, headers[key]);} } let body: string | null null; if (data) {body JSON.stringify(data); } request.send(body);错误处理 网络异常错误 request.onerror function () {reject(net::ERR_INTERNET_DISCONNECTED); };可以通过onerror监控网络产生的异常。 超时处理 export interface AxiosRequestConfig {// ...timeout?: number; // 增加超时时间 }请求参数 let requestConfig: AxiosRequestConfig {method: post,url: baseURL /post_timeout?timeout3000, // 3s后返回结果data: person,headers: {content-type: application/json,},timeout: 1000, // 1s后就超时 };设置超时时间 if (timeout) {request.timeout timeout;request.ontimeout function () {reject(Error: timeout of ${timeout}ms exceeded);}; }状态码错误 请求参数 let requestConfig: AxiosRequestConfig {method: post,url: baseURL /post_status?code401, // 3s后返回结果data: person,headers: {content-type: application/json,}, };设置错误信息 request.onreadystatechange function () {if (request.readyState 4 request.status ! 0) {if (request.status 200 request.status 300) {// ...} else {reject(Error: Request faild with status code ${request.status});}} };拦截器 let requestConfig: AxiosRequestConfig {method: post,url: baseURL /post,data: person,headers: {content-type: application/json,name: , // 用于记录拦截器的执行顺序}, };拦截器执行顺序 // 请求拦截器是倒序执行的先放入的拦截器最后执行 let request axios.interceptors.request.use((config) {config.headers.name a;return config;},(err) Promise.reject(err) ); axios.interceptors.request.use((config) {config.headers.name b;return config; }); axios.interceptors.request.use((config) {config.headers.name c;return config; }); axios.interceptors.request.eject(request); // 放入的可以抛出来// 响应拦截器是正序执行的先放入的拦截器先执行 let response axios.interceptors.response.use((response) {response.data.name a;return response; }); axios.interceptors.response.use((response) {response.data.name b;return response; }); axios.interceptors.response.use((response) {response.data.name c;return response; }); axios.interceptors.response.eject(response);拦截器 promise 写法 axios.interceptors.request.use((config) {return new Promise((resolve) {setTimeout(() {config.headers!.name c;resolve(config);}, 3000);});return Promise.reject(失败了); });拦截器类型定义 // 强制将headers属性进行重写变为非可选 export interface InternalAxiosRequestConfig extends AxiosRequestConfig {headers: Recordstring, any; } export interface AxiosInstance {T any(config: AxiosRequestConfig): PromiseAxiosResponseT;interceptors: {request: AxiosInterceptorManagerInternalAxiosRequestConfig;response: AxiosInterceptorManagerAxiosResponse;}; }AxiosInterceptorManager 实现 type OnFulfilledV (value: V) V | PromiseV; type OnRejected (error: any) any;export interface InterceptorV {onFulfilled?: OnFulfilledV;onRejected?: OnRejected; } class AxiosInterceptorManagerV {public interceptors: ArrayInterceptorV | null [];use(onFulfilled?: OnFulfilledV, onRejected?: OnRejected): number {this.interceptors.push({onFulfilled,onRejected,});return this.interceptors.length - 1;}eject(id: number) {if (this.interceptors[id]) {this.interceptors[id] null;}} } export default AxiosInterceptorManager;拦截器执行原理 缺少属性 “interceptors”但类型 “AxiosInstance” 中需要该属性 const instance: AxiosInstance Axios.prototype.request.bind(context);class Axios {public interceptors {request: new AxiosInterceptorManagerInternalAxiosRequestConfig(),response: new AxiosInterceptorManagerAxiosResponse(),}; }function createInstance() {const context new Axios();let instance Axios.prototype.request.bind(context);// 3.将实例属性合并到request中instance Object.assign(instance, context);return instance as AxiosInstance; }构建执行链 // 存放执行链路 const chain: (| InterceptorAxiosResponse| InterceptorInternalAxiosRequestConfig )[] [{ onFulfilled: this.dipsatchRequest }];this.interceptors.request.interceptors.forEach((interceptor) {interceptor chain.unshift(interceptor); });this.interceptors.response.interceptors.forEach((interceptor) {interceptor chain.push(interceptor); });let promise: PromiseAxiosRequestConfig | AxiosResponse Promise.resolve(config);while (chain.length) {const { onFulfilled, onRejected } chain.shift()!; // 从头部删除元素promise promise.then(onFulfilled as (v: AxiosRequestConfig | AxiosResponse) any,onRejected); } // todo... return promise as PromiseAxiosResponseT;合并配置 创建默认值对象 // 默认配置 const defaults: InternalAxiosRequestConfig {method: get,timeout: 0,headers: {common: {accept: application/json,},}, };// 允许用户给defaults对象添加不同方法的默认值 let allMethods [delete, get, head, patch, post, put]; allMethods.forEach((method: string) {defaults.headers[method] {}; });设置请求 headers if (headers) {for (let key in headers) {// 如果是common或是方法 就将对象合并if (key common || key config.method) {for (let key2 in headers[key]) {request.setRequestHeader(key2, headers[key][key2]);}} else {if (!allMethods.includes(key)) {request.setRequestHeader(key, headers[key]);}}} }请求与响应转换 类型声明 export interface AxiosRequestConfig {url?: string;method?: Methods;params?: any;headers?: Recordstring, any;data?: Recordstring, any;timeout?: number; // 增加超时时间// 转化请求及响应类型定义transformRequest?: (data: Recordstring, any,headers: Recordstring, any) any;transformResponse?: (data: any) any; }方法实现 // 默认配置 const defaults: InternalAxiosRequestConfig {method: get,headers: {common: {accept: application/json,},},// 请求前执行此方法transformRequest: (data: Recordstring, any,headers: Recordstring, any) {headers[content-type] application/x-www-form-urlencoded;return qs.stringify(data);},// 获取后执行此方法transformResponse(data: any) {if (typeof data string) data JSON.parse(data);return data;}, };requestT(config: AxiosRequestConfig): PromiseAxiosResponseT {// 在请求前还要实现拦截器的功能所以先专门提供一个用于请求的方法。config.headers Object.assign(this.defaults.headers, config.headers);// 合并请求、响应转化方法config.transformRequest config.transformRequest || this.defaults.transformRequest;config.transformResponse config.transformResponse || this.defaults.transformResponse;if (config.transformRequest config.data) {config.data config.transformRequest(config.data, (config.headers {}));} }request.onreadystatechange () {if (request.readyState 4 request.status ! 0) {if (request.status 200 request.status 300) {// ...// 转化响应方法if (config.transformResponse) {response.data config.transformResponse(response.data);}resolve(response);}} };请求终止 cancelToken 的使用 const CancelToken axios.CancelToken; const source CancelToken.source(); // 创建取消tokenlet requestConfig: AxiosRequestConfig {method: post,url: baseURL /post,data: person,cancelToken: source.token, // 请求时携带token }; axios(requestConfig).then((response: AxiosResponsePerson) {console.log(response.data);return response.data;}).catch((error: any) {if (axios.isCancel(error)) {return console.log(取消: error);}console.log(error);});source.cancel(用户取消请求);取消实现原理 export class Cancel {constructor(public message: string) {} } export function isCancel(value: any): value is Cancel {return value instanceof Cancel; }// 取消的实现 export class CancelTokenStatic {public resolve!: (val: Cancel) void;source() {return {// token就是一个promisetoken: new PromiseCancel((resolve) {this.resolve resolve;}),// 让这个promise成功并且传入中断的原因cancel: (reason: string) {this.resolve(new Cancel(reason));},};} }声明所需类型 axios/types.ts export interface AxiosInstance {T any(config: AxiosRequestConfig): PromiseAxiosResponseT;interceptors: {request: AxiosInterceptorManagerInternalAxiosRequestConfig;response: AxiosInterceptorManagerAxiosResponse;};CancelToken: CancelTokenStatic; // 取消tokenisCancel: typeof isCancel; // 请求是否是被取消 }export type CancelToken ReturnTypeCancelTokenStatic[source][token]; export interface AxiosRequestConfig {// ...cancelToken?: CancelToken; }axios/Axios.ts if (config.cancelToken) {config.cancelToken.then((reason: Cancel) {request.abort();reject(reason);}); } let body: string | null null; if (data) {body JSON.stringify(data); } request.send(body); //.....装包和拆包 装包 将每个属性都被包装成了一个代理对象用于访问和设置原始对象的属性值。 let props {name: jiangwen,age: 30, }; type ProxyT {get(): T;set(value: T): void; }; type ProxifyT {[P in keyof T]: ProxyT[P]; }; function proxifyT(obj: T): ProxifyT {let result {} as ProxifyT;for (let key in obj) {let value obj[key];result[key] {get() {return value;},set: (newValue) (value newValue),};}return result; } let proxpProps proxify(props);拆包 function unProxifyT(proxpProps: ProxifyT): T {let result {} as T;for (let key in proxpProps) {let value proxpProps[key];result[key] value.get();}return result; } let proxy unProxify(proxpProps);axios 请求方法封装 import axios, { AxiosRequestConfig, AxiosInstance, AxiosResponse } from axios; // 用axios 进行二次封装在使用 目的就是添加一些默认的配置和拦截器// 一般后端返回的类型都是固定的 export interface ResponseDataT any {code: number;data?: T;msg?: string; } class HttpRequest {public baseURL http://localhost:3000/api;public timeout 3000;public request(options: AxiosRequestConfig) {// 能自动推导就不要自己写const instance axios.create();options this.mergeOptions(options); // 合并后的选项this.setInterceptors(instance);return instance(options); // 可以发请求了}public setInterceptors(instance: AxiosInstance) {instance.interceptors.request.use((config) {config.headers![token] xxx;return config;},(err) {return Promise.reject(err);});instance.interceptors.response.use((res: AxiosResponseResponseData) {// res.data.datalet { code } res.data;if (code ! 0) {return Promise.reject(res);}return res;},(err) {return Promise.reject(err);});}mergeOptions(options: AxiosRequestConfig) {return Object.assign({ baseURL: this.baseURL, timeout: this.timeout },options);}public getT any(url: string, data: any): PromiseResponseDataT {return this.request({method: get,url,params: data,}).then((res) {return Promise.resolve(res.data);}).catch((err) {return Promise.reject(err);});}public postT any(url: string, data: any): PromiseResponseDataT {return this.request({method: post,url,data,}).then((res) {return Promise.resolve(res.data);}).catch((err) {return Promise.reject(err);});} }const http new HttpRequest(); http.post{ token: number; username: string }(/login, {username: 123,password: 123,}).then((res) {res.data?.username;}).catch((err) {err;});TSConfig 详解 1.Language and Environment 语言和环境 语言和环境target指定最终生成的代码语言版本更改 target 时会引入对应的 lib。例如指定为 es5 时我们使用includes语法会发生异常提示找不到对应的 lib。当更改为 es6 时会自动引入对应的lib.2015.core.d.tslib手动配置需要引入的类库,例如配置 DOM可以在页面中使用浏览器属性。同时还需手动指定 target 所配置的类库 。jsx常见的属性有react(编译后生成React.createElement方法)、react-jsx(编译后生成自动导入语法)、preserve(不进行转化常用于 vue 中的 tsx)experimentalDecorators启用装饰器实验性语法emitDecoratorMetadata启用 metadata 生成元数据相关逻辑jsxFactory生成 react 对应的React.createElement或者 preact 中的 h 方法。需要在jsx: react时使用。jsxFragmentFactory生成 react 对应的React.Fragment或者 preact 中的 Fragment。需要在jsx: react时使用。文档碎片jsxImportSource配置 jsx 对应导入模块的路径需要在jsx: react-jsx时使用。reactNamespace生成createElement调用的命名空间默认是ReactnoLib禁用默认导入的所有 libuseDefineForClassFields使用 defineProperty 来定义类中的属性moduleDetection模块发现设置为 force 时所有内容均被当做模块。其它两种模式只会将带有import、export的识别为模块。 2.Modules 模块相关 1.module 控制最终 JavaScript 产物使用的模块标准 CommonJs、ES6、ESNext以及 NodeNext AMD、UMD、System等 2.rootDir 项目文件的根目录默认推断为包含所有 ts 文件的文件夹。配合outDir可以看最终的输出结果。 如果指定后只会根据指定的路径进行编译输出。 3.moduleResolution 配置模块解析方式 node、Classic、bundler 在Classic下的模块 import a from a; 导入时会查找 ./a.ts(递归往上找同名文件)。不推荐使用node不支持exportsnode16 / nodenext强制使用相对路径模块时必须写扩展名bundler既能使用 exports 声明类型的同时也可以使用相对路径模块不写扩展名。 4.baseUrl 定义文件进行解析的根目录它通常会是一个相对路径然后配合 tsconfig.json 所在的路径来确定根目录的位置。 // baseUrl:./ import a from src/a; // 默认以tsconfig所在的路径进行解析5.paths 类似于 alias支持通过别名的方式进行导入。 paths: {/shared/*: [./src/shared/*] } import a from /shared/isString;6.rootDirs 实现虚拟目录告诉 TS 将这些模块视为同一层级下但不会影响最终输出结果。可用于映射声明文件。 rootDirs:[src/style,src/typings] var.module.scss :export {color: red;border: 2px; }var.module.scss.d.ts interface IScss {color: string;border: string; } const IScss: IScss; export default IScss;7.typeRoots 默认情况下TypeScript 会在 node_modules/types 下查找类型定义文件可以通过设置 typeRoots 选项指定类型查找的目录。 {typeRoots: [./node_modules/types, ./typings]types: [jquery // 仅添加哪些声明文件] }, include: [src/**/*] // 指定查找目录8.allowUmdGlobalAccess 允许 umd 模块全局访问 export as namespace _; 关闭后需要导入模块后才能访问。 types/lodash/index.d.ts declare const _ _; declare namespace _ {export type flatten () void; } export as namespace _; // 将这个命名空间变成全局的不需要导入即可使用 export _; // 为了用户可以导入console.log(_); // 可以直接访问如果文件不在types 目录下需要配置include包含此文件。 9.moduleSuffixes 模块增添后缀进行查找[.controller, .service] 10.allowImportingTsExtensions 默认不允许开启后在相对导入时就允许使用扩展名.ts、.mts、.tsx注意要同时启用 --noEmit 或者 --emitDeclarationOnly因为这些文件导入路径还需要被构建工具进行处理后才能正常使用。 import a from ./a.mts;11.resolvePackageJsonExports 强制 TypeScript 在从 node_modules 中的包中读取时查询 package.json 文件的 exports 字段。 在moduleResolution这个值为node16, nodenext, 和 bundler时默认开启。 {name:my-package,exports:{.:{types:./index.d.ts, // 声明文件import:./index.mjs, // import导入的方式require: ./index.js // requie导入的方式}} }12.resolvePackageJsonImports 强制 TypeScript 在从其祖先目录包含 package.json 的文件执行以 # 开头的查找时查询 package.json 文件的 imports 字段。 imports: {#dep/*.js: ./src/utils/*.js }13.customConditions 获取当 TypeScript 从 package.json 的导出或导入字段解析时要考虑的附加条件列表。 14.resolveJsonModule 启用了这一配置后你就可以直接导入 Json 文件并对导入内容获得完整的基于实际 Json 内容的类型推导。 15.allowArbitraryExtensions 是否以{file basename}.d.{extension} 的形式查找该路径的声明文件。 文件是app.rc则声明文件是app.d.rc.ts declare const style: {color: string;background: string; }; export default style;16.noResolve 不解析文件导入和三斜线指令。 模块相关module指定编译后采用的模块方式rootDir项目文件的根目录默认推断为包含所有 ts 文件的文件夹。配合outDir可以看最终的输出结果。moduleResolution按照 node 方式进行模块解析。baseUrl配置项目解析的根目录配置后可以直接通过根路径的方式导入模块。paths路径别名配置/utils/*: [src/utils/*]。可以使用相对路径也可以配置baseUrl指定相对路径rootDirs实现虚拟目录告诉 TS 将这些模块视为同一层级下但不会影响最终输出结果。可用于映射声明文件。 rootDirs:[src/a,src/b]typeRoots指定类型查找的目录node_modules/types、./typingstypes手动指定 node_modules/types 下需要加载的类型。allowUmdGlobalAccess允许 umd 模块全局访问 export as namespace _;moduleSuffixes模块增添后缀进行查找[.module, .service]allowImportingTsExtensions在相对导入时就允许使用 ts 的扩展名注意要同时启用 --noEmit 或者 --emitDeclarationOnly因为这些文件导入路径还需要被构建工具进行处理后才能正常使用。resolvePackageJsonExports强制 TypeScript 在从 node_modules 中的包中读取时查询 package.json 文件的 exports 字段resolvePackageJsonImports强制 TypeScript 在从其祖先目录包含 package.json 的文件执行以 # 开头的查找时查询 package.json 文件的 imports 字段。customConditions自定义条件基本用不到resolveJsonModule解析 json 模块allowArbitraryExtensions是否以{file basename}.d.{extension} 的形式查找该路径的声明文件。noResolve不解析文件导入和三斜线指令 3.JS 支持 javascript 相关allowJs在开启此配置后可在 .ts 文件中去导入 .js / .jsx 文件。checkJs检查 js 文件也可以通过ts-checkmaxNodeModuleJsDepthnode_modules”检查 JavaScript 文件的最大文件夹深度。就是 node_modules 向上查找的层级 4.Emit 输出相关 1.declaration declaration 接受一个布尔值即是否产生声明文件 。默认不生产 2.declarationMap 引入第三方模块时默认会查找.d.ts文件配置 declarationMap 后可以映射到原始的 ts 文件。发布 npm 包时并不会携带这些文件 3.emitDeclarationOnly 此配置会让最终构建结果只包含构建出的声明文件.d.ts而不会包含 .js 文件 4.sourceMap 创建 ts 对应的.map文件 5.inlineSourceMap 内嵌 sourcemap不能与 sourceMap 属性连用 6.outFile 将所有结果打包到一个文件中指定文件名仅支持amd和system模块 7.outDir 将所有生成的文件发射到此目录中 8.removeComments 移除 ts 文件内的注释 9.noEmit 在编译过程中不生成文件但是编译过程中会进行类型检测。 10.importHelpers 基于 target 进行语法降级往往需要一些辅助函数将新语法转换为旧语法的实现。启用 importHelpers 配置这些辅助函数就将从 tslib 中导出而不是在源码中定义。 需要安装tslib并且开启moduleResolution选项。 11.importsNotUsedAsValues 是否保留导入后未使用的导入值默认则删除。此属性被verbatimModuleSyntax替代 import Car from ./car; // 导入的是类型默认会被移除。应该使用import type function buyCar(car: Car) {return car; }12.downlevelIteration 是否开启对 iterator 降级处理默认在低版本中直接转化成索引遍历 let arr [1, 2, 3]; for (let key of arr) {console.log(arr); }13.sourceRoot 在 debugger 时用于定义我们的源文件的根目录。 14.mapRoot 在 debugger 时用于定义我们的source map文件的根目录。 15.inlineSources 增加 sourcesContent压缩后依然可以找到对应的源代码 16.emitBOM 生成 BOM 头 17.newLine 换行方式 crlf(Carriage Return Line Feed)widows 系统的换行符。lf(Line Feed)Linux 系统的换行方式 18.stripInternal 是否禁止 JSDoc 注释中带有internal 的代码发出类型声明 /*** internal*/ const a abc; export default a;19.noEmitHelpers 在开启时源码中仍然会使用这些辅助函数但是不存在从 tslib 中导入的过程同时需要将importHelpers关闭。 export function merge(o1: object, o2: object) {return { ...o1, ...o2 }; }20.noEmitOnError 构建过程中有错误产生会阻止写入 21.preserveConstEnums 让常量枚举也转化成对象输出 22.declarationDir 指定声明文件输出的目录 23.preserveValueImports 保留所有值导入不进行移除。(未用到也进行保留,已经废弃) ,同importsNotUsedAsValues 输出相关declaration是否产生声明文件declarationMap为声明文件也生成 source map通过.d.ts映射到.ts文件emitDeclarationOnly仅生成.d.ts文件不生成.js文件sourceMap创建 js 对应的.map文件outFile将所有结果打包到一个文件中仅支持amd和system模块outDir将所有生成的文件发射到此目录中removeComments移除 ts 文件内的注释noEmit在编译过程中不生成文件但是编译过程中会进行类型检测。importHelpers从tslib中引入辅助函数解析高版本语法 {...obj}importsNotUsedAsValues是否保留导入后未使用的导入值downlevelIteration是否开启对 iterator 降级处理默认在低版本中直接转化成索引遍历sourceRoot在 debugger 时用于定义我们的源文件的根目录。mapRoot在 debugger 时用于定义我们的source map文件的根目录。inlineSourceMap内嵌 sourcemap不能与 sourceMap 属性连用inlineSources内链 sourcesContent 属性压缩后依然可以找到对应的源代码emitBOM生成 BOM 头newLine换行方式 crlf(Carriage Return Line Feed)widows 系统的换行符。lf(Line Feed)Linux 系统的换行方式stripInternal是否禁止 JSDoc 注释中带有internal 的代码发出声明noEmitHelpers不从 tslib 中导入辅助函数noEmitOnError构建过程中有错误产生会阻止写入preserveConstEnums让常量枚举也转化成对象输出declarationDir指定声明文件输出的目录preserveValueImports保留所有值导入不进行移除。(未用到也进行保留,已经废弃) 5.Interop Constraints 互操作约束 1.isolatedModules 隔离模块重导出一个类型需要使用export type。 2.verbatimModuleSyntax 取代 isolatedModules、preserveValueImports、importsNotUsedAsValues。import type 就删除 import就留下。 互操作约束isolatedModules隔离模块文件中需要包含import、export导入类型需要使用import type进行导入verbatimModuleSyntax取代 isolatedModules、preserveValueImports、importsNotUsedAsValuesallowSyntheticDefaultImports解决 ES Module 和 CommonJS 之间的兼容性问题。模拟默认导出。esModuleInterop解决 ES Module 和 CommonJS 之间的兼容性问题。可以支持import React from react。会自动开启allowSyntheticDefaultImportspreserveSymlinks不把符号链接解析为真实路径forceConsistentCasingInFileNames强制文件名使用时大小写一致 3.allowSyntheticDefaultImports 解决 ES Module 和 CommonJS 之间的兼容性问题。输出成module:commonjs function sum(a: number, b: number) {return a b; } export sum;import sum from ./sum; // es6方式导入兼容模块间转换模拟commonjs默认导出。 4.esModuleInterop 默认开启解决 ES Module 和 CommonJS 之间的兼容性(.default)问题。可以支持import React from react。会自动开启allowSyntheticDefaultImports 5.preserveSymlinks 是否禁用将符号链接解析为其真实路径 开启后等价于webpack.resolve.symlinks为 false 。webpack 中大多数情况下采用symlinks:trueWebpack 会按照符号链接的实际位置来解析模块这是通常的行为。 6.forceConsistentCasingInFileNames 强制文件名使用时大小写一致 6.Type Checking 类型检测 1.strict 设置为 true 会启用全部类型检测选项同时也可以指定单独关闭某个具体的类型检测的选项 2.noImplicitAny 为具有隐含“any”类型的表达式和声明启用错误报. 3.strictNullChecks 开启此选项让 typescript 执行严格的 null 检查 4.strictFunctionTypes 开启后支持函数参数的双向协变 5.strictBindCallApply 请检查“bind”、“call”和“apply”方法的参数是否与原始函数匹配。 6.strictPropertyInitialization 检查构造函数中已声明但未设置的类属性。 7.noImplicitThis 当“this”的类型为“any”时报错。 8.useUnknownInCatchVariables 将 catch 变量默认为“unknown”而不是“any”。 9.alwaysStrict 确保输出文件始终带有 “use strict” 10.noUnusedLocals 当 ts 发现未使用的局部变量时, 会给出一个编译时错误 11.noUnusedParameters 当 ts 发现参数未使用时, 会给出一个编译时错误 12.exactOptionalPropertyTypes 默认值为 false将可选属性类型解释为写入而不是添加“未定义”。在初始化时可以留空为undefined, 但是不能被手动设置为undefined 13.noImplicitReturns 默认值为 false开启这个选项所有分支都要有 return。 14.noFallthroughCasesInSwitch 默认值为 false开启这个选项每个 switch 中的 case 都要有 break 15.noUncheckedIndexedAccess 默认值为 false开启这个选项给索引签名语法声明的属性补上一个undefined类型 16.noImplicitOverride 默认值为 false开启这个选项保证子类重写基类的方法时, 必须在方法前加上override关键词 17.noPropertyAccessFromIndexSignature 默认值为 false开启这个选项禁止通过访问常规属性的方法来访问索引签名声明的属性。 18.allowUnusedLabels 默认值为 false开启这个选项后允许没有使用的 label 19.allowUnreachableCode 默认值为 false开启这个选项后则允许出现无法触达的代码 类型检查strict启用所有严格类型检测选项noImplicitAny关闭后没有指定参数类型时默认推导为 anystrictNullChecks关闭后null 和 undefiend 将会成为任何类型的子类型strictFunctionTypes关闭后参数变为双向协变strictBindCallApply关闭后不检测 call、bind、apply 传递的参数。strictPropertyInitialization关闭后函数声明属性无需初始化操作。noImplicitThis关闭后this 默认推导为 anyuseUnknownInCatchVariables关闭后catch 中的 error 类型会变为 any。alwaysStrict关闭后不使用严格模式noUnusedLocals关闭后允许声明未使用的变量noUnusedParameters关闭后允许声明未使用的参数exactOptionalPropertyTypes开启后进行严格可选属性检测不能赋予 undefinednoImplicitReturns开启后要求所有路径都需要有返回值。noFallthroughCasesInSwitch开启后switch、case 中不能存在连续执行的情况。noUncheckedIndexedAccess任意接口中访问不存在的属性会在尾部添加undefiend类型noImplicitOverride增添 override 关键字才可以覆盖父类的方法noPropertyAccessFromIndexSignature不允许访问任意接口中不存在的属性allowUnusedLabels是否允许未使用的 label 标签allowUnreachableCode是否允许无法执行到的代码 7.Completeness 完整性 完整性skipLibCheck跳过类库检测不检测内置声明文件及第三方声明文件。skipDefaultLibCheck跳过 TS 库中内置类库检测。 8.Projects 项目 1.incremental incremental 配置将启用增量构建在每次编译时首先 diff 出发生变更的文件仅对这些文件进行构建然后将新的编译信息通过 .tsbuildinfo 存储起来。 2.tsBuildInfoFile 控制这些编译信息文件的输出位置。 3.composite 在 Project References 的被引用子项目 tsconfig.json 中必须为启用状态。并且在子项目中必须启用 declaration 必须通过 files 或 includes 声明子项目内需要包含的文件等。 项目相关incremental启用增量构建 当使用–watch 的时候可以配合开启composite被 references 引用的tsconfig.json必须标识为 truetsBuildInfoFile增量构建文件的存储路径disableSourceOfProjectReferenceRedirect在引用复合项目时首选源文件而不是声明文件。disableSolutionSearching编辑时选择不检查多项目引用的项目。disableReferencedProjectLoad禁用引用项目加载 9.其他 1.files、include 与 exclude 使用 files 我们可以描述本次包含的所有文件每个值都需要是完整的文件路径适合在小型项目时使用。 {include: [src/**/*, utils/*.ts],exclude: [src/file-excluded, /**/*.test.ts, /**/*.e2e.ts] }exclude 只能剔除已经被 include 包含的文件。 2.extends tsconfig.base.json {compilerOptions: {target: ES6,module: CommonJS,strict: true,esModuleInterop: true},include: [src/**/*] }tsconfig.json {extends: ./tsconfig.base.json,compilerOptions: {outDir: ./dist} }3.references 可以将整个工程拆分成多个部分我们可以定义这些部分的引用关系为它们使用独立的 tsconfig 配置。 root index.ts import user from ../user; console.log(user());tsconfig.json {extends: ../tsconfig.json,compilerOptions: {target: ES2015,baseUrl: .,outDir: ../dist/root},include: [./**/*.ts],references: [{path: ../user}] }user index.ts export default function () {return get user; }tsconfig.json {extends: ../tsconfig.json,compilerOptions: {composite: true,target: ES5,module: NodeNext,baseUrl: .,outDir: ../dist/user},include: [./**/*.ts] }tsconfig.json {compilerOptions: {declaration: true,module: NodeNext,moduleResolution: NodeNext} }tsc --build 要打包的文件夹4.watchOptions 监听选项,一般不进行配置 watchOptions: {// 如何监听文件 使用操作系统的原生事件来进行监听watchFile: useFsEvents,// 如何监听目录watchDirectory: useFsEvents,// 对变更不频繁的文件检查频率降低fallbackPolling: dynamicPriority,synchronousWatchDirectory: true,excludeDirectories: [**/node_modules, _build],excludeFiles: [build/fileWhichChangesOften.ts] // 减少更新范围 }
http://www.tj-hxxt.cn/news/217269.html

相关文章:

  • 广州品牌网站开发我们网站在那里登陆后台系统管理
  • 平潭建设局网站首页网站设计怎么做有效的
  • 赣州建设监督网站wordpress首页模板文件
  • 手机网站常用代码检测网站是否被挂黑链
  • 做冰淇淋生意网站一个网站能卖多少钱?
  • 没有网站怎样做搜索引擎推广北京旧房改造装修哪家好
  • 慈利做网站在哪里梧州论坛一红豆社区
  • 一流学科建设专题网站国外网站页面做多大
  • 深圳网站建设公司服务商网站设计与网页制作团队
  • 自己如何开网站impreza wordpress
  • 有网站代码怎么建站wordpress 插件安全吗
  • 大同网站建设制作中国响应式网站建设
  • 互联网网站建设挣钱吗四川建筑设计公司排名
  • 监控视频做直播网站网站建设198
  • 网网站设计网郑州网站关键词推广
  • 太原市建设北路小学网站上海市企业服务云平台登录
  • 旅游门户网站模板wordpress 4.5.7
  • 网站建设教学点北京网站制作策划
  • 网站SEO建设百度号码查询平台
  • 做网站需要公司么企业网站建设设计服务
  • .tel域名不可以做网站域名吗佛山建设网站制作
  • 惠州水口网站建设张家界公司网站建设
  • 江阴外贸网站制作建设机械官方网站
  • 无锡企业推广网站图片制作视频手机软件
  • 做酒的网站爱城市网app官方下载
  • 专业网站开发工具缩短链接
  • 做网站系统学校手机网站建设公
  • 合肥快速做网站做logo网站的公司
  • 公司网站的功能wordpress火车头数据库模块
  • 网站开发设计过程网站建设收费流程