原文出处:参考:coderwhy公众号
TypeScript进阶
五、TypeScript面向对象
1. TypeScript类的基本使用
class Person {
// 成员属性:必须要声明成员属性
!name = ""; // !非空类型断言
age = 0;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
eating() {
console.log(this.name);
}
}
// 实例对象:instance
const p1 = new Person("why", 18);
const p2 = new Person("kobe", 30);
console.log(p1.name, p2.age);
export {};
1.1 成员修饰符
public:公有,默认是public
private: 私有,类的内部可访问
protected:受保护的(仅在类自身及子类中可见)
class Person {
constructor(
public name: string,
private age: number,
protected sex: number
) {}
}
// 实例对象:instance
const p1 = new Person("why", 18, 1);
console.log(p1.name);
p1.name = "kobe";
// p1.age = 18;
//子类中可访问
class Student extends Person {
constructor(name: string, age: number, sex: number) {
super(name, age, sex);
}
studying() {
console.log(this.sex);
}
}
export {};
1.2 readonly
只读属性,不能进行写入操作
1.3 setter和gettter
class Person {
//私有属性:属性前面会使用_
private _name: string;
private _age: number;
constructor(name: string, age: number) {
this._name = name;
this._age = age;
}
running() {
console.log("running", this._name);
}
// 一般针对私有属性,会提供setter和getter。一般情况下不允许直接访问类内部的私有属性
// setter/getter : 对属性的访问进行拦截操作
set name(newValue: string) {
this._name = newValue;
}
get name(): string {
return this._name;
}
set age(newValue: number) {
if (newValue >= 0 && newValue < 200) {
this._age = newValue;
}
}
get age() {
return this._age;
}
}
const p = new Person("why", 20);
p.name = "kobe";
p.age = -10; //设置不上,setter对它进行了拦截,限制在了0 -200之间
console.log(p.age);
export {};
1.4 参数属性
通过在构造函数参数前添加一个可见性修饰符public private protected 或者 readonly来创建参数属性,最后这些类属性字段也会得到这些修饰符。
class Person {
// name: string
// age: number
// height: number
// // 语法糖
// constructor(name: string, age: number, height: number) {
// this.name = name;
// this.age = age;
// this.height = height;
// }
// 等价于 《=》
// 语法糖
constructor(
public name: string,
private _age: number,
readonly height: number
) {}
}
const p = new Person("why", 18, 1.88);
console.log(p.name);
export {};
1.5 抽象类和抽象方法 abstract
多个子类可能有共同的行为,将共同的行为放到父类中,但父类中不想对这个行为有一个具体的实现,此时,就需要定义抽象类,再里面写上抽象方法,然后让子类继承这个抽象类,再在子类中实现这个方法,然后使用通用类写上抽象类的参数即可
抽象方法:没有方法体的方法(没有具体实现),必须存在于抽象类中,使用abstract声明的类和方法。
抽象类的特点:
抽象类不能被实例 (new)
抽象类可以包含抽象方法,也可以包含有实现体的方法;
有抽象方法的类,必须是一个抽象类;
抽象方法必须被子类实现,否则该类必须是一个抽象类。
abstract class Shape {
// getArea 方法只有声明,没有实现体
// 实现体让子类自己实现
// 可以将getArea方法定义为抽象方法,在方法前面添加 abstract
// 抽象方法必须出现在抽象类中。抽象类不能被实例化
abstract getArea(): number;
}
class Rectangle extends Shape {
constructor(
public width: number,
public height: number
) {
super();
}
getArea() {
return this.width * this.height;
}
}
class Circle extends Shape {
constructor(public redius: number) {
super();
}
getArea() {
return this.redius ** 2 * Math.PI;
}
}
class Triangle extends Shape {
getArea() {
return 100;
}
}
// 通用的函数
function calcArea(shape: Shape) {
return shape.getArea();
}
calcArea(new Rectangle(10, 20));
calcArea(new Circle(5));
1.6 ts中类型检测-鸭子类型
// TypeScript 对于类型检测的时候使用的是鸭子类型
// 鸭子类型:如果一只鸟,走起来像鸭子,游起来像鸭子,看起来像鸭子,那么你可以认为它就是一只鸭子。
// 鸭子类型只关心属性和行为,不关心具体是不是对应的类型。
class Person {
constructor(
public name: string,
public age: number
) {}
}
class Dog {
constructor(
public name: string,
public age: number
) {}
}
function printPerson(p: Person) {
//鸭子类型的类型检测
console.log(p.name, p.age);
}
printPerson(new Person("why", 18));
printPerson({ name: "kobe", age: 30 });
printPerson(new Dog("旺财", 2)); //没有报错
const person: Person = new Dog("果汁", 5);
export {};
1.7 类-具有的类型特性
class Person {}
/**
* 类的作用:
* 1. 可以创建类对应的实例对象
* 2. 类本身可以作为这个实例的类型
* 3. 类也可以当成有一个构造签名的函数
*/
const name: string = "aaa";
const p: Person = new Person();
function printPerson(p: Person) {}
function factory(ctor: new () => void) {}
factory(Person);
export {};
1.8 ts对象类型的修饰符
? 可选
readonly 只读
//定义对象类型
type IPerson = {
// 属性?:可选的属性
name?: string;
// readonly:只读的属性
readonly age: number;
};
interface IKun {
name: string;
slogan: string;
}
const p: IPerson = {
name: 'why',
age:18
}
// p.age = 20
export {};
1.9 ts对象类型的索引签名
在对象类型里面定义索引签名
有的时候,你不能提前知道一个类型里的所有属性的名字,但是你知道这些值的特征,这种情况,就可以使用一个索引签名来描述可能的值的类型。
一个索引签名的属性类型必须是string或者是number
虽然同时支持string和number类型,但数字索引的返回类型一定要是字符串返回类型的子类型。
- 基本使用:
// 1. 索引签名的理解
interface InfoType {
// 索引签名:可以通过字符串索引,去获取到一个值,也是字符串
[key: string]: string;
}
function getInfo(): InfoType {
const abc: any = "haha";
return abc;
}
const info = getInfo();
const name = info.name;
console.log(name, info.age, info.address);
// 2.索引签名的案例
interface ICollection {
[index: number]: string;
length: number;
}
function printCollection(collection: ICollection) {
for (let i = 0; i < collection.length; i++) {
const item = collection[i];
console.log(item.length);
}
}
const array = ["abc", "ab2", "ddb"];
const tuple: [string, string] = ["why", "广州"];
printCollection(array);
printCollection(tuple);
export {};
- 类型问题
interface IIndexType {
// 返回值类型的目的是告知通过索引去获取到的值是什么类型
// 且类型只能是number | string 的其中的一个类型
// [index:number]:string
// [index:string]:any
// [index: string]: string;
[index: string]: any;
}
// 索引签名: [index:number]:string
// const names: IIndexType = ["abc", "bdc"];
// 索引签名:[index:string]:any; //没有报错
// 1.索引要求必须是字符串类型 names[0] => names['0']
// const names: IIndexType = ["abc", "bdc"];
// 索引签名:[index: string]: string; 会报错
// 严格字面量赋值检测:["abc", "bdc"] => Array实例 => names[0] names.forEach
// const names: IIndexType = ["abc", "bdc"];
// names['forEach'] => function
//索引签名
export {};
- 两个签名
interface IIndexType {
// 两个索引类型的写法
[index: number]: string;
[key: string]: any;
// 要求一:下面的写法不允许:数字类型索引的类型,必须是字符串类型索引的类型的子类型
// 结论:数字类型必须是比字符串类型更加确定的类型(需要是字符串类型的子类型)
// 原因:所有的数字类型都是会转成字符串类型去对象中获取内容
// 数字 0:number | string。当我们是一个数字的时候,既要满足通过number拿到的内容,不能和string拿到的结果矛盾。
// 数字 '0' :sting
// 数字 0:string
// 数字 '0':number | string
// [index: number]: number | string;
// [key: string]: string;
// 要求二:如果索引签名中有定义其它属性,其它属性返回的类型,必须符合string类型返回的属性
[index:number]:string
[key:string]:number | string
aaa:string
// aaa:boolean 不允许
}
const names: IIndexType = ["ABC", "DDD"];
const item1 = names[0];
const item = names["forEach"];
export {};
1.10 接口继承
interface IPerson {
name: string;
age: number;
}
//可以从其他的接口中继承过来属性
// 1.减少了相同代码的重复编写
// 2.如果使用第三方库,给我们定义了一些属性,自定义一个接口,同时你希望自定义接口拥有第三方某一个类型中的所有属性,使用继承extends
interface IKun extends IPerson {
slogan: string;
}
const ikun: IKun = {
name: "why",
age: 18,
slogan: "xxxx"
};
export {};
1.11 接口的类实现过程
接口是可以被类实现的
interface IKun {
name: string;
age: number;
slogan: string;
playBasketball: () => void;
}
const ikun: IKun = {
name: "why",
age: 18,
slogan: "xxx",
playBasketball: function () {}
};
interface IRun {
running: () => void;
}
//作用:接口被类实现
class Person implements IKun, IRun {
// name: string;
// age: number;
// slogan: string;
name = "";
age = 0;
slogan = "";
playBasketball() {}
running() {}
}
const ikun2 = new Person();
const ikun3 = new Person();
console.log(ikun2.name, ikun2.age, ikun2.slogan);
ikun2.playBasketball();
ikun2.running();
export {};
1.12 严格字面量赋值检测
interface IPerson {
name: string
age: number,
}
//1. 奇怪的现象一:
// 定义info类型是IPerson类型
// const info: IPerson = {
// name: 'why',
// age: 19,
// height:1.88
// }
const obj = {
name: "why",
age: 18,
//多了一个height属性
height: 1.88
};
const info: IPerson = obj;
//2.奇怪的现象二:
function printPerson(person: IPerson) {
}
// printPerson({name:'kobe',age:30,height:1.98})
const kobe = { name: 'kobe', age: 30, height: 1.98 };
// 解释现象
// 第一次创建的对象字面量,称之为fresh(新鲜的)
// 对于新鲜的字面量,会进行严格的类型检查,必须完全满足类型的要求(不能有多余的属性)
// 当类型断言或对象字面量的类型扩大时(重新赋值了),就不再进行类型检测
const p : IPerson = {};
printPerson(kobe);
1.13 抽象类和接口的区别
都可以在其中定义一个方法,让子类或实现类来实现对应的方法。
抽象类和接口的区别:
- 抽象类是事物的抽象,抽象类用来捕捉子类的通用特性,接口通常是一些行为的描述。
- 抽象类通常用域一系列关系紧密的类之间,接口只是用来描述一个类应该具有什么行为;
- 接口可以被多层实现,而抽象类只能单一继承
- 抽象类中可以有实现体,接口中只能有函数的声明;
2.TypeScript 枚举类型
2.1 枚举类型基本使用
// 定义枚举类型
enum Direction {
UP,
DOWN,
LEFT,
RIGHT
}
const d1: Direction = Direction.UP;
function turnDirection(direction: Direction) {
switch (direction) {
case Direction.LEFT:
console.log("角色向左移动一个格子");
break;
case Direction.RIGHT:
console.log("角色向右移动一个格子");
break;
}
}
//监听键盘的点击
turnDirection(Direction.LEFT);
export {};
2.2 枚举类型设置值
// 定义枚举类型
enum Direction {
UP, //0
DOWN, //1
LEFT, //2
RIGHT
}
// enum Direction {
// LEFT = 100
// }
// enum Direction {
// Left,
// RIGHT,
// TOP = "TOP",
// BOTTOM = "BOTTOM",
// }
enum Operation {
READ = 1 << 0, //1 位运算
WRITE = 1 << 1, //2
FOO = 1 << 2 //3
}
const d1: Direction = Direction.UP;
function turnDirection(direction: Direction) {
switch (direction) {
case Direction.LEFT:
console.log("角色向左移动一个格子");
break;
case Direction.RIGHT:
console.log("角色向右移动一个格子");
break;
}
}
//监听键盘的点击
turnDirection(Direction.LEFT);
export {};
六、泛型
6.1 类型的参数化
// 1.理解形参和实参,但是参数的类型是固定的
// function foo(name:string, age:number) {
// }
// foo('why',10)
// 2.定义函数:将传入的内容返回
// number/string/{name:string}
function bar<Type>(arg: Type): Type {
return arg;
}
//2.1. 完整的写法 将参数类型传入
const res1 = bar<number>(123);
const res2 = bar<string>("abc");
const res3 = bar<{ name: string }>({ name: "why" });
//2.2 省略的写法
const res4 = bar("aaaa"); //aaaa类型
const res5 = bar(111); //111类型
let message = "hello world"; //string
export {};
6.2 useState 的函数的泛型实现
// 元组:useState函数
function useState<T>(initialState: T): [T, (newState: T) => void] {
let state = initialState;
function setState(newState: T) {
state = newState;
}
return [state, setState];
}
//初始化count
const [count, setCount] = useState(100);
const [message, setMessage] = useState("xxxx");
export {};
6.3 支持传入多个泛型类型参数
function foo<Type, Element>(arg1: Type, arg2: Element) {}
foo(10, 20);
foo(10, "abc");
export {};
平时在开发中的一些常用的名称:
T : Type的缩写,类型
K、V : key 和 value的缩写,键值对
E:Element的缩写,元素
O:Object的缩写,对象
6.4 泛型接口
//可以使用默认值
interface IKun<T = string> {
name: T;
age: number;
slogan: T;
}
const kunkun: IKun<string> = {
name: "why",
age: 18,
slogan: "hahhaha"
};
const ikun2: IKun<number> = {
name: 123,
age: 23,
slogan: 23
};
const ikun3: IKun = {
name: "kobe",
age: 30,
slogan: "wwewe"
};
export {};
6.5 泛型类的使用
class Point<T = number> {
x: T;
y: T;
constructor(x: T, y: T) {
this.x = x;
this.y = y;
}
}
const p1 = new Point(10, 20);
console.log(p1.x);
const p2 = new Point("123", "12");
console.log(p2.x);
export {};
6.6 泛型约束(Generic Constraints)
interface ILength {
length: number;
}
//1.getLength没有必要使用泛型
function getLength(arg: ILength) {
return arg.length;
}
const length1 = getLength("aaaa");
const length2 = getLength(["aaa", "bb"]);
const length3 = getLength({ length: 100 });
//2.获取传入的内容,这个内容必须有length属性
// T相当于是一个变量,用于记录本次调用的类型,所以在整个函数的执行周期中,一直保留着参数的类型
function getInfo<T extends ILength>(args: T): T {
return args;
}
const info1 = getInfo("aaaa");
const info2 = getInfo(["aaa", "bb"]);
const info3 = getInfo({ length: 100 });
getInfo(1234);
getInfo({});
export {};
6.7 泛型参数使用约束
// 传入的key的类型,obj当中key的其中之一。 对对象的key做约束
interface IKun {
name: string;
age: number;
}
type IKunkeys = keyof IKun; // "name" | "age" 所有key的联合类型
function getObjectProperty<O, K extends keyof O>(obj: O, key: K) {
return obj[key];
}
const info = {
name: "why",
age: 18,
height: 1.88
};
const address = getObjectProperty(info, "address"); //进行key的约束后,报错 类型“"address"”的参数不能赋给类型“"name" | "age" | "height"”的参数。
const name = getObjectProperty(info, "name");
export {};
6.8 映射类型 - 基本使用
// TypeScript提供了映射类型:函数
// 映射类型不能使用interface定义
// Type = IPerson
// keyof = 'name' | 'age'
type MapPerson<Type> = {
// [index: number]: any
// 索引类型依次进行遍历使用
[Property in keyof Type]: Type[Property];
// name:string
// age:number
};
interface IPerson {
name: string;
age: number;
}
// 拷贝一份IPerson
// interface NewPerson {
// name: string;
// age: number;
// }
type NewPerson = MapPerson<IPerson>
export {};
6.9 映射类型-修饰符使用
type MapPerson<Type> = {
// 修饰符前 使用 + 或者 - ,表示增加,删除这个修饰符产生的可选属性
-readonly [Property in keyof Type]-?: Type[Property]; // ?变成可选属性 readonly变成只读
};
interface IPerson {
name: string;
age: number;
height: number;
address: string;
}
//拷贝出来一份对某个属性的类型做修改
type NewPerson = MapPerson<IPerson>;
export {};
6.10 映射类型-修饰符符号
type MapPerson<Type> = {
// 修饰符前 使用 + 或者 - ,表示增加,删除这个修饰符产生的可选属性
// - 把原来的可选或者只读的属性变成必传
-readonly [Property in keyof Type]-?: Type[Property]; // ?变成可选属性 readonly变成只读
};
interface IPerson {
name: string;
age?: number;
readonly height: number;
address: string;
}
type IpersonRequired = MapPerson<IPerson>;
const p: IpersonRequired = {
name: 'why',
age: 18,
height: 1.99,
address:'xxxx'
}
export {};
类型体操的题目地址:
https://github.com/type-challenges/type-challenges
https://ghaiklor.github.io/type-challenges-solutions/en/
七.扩展知识
7.1 TypeScript 模块化的使用
模块化
commonjs => node环境 => webpack/vite
module.exports = {}
esmodule:import/export
import { sum } from "./utils/math";
// 导入的是类型,推荐在类型的前面加上type关键字,表示这是一个类型。
// 让非TypeScript编译器,如:Babel,swc,esbuild知道什么样的导入可以被安全移除。最后在经过编译后,全部都会被删除掉。
import type { IDType, IPerson } from "./utils/type";
const id1: IDType = 111;
const p:IPerson = {name:'why',age:18}
export {};
7.2 命名空间namespace(了解)基本不这么使用了
目的:将一个模块内部再进行作用域的划分,防止一些命名冲突的问题。更推荐使用ES模块
// format.ts
export namespace price {
export function format(price:number){
return '¥' + price
}
const name = 'price'
}
export namespace date {
export function format(dateString){
return '2024-02-02'
}
const name = 'date'
}
//index.ts
import {price,date} from './utils/format';
//使用命名空间中的内容
price.format('1111')
7.3 类型的查找
.d.ts文件:用来做类型的声明(declare),称之为类型声明(Type Declaration)或者类型定义(Type Definition)文件。
typescript会在哪里查找类型声明呢?
- 内置类型声明;(ts自带的)lib.[something].d.ts 不需单独引入
- 外部定义类型声明;(第三方库)
- 自己定义类型声明;
7.4 ts知识扩展-webpack
将ts转为js,运行在浏览器
// src/index.ts
import {sum} from './utils/math';
import axios from 'axios';
import type {AxiosRequestConfig,AxiosInstance} from 'axios'
const message:string = 'Hello World';
console.log(message.length,message);
console.log(sum(20,30));
// lib.dom.d.ts
const h2El = document.createElement("h2");
h2El.textContent = 'Hello TypeScript';
document.body.append(h2EL);
//lib.es2015.d.ts
const promise = new Promise((resolve,reject)=>{})
console.log(message.startsWith('Hello'))
npm init
创建package.json文件
npm install webpack webpack-cli -D
npm install ts-loader -D
npm install html-webpack-plugin -D
npm install webpack-dev-server -D
开启本地服务
tsc --init
生成tsconfig.json文件
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode:"development",
entry:"./src/index.ts",
output:{
path:path.resolve(__dirname,'./dist'),
filename:'bundle.js'
},
resolve:{
extensions:[".ts",".js",".cjs",".json"]
},
devServer:{},
module:{
rules:[
{
test:/\.ts$/,
loader:"ts-loader"
},
{
test:/\.(png|jpe?g|svg|gif)$/,
type:"asset/resource"
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:"./index.html"
})
]
}
<!-- index.html-->
//package.json文件
"scripts":{
"server":"webpack serve",
"build":"webpack"
}
//tsconfig.json
内置声明的环境是通过tsconfig.json中的target和lib来配置的
外部定义类型声明-第三方库:通常有两种类型声明方式:
- 在自己库中进行类型声明(编写.d.ts文件),比如axios
npm install axios
包本身包含声明文件 - 通过社区的一个公有库DefinitelyTyped存放类型声明文件
- 该库的GitHub地址:https//github.com/DefinitelyTyped/DefinitelyTyped/
- 该库查找声明安装方式的地址:https://www.typescriptlang.org/dt/search?search=
- 比如我们安装react的类型声明:
npm i @types/react --save-dev
没有包含但在官方DefinitelyTyped库中包含。
- 自定义类型声明文件 当没有@types/xxx的包时,怎么处理报错呢?
手写自定义声明文件。新建types文件夹及.d.ts文件,使用declare声明该模块
//why.d.ts .d.ts文件只写声明,不写实现
declare module 'xxx'; //声明这个模块后,就不会再报错了
declare module 'xxx' { // 声明模块,在内部,可导出类,函数等
export function join(...args:any[]):any
}
//需要编写类型声明 全局
//为自己的变量定义类型声明
declare const whyName:string
declare function foo(bar:string):string
declare class Person {
constructor(public name:string,public age:number)
}
// 作为一个第三方库为其它开发者提供类型声明文件,.d.ts => axios.d.ts
// 声明文件模块,如在ts文件中使用import导入图片时,报错。把文件声明成一个模块
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.svg'
// 不识别 .vue文件
declare module '*.vue' {
import {DefineComponent} from 'vue'
const component:DefineComponent
export default component
}
declare module '*.jpeg' {
const src:string
export default src
}
//jquery 不能使用 声明成模块(不合适)
// 声明称命名空间
declare namespace $ {
export funciton ajax(settings:any):any
}
给自己的代码添加声明文件
type IDType = number | string
平时使用的代码中用到的类型,直接在当前位置进行定义或者在业务文件夹某一个位置编写一个类型文件即可。
7.5 tsconfig.json文件
当目录中出现了tsconfig.json文件,则说明该目录是TypeScript项目的根目录;
tsconfig.json文件指定了编译项目所需的根目录下的文件以及编译选项。
tsconfig.json文件的作用:
-
让TypeScript Compiler在编译的时候,知道如何去编译TypeScript代码和进行类型检测;
a. 比如是否允许不明确的this选项,是否允许隐式的any类型
b. 将TypeScript代码编译成什么版本的JavaScript代码 -
让编辑器,比如VSCode,可以按照正确的方式识别TypeScript代码;
a. 对于哪些语法进行提示、类型错误检测等等。
tsconfig.json在编译时如何被使用?(手动编译)
- 在调用tsc命令并且没有其它输入文件参数时,编译器将由当前目录开始向父级目录寻找包含tsconfig文件的目录。
- 调用tsc命令并且没有其它输入文件参数,可以使用 --project (或者只是 -p)的命令行选项来指定包含了tsconfig.json的目录
- 当命令行中指定了输入文件参数,tsconfig.json 文件会被忽略。如
tsc index.ts
webpack中使用ts-loader进行打包时,会自动读取tsconfig文件,根据配置编译TypeScript代码
tsconfig.json文件选项:https://www.typescriptlang.org/tsconfig
// tsconfig.json文件
{
"compilerOptions":{
//目标代码
"target":"esnext",
//生成代码使用的模块化
"module":"esnext",
//打开所有的严格模式检查
"strice":true,
"allowJs":fasle,
"noImplicitAny":false,
//jsx的处理方式(保留原有的jsx格式)
"jsx":"preserve",
//是否帮助导入一些需要的功能模块
"importHelpers":true,
//按照node的模块解析规则
"moduleResolution":"node",
//跳过对整个库进行类型检测,而仅仅检测你用到的类型
"skipLibCheck":true,
//可以让es module和commonjs相互调用
"esModuleInterop":true,
//允许合成默认模块导出
// import * as react from 'react:false
// import react from 'react':true
"allowSyntheticDefaultImports":true,
//是否要生成sourcemap文件
"sourceMap":true,
//文件路径在解析时的基本url
"baseUrl":".",
//指定tyeps文件需要加载哪些(默认是都会进行加载的)
//"types":[
// "weback-env"
//],
//路径的映射设置,类似于webpack中的alias
"paths":{
"@/*":["src/*"], //配置别名
},
//指定我们需要使用到的库(也可以不配置,直接根据target来获取)
"lib":["esnext","dom","dom.iterable","scripthost"]
},
//一般不需要指定 start
// "files":[], //指定哪些ts文件需要进行编译
//"include":["./src/**/*.ts",'./types/*.d.ts'] //编译src下所有的ts文件,**表示所有的子目录,*.ts表示所有子目录下的.ts文件
//"exclude":["node_modules"], //用于指定从include中排除哪些文件
//一般不需要指定 end
}
7.6 TS知识扩展-axios封装
// src/service/index.ts
import {BASE_URL,TIME_OUT} from './config';
import HYRequest from './request';
const hyRequest = new HYRequest({
baseURL:BASE_URL,
timeout:TIME_OUT
});
//如果只针对某个接口加拦截,就可以这样操作
export const hyRequest2 = new HYRequest({
baseURL:'http://xxx.com:1888/xxx/api',
timeout:8000,
//假设想给2这个接口的添加不同的拦截起
interceptors:{
requestSuccessFn:(config)=>{
console.log('端口为1888的请求成功的拦截')
return config
},
requestFailureFn:(err)=>{
console.log('端口为1888的请求失败的拦截')
return err
},
responseSuccessFn:(res)=>{
console.log('端口为1888的响应成功的拦截')
return res
},
responseFailureFn:(err)=>{
console.log('端口为1888的响应失败的拦截')
return err;
}
}
})
export default hyRequest
//src/service/request/types.ts
import type {AxiosRequestConfig,AxiosResponse} from 'axios';
//针对AxiosRequestConfig配置进行扩展
export interface HYInterceptors<T = AxiosResponse> {
requestSuccessFn?:(config:AxiosRequestConfig)=>AxiosRequestConfig
requestFailureFn?:(err:any)=>any
responseSuccessFn?:(res:T)=>T
responseFailureFn?:(err:any)=>any
}
export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?:HYInterceptors<T>
}
//src/service/request/index.ts
import axios from 'axios';
import type {AxiosInstance,AxiosRequestConfig,AxiosResponse} from 'axios';
import type {HYRequestConfig} from './type';
// 拦截器:蒙版 loading/token/修改配置
/**
* 两个难点:
* 1. 拦截器进行精细控制
* > 全局拦截器
* > 实例拦截器
* > 单次请求拦截器
* 2. 响应结果的类型处理(泛型)
*/
class HYRequest {
instance:AxiosInstance
//request示例 => axios的实例
constructor(config:HYRequestConfig){
this.instance = axios.create(config)
// 每个instance实例都添加拦截器
this.instance.interceptors.request.use((config)=>{
// loading/token
console.log("全局请求成功的拦截")
return config
},err=>{
console.log('全局请求失败的拦截')
return err;
})
this.instance.interceptors.response.use((res)=>{
console.log('全局响应成功的拦截');
return res.data;
},err=>{
console.log('全局响应失败的拦截');
return err
})
//拦截器可以添加多次,不会覆盖,同时存在
//针对特定的hyRequest 实例添加拦截器
this.instance.interceptors.request.use(
config.interceptors?.requestSuccessFn,
config.interceptors?.requestFailureFn
)
this.instance.interceptors.response.use(
config.interceptors?.responseSuccessFn,
config.interceptors?.responseFailureFn
)
}
// 封装网络请求的方法
request<T = any>(config:HYRequestConfig) {
//单次请求的成功拦截处理
if(config.interceptors?.requestSuccessFn){
config = config.interceptors.requestSuccessFn(config);
}
//返回Promise
return new Promise<T>((resolve,reject)=>{
this.instance.request<any,T>(config).then(res=>{
//单次响应的成功拦截处理
if(config.interceptors?.responseSuccessFn){
res = config.interceptors?.responseSuccessFn(res)
}
}).catch(err=>{
reject(err);
})
})
}
get<T = any>(config:HYRequestConfig<T>){
return this.request({...config,method:'GET'})
}
post<T = any>(config:HYRequestConfig<T>){
return this.request({...config,method:'POST'})
}
delete<T = any>(config:HYRequestConfig<T>){
return this.request({...config,method:'DELETE'})
}
patch<T = any>(config:HYRequestConfig<T>){
return this.request({...config,method:'PATCH'})
}
}
export default HYRequest;
//src/service/modules/home.ts
import hyRequest from '..';
//发送网络请求
interface IHomeData {
data:any,
returnCode:string,
success:boolean
}
hyRequest.request<IHomeData>({
url:'/home/multidata',
}).then(res=>{
console.log(res.data,res.success,res.returnCode);
})
//src/service/modules/entire.ts
import {hyRequest2} from '..';
hyRequest2.request({
url:'/entire/list',
params:{
offset:0,
size:20
}
}).then(res=>{
console.log(res.data)
})
interface IHighScoreData {
list:any[],
subtitle:string,
title:string,
type:stirng,
_id:string
}
//给这个接口额外增加拦截器
hyRequest2.request<IHighScoreData>({
url:'/home/highscore',
interceptors:{
requestSuccessFn:(config)=>{
console.log('接口/home/highscore请求成功的拦截')
return config
},
responseSuccessFn:(res)=>{
console.log('接口/home/highscore响应成功的拦截')
return res;
}
}
})
//src/service/config/index.ts
export const BASE_URL = "http://XXX.XXX:8000"
export const TIME_OUT = 10000;
// src/index.ts
import './service/modules/home';
import './service/modules/entire';
//webpack依赖图
八、ts条件类型-类型体操
8.1 条件类型基本使用
type IDType = number | string;
//判断number是否是extends IDType
// const res = 2 > 4 ? true : false;
type ResType = number extends IDType ? true : false;
//举个例子:函数的重载
// function sum(num1: number, num2: number): number;
// function sum(num1: string, num2: string): string;
//错误做法:类型扩大化
// function sum(num1:string | number,num2:string | number):string
function sum<T extends number | string>(num1: T, num2: T): T extends number ? number : string;
function sum(num1: any, num2: any) {
return num1 + num2;
}
const res = sum(20, 30); //number
const res2 = sum('abc', 'cba'); // string
// const res3 = sum(123, 'ab');
export {};
8.2 条件类型的类型推断 infer
// infer对返回值和参数的类型推断
type CalcFnType = (num1: number, num2: number) => number;
function foo() {
return "abc";
}
//获取一个函数的返回值类型 内置工具
type MyReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R
? R
: never;
type MyParameterType<T extends (...args: any[]) => any> = T extends (...args: infer A) => any
? A
: never;
// type CalcReturnType = ReturnType<CalcFnType>;
// type FooReturnType = ReturnType<typeof foo>;
// type FooReturnType2 = MyReturnType<boolean>; //报错
type CalcReturnType = MyReturnType<CalcFnType>; //type CalcReturnType = number
type FooReturnType = MyReturnType<typeof foo>; //type FooReturnType = string
type CalcParameterType = MyParameterType<CalcFnType>; //type CalcParameterType = [num1: number, num2: number]
export {};
8.3 ts条件类型-分发类型
//当在泛型中使用条件类型的时候,如果传入一个联合类型,就会变成分发的
type toArray<T> = T extends any ? T[] : never;
type NumArray = toArray<number>;
// number[] | string[] 而不是(number | string)[]
type NumAndStrArray = toArray<number | string>; //type NumAndStrArray = number[] | string[]
export {};
8.4 TS内置工具-类型体操
interface IKun {
name: string;
age: number;
slogan?: string;
}
// 类型体操 实现 Partial
type HYPartial<T> = {
// 使用映射类型
[P in keyof T]?: T[P];
};
// 1. Partial 把一个对象中的所有属性都变成可选的
// IKun 都变成可选的
type IKunOptional = Partial<IKun>;
/**结果:type IKunOptional = {
name?: string | undefined;
age?: number | undefined;
slogan?: string | undefined;
} */
// 类型体操 实现 Required
type HYRequired<T> = {
// 使用映射类型
[P in keyof T]-?: T[P];
};
// 2. Required 将所有属性变成必选的
type IKunRequired = Required<IKun>;
/**结果: type IKunRequired = {
name: string;
age: number;
slogan: string;
}*/
// 类型体操 实现 Readonly
type HYReadonly<T> = {
// 使用映射类型
readonly [P in keyof T]: T[P];
};
// 3. Readonly 将所有属性变成只读的
type IKunReadonly = Readonly<IKun>;
/**结果: type IKunReadonly = {
readonly name: string;
readonly age: number;
readonly slogan?: string | undefined;
}*/
type keys = keyof IKun;
type Res = keyof any; //type Res = string | number | symbol
// 类型体操 实现 Record
// 确保Keys一定是可以作为key的联合类型
type HYRecord<Keys extends keyof any, T> = {
[P in Keys]: T;
};
type t1 = "上海" | "北京" | "洛杉矶";
// 3. Record 用于构造一个对象类型,它所有的key(键)都是Keys类型,它所有的value(值)都是Type类型
type IKunRecord = Record<t1, IKun>;
/**结果:type IKunRecord = {
上海: IKun;
北京: IKun;
洛杉矶: IKun;
}*/
const ikuns: IKunRecord = {
上海: {
name: "xxx",
age: 10
},
北京: {
name: "yy",
age: 5
},
洛杉矶: {
name: "zz",
age: 10
}
};
// 类型体操 实现 Pick
type HYPick<T, K extends keyof T> = {
[P in K]: T[P];
};
// 4. Pick<Type,Keys> 从Type类型里面挑选一些属性Keys
type Ikuns = Pick<IKun, "name" | "age">;
/**
* 结果:type Ikuns = {
name: string;
age: number;
}
*/
// 类型体操 实现 Omit
type HYOmit<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P];
};
// 5. Omit<Type,Keys> 从Type类型过滤掉一些属性Keys
type IkunOmit = Omit<IKun, "name" | "age">;
/**
* 结果:type IkunOmit = {
slogan?: string | undefined;
}
*/
type IKun1 = "sing" | "dance" | "rap";
// 类型体操 实现 Exclude
type HYExclude<T, E> = T extends E ? never : T;
// 6. Exclude<UnionType,ExcludedMembers> 从UnionType联合类型里排除了所有可以赋给ExcludedMembers的类型
type IkunExclude = Exclude<IKun1, "rap">;
/**
* 结果:type IkunExclude = "sing" | "dance"
*/
// 类型体操 实现 Extract
type HYExtract<T, E> = T extends E ? T : never;
// 7. Exclude<Type,Union> 从Type类型中提取出Union类型
type IkunExtract = Extract<IKun1, "rap">;
/**
* 结果:type IkunExtract = "rap"
*/
type IKun2 = "sing" | "dance" | "rap" | null | undefined;
// 类型体操 实现 NonNullable
type HYNonNullable<T> = T extends null | undefined ? never : T;
// 8. NonNullable<Type> 从Type类型中移除null和undefined类型
type IkunNonNullable = NonNullable<IKun2>;
/**
* 结果:type IkunNonNullable = "sing" | "dance" | "rap"
*/
// 9. InstanceType<Type> 用于构造一个由所有Type的构造函数的实例类型组成的类型
// 类型体操 实现InstanceType
type HYInstanceType<T extends new (...args: any[]) => any> = T extends new (
...args: any[]
) => infer R
? R
: never;
class Person {}
class Dog {}
const p1: Person = new Person();
// typeof Person构造函数具体的类型
// InstanceType 构造函数创建出来的实例对象的类型
type HYPerson = InstanceType<typeof Person>;
const p2: HYPerson = new Person();
// 构造函数的例子
function factory<T extends new (...args: any[]) => any>(ctor: T): InstanceType<T> {
return new ctor();
}
const p3 = factory(Person); //const p3: Person
const d = factory(Dog); //const p4: Dog
export {};