半个小时让你精通TypeScript【6K字学习笔记】

209 阅读18分钟

TypeScript

基本数据类型

类型推导:

声明一个标识符(变量)时,如果有则直接赋值,会根据赋值的类型推导出标识符(变量)的类型注解

let推导的是通用类型

若用 const 来进行类型推导,其变量会推导为字面量类型:

Number类型

ts 与 js 都不区分int 和double统一为number

num=100 //十进制
num=0b110 //二进制
0o555//八进制
0xf23//十六进制

Boolean类型

string类型

Array类型

Array也会根据里面的值自动推导

let name: string[]=['aaa','bbb'];手动定义类型
let name=['abc','aaa'];自动推导
let nums: Array<number>=[]//泛型写法

Object类型

let info: object={
    name:"why",
    age:18
}

上述写法指明info为通用对象类型

在调用 object.name,object.age 时会报错,不推荐使用

let info:{
	name:string,
	age:number,
}={
	name:"zxs",
	age:18,
	heiht:1.8
}

上述方法,在添加对象属性时很麻烦,不推荐使用

建议用法

1.type声明

type info={
	age:stirng,
	age:number
}

2.interface声明

Symbol类型

const s1: symbol=Symbol("title");

Null&undefined类型

函数的类型

返回值会进行自动类型推导

function sum(num1 :xxx,num2 :xxx):xxx{
	
}

匿名函数

const names=['abc','bcd','ccc'];

names.forEach(funtion(item,index,arr){
              
 })

这里ts根据forEach函数类型以及数组的类型推断出item的类型

这个过程称之为上下文类型,因为函数的上下文可以帮助确定参数的返回值的类型;

匿名函数不建议设置参数类型,因为其会自动类型推导

对象类型与函数类型结合

function printCoordinate(point:{x: number,y: number}){
	
}

可选类型: ?

Any类型

unknown类型

用于描述类型不确定的变量

和any有点类似,但unknown类型的值上做任何事情都是不合法的****

let foo: any='aaa';
foo=123;//合法

let foo1: unknown="bbb"
console.log(foo1.length);不合法

如果要对unknown类型的变量进行操作,则需要进行类型判断::被称为类型缩小

let foo1: unknown="bbb"
if(typeof foo==="string"){ 
	console.log(foo1.length);//合法
}

Void类型

函数无返回值=返回undefined

应用场景:

用来指定函数类型的返回值是void

const foo:()=>void=()=>{
    
}

type fooType=()=>void;//定义函数类型
const foo:FooType=()=>{}//使用该函数类型

Never类型

应用场景:

  1. 开发中很少实际去定义never类型,某些情况会自动类型推导出never

  2. 开发框架,工具时可能会用到

  3. 封装类型工具时,可以使用never

function abb(message: string|number){
	switch(typeof message){
        case "string":
            break;
        case "number"
            break;
        default:
            const check: never=message;
    }
}

上述代码永远不会跑到default选项,别人使用时 如果增加传参的类型,其使用时会报错。

但如不加never则会无提示。

总的来说,用来提高代码健壮性

Tuple元组类型

需求:保存个人信息: "zxs",18,1.88

1.数组最好用来存放相同类型的数据

2.使用对象类型(最多)

3.使用元组

const info: any[]=["zxs",18,1.88]
const info2={
    name:'zxs',
    age:18,
    height:1.88
}
//元组内可以存放不同数据类型,取出来的item也是有明确的数据类型
const info3: [string,number,number]=["zxs",18,1.88]
const item2=info3[0]//一定是string类型

在函数中使用元组是最多的(定义函数返回值)

const [count,setCount]: [number,(newValue:number)=>void]=useState(10);

语法细节

联合类型:

ts允许使用多种运算符,从现有类型中构建新类型

const foo: string|number|boolean='abc'

注意使用类型缩小

类型别名

type name=number|string
type location={
    x:number,
    y:number,
    z?:number
}

Interface与Type区别

Type是赋值的方式

Interface是声明的方式

type info={
    name:string,
    age:number
}
interface info{
    name:string
    age:numver
}

在定义对象类型时,你可以任意选择使用

区别:1.type 使用范围更广,而接口只能用来声明对象

2.在声明对象时interface可以多次声明,而type不允许两个同名别名同时存在

interface pointType{
	x:number
	y:number
}
interface pointType{
	z:number
}
const point:pointType={
    x:100,
    y:100,
    z:100
}//正确
const point:pointType={
    x:100,
    y:100,
}//报错

interface多次声明时,其实现必须包含里面全部的属性

3.interface支持继承

interface Person{
	name:string
	age:number
}
interface Ikun extends Person{
    logo:string
}
const ikun:Ikun={
    logo:"哈哈哈",
    name:"zxs",
    age:30
}

4.interface可以被类实现:类里必须含有接口里的属性

class Person implements Person{

}

总结:非对象类型使用type对象类型使用interface

交叉类型

基础类型使用交叉类型,一般没有意义

type NewType=number & string//==>never类型

在对象中使用交叉类型才有意义

interface Ikun{
	name:string
	age:string
}
interface ICoder{
	name:string
	codig:()=>void
}

//使用交叉类型
const Me: Ikun & ICoder={
    name:'zxs',
    age:18,
    coding:function(){
        console.log("coding");
    }
}

类型断言 as

场景:获取DOM元素

const imgEl=documnet.querySeletor(".img")

这里调用migEl的src,alt等特有属性,都会报错,因为不确定有没有该属性

如果确定元素类型,且存在则

const imgEl=document.querySelector(".img") as HTMLImageElement
imgEl.src="xxx"

Ts只允许类型断言转换为更具体不太具体(any,unknown)的类型,此规则可防止不可能的强制转换

比如

const age: number=19
const age2=age as string//错误做法

下述代码编译器是不会报错的,但其实错误的,不可使用

const age:number:19 as any
const age4=age2 as string

非空类型断言: !.

引例

interface Person{
    name:string
	age:string
    friend?:{
        name:string
    }
}

const me:Person={
    name:'why',
    age:18,
}

访问属性:当不确定friend属性是否存在时,可以使用可选链:?.
me.friend?.name
但当给属性赋值时,可选链就用不了了
me.friend?.name="kobe"//报错
解决方案1:类型缩小
方案2:非空类型断言
info.friend!.name="zxs"//正确
注:需要确保属性存在且有值时使用

字面量类型

1.基本使用

const name:"why"="why"
let age:19=19

2.多个字面量联合:类似于枚举类型

type Direction="left"|"right"|"up"|"down"
const d1=Direction="left"

例子:封装请求方法

function request(url:string,method:"get"|"post"|"patch"){}

注意:
const info={
    url:"xxx",
    method:"post"
}
request(info.url,info.method) //错误
此时,request只知道info.method是个字符串,并不知道他的字面量

解决方案
1.类型断言  request(info.url,info.method as "post")
2. 强制声明字面量
const info1:{url:string,method:"post"}={
    url:"xxx",
    method:"post"
}
或:细节:
const info1={
    url:"xxx",
    method:"post"
} as const //as const:将整个对象进行字面量推理

类型缩小

typeof

typeof name==="string"

instanceof

in:判读是否有某个属性

if("swim" in sport){}

TS函数类型

function foo(arg:number):number{
    return 124
}

foo本身也是个变量,也有自己的类型

函数类型定义方式:

1.函数类型表达式


const foo:(num1: number)=>number=(arg:number):number=>{
    
}//直接声明:可读性低

type funcType=(num1: number)=>number //使用类型别名声明函数类型表达式
const foo:funcType=(arg:number)=>{}

函数类型参数的格式

ts对传入的函数类型的参数,不校验

type CalcType=(num1:number,num2:number)=>number

function calc(calcFn:CalcType){

}

calc(function(){
    return 1
})正确

calcFn定义时要传入两个参数,可在calc里calcFn作为参数,其里面的传参却不进行校验

调用签名

函数类型的第2种写法

从对象的角度看待函数

举例:

interface Foo{
	name:string
	age:number
    (参数列表):返回值类型
    (num1:number):number
}

const foo:Foo=(num1:number):number=>{
    retrun 123
}
bar.name="aaa"
bar.age=18
bar(123)

开发中如何选择:

1.如果只是描述函数类型本身(可以被调用),使用函数类型表达式

2.如果在描述函数被作为对象可以被调用,同时也有其他属性时,使用函数签名

构造签名

const foo=function(){
	
}注意不能使用箭头函数,因为其无原型,thisconst f=new foo()
class Person{

}
function factory(fn){
	return new fn() //any类型
}

想要给构造函数定义类型
interface ICTORPerson{
    new():Person
}
function factory(fn: ICTORPerson){
	return new fn() //Person类型
}
factory(Person) 正确

函数的可选参数

可选参数要放后面

function foo(x: number,y?: number){
	
}
可选参数y为 number|undefined的联合类型

函数的默认参数

有默认参数时类型注解可以省略

function foo(x: number,y=100){
    
}正确
function foo(x: number,y: number=100){
	
}正确
foo(10,undefined)正确

有默认值的参数,是可以传入undefined,相当于

剩余参数

function foo(...args: (string|number)){
    
}

函数的重载

注意要先定义通用函数,但其不能直接被调用

需求:只能将两个数字、字符串相加
function add(arg1:any,arg2:any){
    return arg1+arg2
}
function add(arg1:number,arg2:number):number 函数重载,无函数体{}

function add(arg1:string,arg2:string):string

自己开发库,工具时使用

联合类型实现函数重载:

function getLength(arg: string|any[]){
    return arg.length
}

注意:开发中能用联合类型实现就用联合类型

对象类型实现函数重载

只要有长度属性

function getLength(arg: {length: number}){
    return arg.length
}

This相关的内置工具

ThisParameterType

function foo(this: {name: string},info:{name: string}){
	console.log(this,info)
}
type FooType=typeof foo;获取已知函数的类型

需求:提取FooType类型中的this的类型

type FooThisType=ThisParameterType<FooType>

OmitThisParameter

获取排除this外的数据类型

type PureFooType=OmitThisParameter<FooType>

ThisType

标记上下文中的this类型:用于绑定上下文this

interface State{
	name:stirng
	age:number
}
interface Store{
	state:State
    eating:()=>void
    running:()=>void
}

const store: Store & ThisType<State>={ 方法2.//修改this指向
    state:{
        name:"zxs",
        age:19
    },
    eating:function(){
        console.log(this.name)此时的this是store,应该用this.state.name
        
    },
    在定义层,如果想使用this.name获取state里的name
    方法1.
    eating:function(this:State){
        console.log(this.name)
    }
    running:function(){
        
    }
}

众所周知在调用函数时可以使用call().apply(),bind()修改this指向

在函数定义时:1.可以传入this参数来修改this指向

  1. Store & ThisType联合类型修改this指向 (pinia使用的就是这个)

TS面向对象

声明类

成员属性都需要在外面声明

class Person{
	name=''
	age=18
	constructor(name:string,age:number){
	this.name=name
	this.age=age
	}
	
}

有个严格属性初始化模式,让属性必须有初始值

继承

extends继承

super()访问父类

类的成员修饰符

public //任何地方可访问
private//私有属性/方法:仅类自身可访问
protected//受保护属性/方法 ,仅在类自身及子类中可访问

想访问就写set,get函数

只读 readonly

class Person{
 	readonly name=''
	age=18
	constructor(name:string,age:number){
	this.name=name
	this.age=age
	}
}

const p=new Person("zxs",19);
p.name="xxx"//错误

getters/setters

私有属性命名建议前面加下划线

用于给外界提供修改,访问私有属性的方法

getter/setter可以对外界传入的值进行拦截/过滤

class Person{
 	private _name: string 
	age=18
	constructor(name:string,age:number){
	this._name=name
	this.age=age
	}
    //setter
    set setName(newVal:string){
        this._name=newVal;
    }
    //getter
    get getName():string{
        return this._name
    }
}

const p=new Person("zxs",19);

p.setName='boke';此时调用的是setter
console.log(p.getName)调用的是getter

参数属性

一种ts语法糖

当类内有很多属性时,constructor内要传入很多参数,就行下面这样

class Person{
 	private _name: string 
	age: number
    height: number
    xxx
    xxx
	constructor(name:string,age:number,height: number,xxxx){
		this._name=name
		this.age=age
        this.height=height
        xxxx
        xxxx
	}
    
}

属性在类内要定义一遍,又要在构造函数里定义一遍,很是麻烦

此时可以使用ts提供的语法糖

示例

class Person{
	constructor(private _name:string,public age:number,public height: number){
		
	}
    set name(newName){
        this._name=newName
    }
    
}

这样就简写了很多

抽象类abstract

抽象类只定义类,属性和抽象方法,不能实例化,其子类必须继承并重写抽象类中的方法

abstract Person{
    abstract sayHello()
}

class ZXS extends Person{
    sayHelllo(){
        console.log("Hello")
    }
}

鸭子类型

ts对类型检测使用鸭子类型:如果走起路来像鸭子,叫起来也像鸭子,那么它就是鸭子

class Person{
    constructor(public name,public age:number){}
}

function printPerson(p:Person){
    console.log(p.name,p.age);
}

printPerson(new Person("zxs",18))这个是正常情况
printPerson("zxs") 错误

printPerson({name:"zxs",age:18})根据鸭子类型,这个也是对的

class Dog{
    constructor(public name,public age:number){}
}
printPerson(new Dog("zxs",18))这个也是对的,鸭子类型

类的作用

1.类可以创建对应的实例对象

2.类本生可以作为这个示例的类型

3.类可以当成有一个构造签名的函数

function factory(ctor: new ()=>void)//表示其需要传入一个构造函数
factory(Person)

对象类型的属性修饰符

type Person={
    //可选属性
	name?:string
    //只读属性
	readonly age:number
}定义了对象类型

interface Ikun{
	name:string
	slogan:string
}

const p:Person={
    name:"zxs",
    age:18
}

索引签名

可以通过字符串索引去获取一个值,也是字符串

一个索引签名的属性类型必须是string或number

interface Collection{
	//索引签名
    //[aaa: number]:string
    [key:string]:string
    length:number //默认length为字符串类型
}

function zxs(collection: Collection){
    console.log(collection["key"]) 
}


zxs({18:"hhh",length:18})

const 

示例

interface Collection{
   	[aaa: number]:string
    length:number //默认length为字符串类型
}

function zxs(collection: Collection){
    for(let i=0;i<collection.length;i++){
        console.log(collection[i]);
    }
}

const array=['abc','aaa','ccc'] //[0]:'abc',[1]:'aaa',[2]:'ccc'符合Collection
const tuple:[string,string]=["zxs","换行"]
zxs(array)
zxs(tuple)

接口继承

1.减少了代码耦合

2.如果使用了第三方库,给我们定义了一些接口

我们可以继承第三方库的接口,进行自定义开发

interface Person{
	name:string
	age:number
}
interface Ikun extends Person{
	slogan:string
}

严格字面量赋值检测

引例:

interface Person{
	name:string
	age:number
}

//
const info:Person={
    name:"zxs",
    age:18,
    
    //多了height属性
    height:1.88 //报错
    
}

换个方法
//奇怪的现象1
const obj={
    name:"zxs",
    age:18,
    height:1.99
}

const info:Person=obj //不会报错

//奇怪的现象2
function getPerson(person: Person){}

getPerson({name:"zxs",age:18,height:1.99})也不会报错

为什么会这样呢?

每个对象字面量最初都被认为是新鲜的(fresh)

在第一次创建对象字面量时,将其分配给一个变量或传递给一个非空目标类型的参数时,会进行严格类型检测

当类型断言或对象字面量类型扩大时,新鲜度会消失

Error when 'extra' properties appear in object literals · Issue #3755 · microsoft/TypeScript (github.com)

Freshness | 深入理解 TypeScript (jkchao.github.io)

抽象类和接口的区别

1.一个类只能继承一个抽象类,而一个类却可以实现多个接口。

2.抽象类可以有构造器,而接口不能有构造器;

3.抽象类里面可以有方法的实现,但是接口完全都是抽象的,不存在方法的实现;

枚举类型

enum Direction{
	UP,
    DOWN,
    LEFT,
    RIGHT
}

const d1:Direction=Direction.UP

function fangx(direction: Direction){
    switch(direction){
        case Direction.LEFT:
            break;
        case Direction.UP:
            break;
        
    }
}
enum Direction{
	UP=0,
    DOWN=1,
    LEFT=2,
    RIGHT=3
}
enum Direction{
	UP=1<<0,//1
    DOWN=1<<1,//2
    LEFT=1<<2,//4
    RIGHT=1<<3//8
}
enum Direction{
	UP=100,
    DOWN,//101
    LEFT,//102
    RIGHT//103
}

enum Direction{
	UP,
    DOWN,
    LEFT="left",
    RIGHT="right"
}

赋值数字,其会自动递增

泛型

类型的参数化

function foo(name: string,age: number){
	
}

上面参数的类型已固定

需求:定义一个函数,将传入的内容然回

function foo(arg){
	return arg
}
如果像上面这么写,就丢失了参数原本的类型
const res1=foo(123) any类型
const res2=foo("abc") any类型

但如果在传参处定义类型时
function foo(arg: string){
	return arg
}
这样就写死了

如果写联合类型
function foo(arg: string|number){
	return arg
}
拿到的结果还是联合类型
const res3=foo("aaa") string|number

所以要使类型参数化---》泛型

function foo<Type>(arg: Type){
	return arg
}

const res=foo<number>(123)  //使用
const res1=foo<string>("aaa")

简写
ts会自动进行类型推导
const res1=foo("aaa") //推导:aaa字面量类型
const res2=foo(111)  //推导:number=111 字面量类型
let  res2=foo(111) //推导 number类型
let res1=foo("aaa") //string类型

ts会自动进行类型推导

const 会推导的更严谨:推导为字面量类型

let则会推导为基本类型

泛型使用场景

useState中使用:

function useState(initialState: T):[T,(newState: T)=>void]{
	let state=initialState
    function setState(newState: T){
        state=newState;
        render();
    }
    return  [state,setState]
}


const [count,setCount]=useState(100)
const [count,setCount]=useState("aaaa")

传入多个类型

常用名称

T: Type

K,V: key,value

E: Element

O: Object

function foo<Type,Element>(arg1: Type,arg2: Element){
	return arg
}

foo(10,10)

泛型接口

interface Info<T>{
	name:string
	age:T
}

interface Info<T=string>{ //设置泛型的默认值
	name:string
	age:T
}

const me:Info<number>{
    name:"zxs",
    age:18
}

类中的泛型

class Point<T>{
	x:T
	y:T
	constructor(...){...}
}

const p1=new Point(10,20)
const p1=new Point("10","20")

泛型约束

有时候希望传入的类型有某些共性

比如,string和array都有length

interface Length{
	length:number
}

//获取传入的内容,其必须要有length属性
function getInfo<T extends Length>(args:T): T{
    return args
}

泛型参数使用约束

//规定参数key必须是obj对象当中的一个键
function getObjectProperty<O,K extends keyof O>(obj: O,key: K){
	return obj[key]
}
相当于:
function getObjectProperty<O,K extends "name"|"age"|"height">(obj: O,key: K){
	return obj[key]
}


const info={
    name:"zxs",
    age:18,
    height:1.88
}

映射类型

拷贝一份类型

手动复制也可以,不过映射类型更简便

type MapPerson<T>={
    [property in keyof T]: T[property]
} 把它看成一个函数

interface Person{
    name:string
    age:number
}

type newPerson=MapPerson<Person>

映射类型的修饰符: readonly ,?

type MapPerson<T>={
    readonly [property in keyof T]?: T[property]
} 

内置工具、类型体操

类型体操:练习ts

type-challenges/README.zh-CN.md at main · type-challenges/type-challenges (github.com)

TS模块化

TS使用的模块化方案:ES Module

什么是一个模块:js 认为没有export的 js 文件都认为是一个脚本,而非模块

如果一个文件,内有任何import,export,但你希望他被作为模块处理:添加 export{}

导入类型

type可加可不加。

import {type Person} form "..."

type关键字可以让非ts编译器如Babel,esbuild 知道什么样的导入可以安全移除

命名空间namespace

namespace zxscode{
	
}

类型查询

类型声明文件 .d.ts

内置类型声明

按f12,在VSCODE 里的 lib.dom.d.ts文件里


浏览器不能识别ts代码,需要将其编译成js代码:webpack热编译、ts-node每次手动编译

webpack自动编译ts

npm i webpack webpack-cli -D
//webpack.config.js
const htmlWebpackPlugin=require("html-webpack-plugin")
const path=require("path")
module.exports={
	mode:"developmnet",
    entry:"./src/index.ts",
    output:{
        path:path.resolve(__dirname,"./dist"),
        filename:"bundle.js"
    },
    resolve:{
        
        extensions:[".ts",".js",".cjs",".json"]
    },
    module:{
        rules:[
            {
                test:/\.ts$/,
                loader:"ts-loader"
            }
        ]
    },
    pligins:[
        new htmlWebpackPlugin({
            template:"./index.html"
        })
    ]
}

开启本地服务器

npm i webpack-dev-serve -D
module.exports={
	mode:"developmnet",
    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"
            }
        ]
    },
    pligins:[
        new htmlWebpackPlugin({
            template:"./index.html"
        })
    ]
}
//package.json
"scripts":{
    "serve":"webpack serve"
}

第三方库类型查询

f12 "axios"

npm i @types/xxxx

自定义类型声明文件

自己写。。。

declare声明模块

TS导入图片

//index.ts
import Img from "./img/zxs.png" 错误

解决方法

//index.d.ts
声明文件模块
declare module "*.png"
declare module "*.jpg"
declare module "*.jpeg"
declare module "*.svg"
declare module "*.vue"
//index.ts
import Img from "./img/zxs.png"
const img=document.....
//webpack.config.js
module:{
    test:/\.(png|jpe?g|svg|gif)$/
    type:"asset/resource"
}

TS与CDN引入模块

声明成模块不合适

要声明命名空间

declare namespace ${
	export function ajax(settings: any):any
}

tsconfig.json

作用1:让tsc编译时,知道如何去编译ts代码进行类型检测

作用2:让编辑器可以代码提示


{
    "compilerOptions":{
        
    },
    "files":[],//指定哪些ts文件需要编译
    "include":["./src/**/*.ts","./types/*.d.ts"]
}

image-20230106234528774

TypeScript: TSConfig 参考 - 所有 TSConfig 选项的文档 (typescriptlang.org)

Axios封装

//request/index.ts
import axios from "axios"
import type {AxiosInstance,AxisoRequestConfig} from "axios"
class Request{
    instance: AxiosInstance
	constructor(config: AxisoRequestConfig){
		this.instance=axios.create(
            //{baseURL:"xxx",
            //timeout:1000,
            //headers:{}}
            config
        )
        //拦截器loading//token/修改配置
        this.instance.intercepteors.request.use((config)=>{
            //全局请求成功拦截
            return config
        },err=>{
            //全局shi'bai
            return err
        })
		this.instance.intercepteors.response.use(res=>{
            //全局响应成功拦截
            return res
        },err=>{
            //全局失败响应拦截
            return err
        })
        
	}
    request(config: AxisoRequestConfig){
       	return this.instance.request(config)
    }
    get(){}
    
}	
export default Request

局部拦截器

//使用
//单个模块的实例
export const hyRequest2=new Request({
	baseURL:"xxx",
    timeout:100,
    interceptors:{
        requestSuccessFn:function(){},
        requestFailureFn:function(){},
        responseSuccessFn:function(){},
        responseFailureFn:function(){}
        
    }
})

在需要的模块的 config 对象传参里自定义传入局部拦截器,再在配置里处理局部拦截器,以此达到局部拦截目的

在request的构造函数中,config是AxisoRequestConfig类型,无interceptors参数,所以需要对其进行扩展

//request/index.ts
import axios from "axios"
import type {AxiosInstance,AxisoRequestConfig} from "axios"

//扩展
interface HYInterceptors{
        requestSuccessFn:(config:AxisoRequestConfig)=>AxisoRequestConfig
		requestFailureFn:(err:any)=>any
		responseSuccessFn:(res:AxiosResponse)=>AxiosResponse
        responseFailureFn:(err:any)=>any        
    }
interface HYRequestConfig extends AxisoRequestConfig{
    interceptors?:HYInterceptors
}

class Request{
    instance: AxiosInstance
	constructor(config: AxisoRequestConfig){
		this.instance=axios.create(
            //{baseURL:"xxx",
            //timeout:1000,
            //headers:{}}
            config
        )
        //拦截器loading//token/修改配置
        this.instance.interceptors.request.use((config)=>{
            //全局请求成功拦截
            return config
        },err=>{
            //全局shi'bai
            return err
        })
		this.instance.interceptors.response.use(res=>{
            //全局响应成功拦截
            return res
        },err=>{
            //全局失败响应拦截
            return err
        })
        //针对特定实例添加拦截器
        if(config.interceptors){
            //局部请求拦截
            this.instance.interceptors.request.use(
                config.interceptors.requestSuccessFn,
                config.interceptors.requestFailureFn
            )
            //局部响应拦截
            this.instance.interceptors.response.use(
                config.interceptors.responseSuccessFn,
                config.interceptors.responseFailureFn
            )
        }
        
	}
    request(config: AxisoRequestConfig){
       	return this.instance.request(config)
    }
    get(){}
    
}	
export default Request

单次请求拦截器

只拦截单次的request,get...

//request/index.ts
import axios from "axios";
import type { AxiosInstance, AxisoRequestConfig } from "axios";

//扩展
interface HYInterceptors {
	requestSuccessFn: (config: AxisoRequestConfig) => AxisoRequestConfig;
	requestFailureFn: (err: any) => any;
	responseSuccessFn: (res: AxiosResponse) => AxiosResponse;
	responseFailureFn: (err: any) => any;
}
interface HYRequestConfig extends AxisoRequestConfig {
	interceptors?: HYInterceptors;
}

class Request {
	instance: AxiosInstance;
	constructor(config: AxisoRequestConfig) {
		this.instance = axios.create(
			//{baseURL:"xxx",
			//timeout:1000,
			//headers:{}}
			config
		);
		//拦截器loading//token/修改配置
		this.instance.interceptors.request.use(
			(config) => {
				//全局请求成功拦截
				return config;
			},
			(err) => {
				//全局shi'bai
				return err;
			}
		);
		this.instance.interceptors.response.use(
			(res) => {
				//全局响应成功拦截
				return res;
			},
			(err) => {
				//全局失败响应拦截
				return err;
			}
		);
		//针对特定实例添加拦截器
		if (config.interceptors) {
			//局部请求拦截
			this.instance.interceptors.request.use(
				config.interceptors.requestSuccessFn,
				config.interceptors.requestFailureFn
			);
			//局部响应拦截
			this.instance.interceptors.response.use(
				config.interceptors.responseSuccessFn,
				config.interceptors.responseFailureFn
			);
		}
	}
	//单次请求
	request(config: HYRequestConfig) {
		if (config.interceptors?.requestSuccessFn) {
			config = config.interceptors.requestSuccessFn(config);
		}
		return new Promise<AxiosResponse>((resolve, reject) => {
			this.instance
				.request(config)
				.then((res) => {
					//单次响应拦截处理
					if (config.interceptors?.responseSuccessFn) {
						res = config.interceptors.responseSuccessFn(res);
					}
					resolve();
				})
				.catch((err) => {
					reject(err);
				});
		});
	}
	get() {}
}
export default Request;

使用

hyRequest2.request({
	url:"/home",
	ingterceptors:{
		requestSuccessFn(config)=>{
			console.log("/home单次请求拦截成功");
			retrun config
		},reponseSuccessFn(res)=>{
			console.log("/home单次响应拦截成功")
		}
	}
})

上述res类型为unknown,其任何操作都会报错

原因:.then是Promise的then,

解决方法:

  1. res as any

  2. new Promise()

//request/index.ts
import axios from "axios";
import type { AxiosInstance, AxisoRequestConfig } from "axios";

//扩展
interface HYInterceptors<T=AxiosResponse> { //修改
	requestSuccessFn: (config: AxisoRequestConfig) => AxisoRequestConfig;
	requestFailureFn: (err: any) => any;
	responseSuccessFn: (res: T) => T;//修改
	responseFailureFn: (err: any) => any;
}
interface HYRequestConfig<T> extends AxisoRequestConfig {
	interceptors?: HYInterceptors<T>;//修改
}

class Request {
	instance: AxiosInstance;
	constructor(config: AxisoRequestConfig) {
		this.instance = axios.create(
			//{baseURL:"xxx",
			//timeout:1000,
			//headers:{}}
			config
		);
		//拦截器loading//token/修改配置
		this.instance.interceptors.request.use(
			(config) => {
				//全局请求成功拦截
				return config;
			},
			(err) => {
				//全局shi'bai
				return err;
			}
		);
		this.instance.interceptors.response.use(
			(res) => {
				//全局响应成功拦截
				return res;
			},
			(err) => {
				//全局失败响应拦截
				return err;
			}
		);
		//针对特定实例添加拦截器
		if (config.interceptors) {
			//局部请求拦截
			this.instance.interceptors.request.use(
				config.interceptors.requestSuccessFn,
				config.interceptors.requestFailureFn
			);
			//局部响应拦截
			this.instance.interceptors.response.use(
				config.interceptors.responseSuccessFn,
				config.interceptors.responseFailureFn
			);
		}
	}
	//单次请求
	request(config: HYRequestConfig<T>) { //
		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);
					}
					resolve(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"})
    }
}
export default Request;

hyRequest2.request<>({
	url:"/home",
	ingterceptors:{
		requestSuccessFn(config)=>{
			console.log("/home单次请求拦截成功");
			retrun config
		},reponseSuccessFn(res)=>{
			console.log("/home单次响应拦截成功")
		}
	}
})

难点:

1.拦截器精细控制:全局拦截器,实例拦截器,单次请求拦截器

2.响应结果的类型处理(泛型)

在项目中用ts封装axios,一次封装整个团队受益😁 - 掘金 (juejin.cn)

条件类型

用来帮助我们描述输入类型和输出类型之间的关系

写法类似于三元表达式

type IDtype=number|string
//判断number是否extends IDtype
type ResType=number extends IDtype? true :false

例子:函数重载 简便写法

function sum(num1,num2){
    return num1+num2
}

function sum<T extends number|string>(num1:T,num2:T):T extends number? number : string

在条件类型中推断

infer:从正在比较的类型中推断类型,然后在true分支里引用该推断结果

如:现有一个函数类型,想要获取到一个函数的参数类型和返回值类型

type Fn=(num1:number,num2:number)=>number

function foo(){
    return "aaa"
}

//获取返回值的类型 RetrurnType<xxx>
type ResType=ReturnType<Fn> //number
type ResType1=ReturnType<typeof foo>  //string

原理:

type myReturnType<T extends (...args:any[])=> any> = T extends (...args:any[])=>infer R? R:never

分发类型

Partial

用于构造一个Type下面所有属性都设置为可选类型

interface Person{
    name:string
    age:number
    slogan?:string
}

type Me=Partial<Person>
//name?:string
//age?:number
//slogan?:string
原理
type MyPartial<T>={
	[P in keyof T]?:T[P]
}

Required

与Partial相反,所有属性都变成必选

原理
type MyPartial<T>={
	[P in keyof T]-?:T[P]
}

Readonly

只读

Record<Keys,Type>

用于构造一个对象类型,他所有key都是keys类型,所有value都是Type类型

type iKun={
    name:string
    age:number
    slogan?:string
}
type city="上海"|"北京"|"长沙"
type IKuns=Record<city,Ikun>

const me:IKuns={
    "上海":{
        name:'zxs',
        age:18,
    },
    "北京":{
        name:'zxss',
        age:20,
    },
}
原理

type MyRecord<Keys extends keyof any,T>={
	[P in Keys]:T
}

Pick<T,Keys>

从T中挑选出一些属性Keys组成新类型

type iKun={
    name:string
    age:number
    slogan?:string
}
type aaa=Pick<IKun,"name"|"age">
原理
type MyPick<T,K extends keyof T>={
	[P in K]:T[P]
}

Omit<T,K>

从T中过滤K,与Pick相反

原理
type MyOmit<T,K extends keyof T>={
	[P in keyof T as P extends K?never:P]:T[P]
}

Exclude<UnionType,ExcludedMenmbers>

从UnionType联合类型中排除ExcludedMenmbers

type city="上海"|"北京"|"长沙"
type aaa=Exclude<city,"长沙">
原理
type MyExclude<T,E>=T extends E ? never:T

Extract<UnionType,ExcludedMenmbers>

和exclude相反

原理
type MyExclude<T,E>=T extends E ? T:never

NonNullable

移除null和undefined

InstanceType

拿T里的构造函数的实例的类型