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类型
应用场景:
-
开发中很少实际去定义never类型,某些情况会自动类型推导出never
-
开发框架,工具时可能会用到
-
封装类型工具时,可以使用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(){
}注意不能使用箭头函数,因为其无原型,this等
const 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指向
- 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)
在第一次创建对象字面量时,将其分配给一个变量或传递给一个非空目标类型的参数时,会进行严格类型检测
当类型断言或对象字面量类型扩大时,新鲜度会消失
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"]
}
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,
解决方法:
-
res as any
-
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里的构造函数的实例的类型