文章目录
1. axios的AxiosResponse分析
import { AxiosResponse } from 'axios' //引入
//在axios的response中的使用。
service.interceptors.response.use(
(function(response: AxiosResponse<Recordable>) {
//...
}
)
接下来可以看下这个接口
export interface AxiosResponse<T = any, D = any> {
data: T;
status: number;
statusText: string;
headers: AxiosResponseHeaders;
config: AxiosRequestConfig<D>;
request?: any;
}
然后是:Recordable
declare type Recordable<T = any, K = string> =
Record<K extends null | undefined ? string : K, T>
这个类型是我自己定义的(忘记哪里抄的了,好像是官网给出的方案)
然后是:Record
,这个是ts内置的
type Record<K extends keyof any, T> = {
[P in K]: T;
};
从最顶层开始分析
1.1 Record的展开讲解
type Record<K extends keyof any, T> = {
[P in K]: T;
};
讲这个类型之前,先讲下其他
1.1.1 keyof
索引类型查询:
-
通过索引类型查询能够获取给定类型中的属性名类型。
-
索引类型查询的结果是由字符串字面量类型构成的联合类型,该联合类型中的每个字符串字面量类型都表示一个属性名类型。索引类型查询的语法如下所示:
keyof Type
keyof是关键字,Type表示任意一种类型。
实例:
interface Point {
x: number;
y: number;
}
type T = keyof Point; // 'x' | 'y'
let t:T = 'x'
所以keyof any
等同于string | number | symbol
1.1.2 type 类型别名
类型别名与接口相似,它们都可以给类型命名并通过该名字来引用表示的类型:
-
区别一:类型别名能够表示非对象类型,而接口则只能表示对象类型。因此,当我们想要表示原始类型、联合类型和交叉类型等类型时只能使用类型别名。示例如
type NumbericType = number | bigint;
-
区别二:接口可以继承其他的接口、类等对象类型,而类型别名则不支持继承
interface Shape { name:string; } interface Circle extends Shape { radius: number; }
若要对**类型别名实现类似继承的功能,**则需要使用一些变通方法。例如,当类型别名表示对象类型时,可以借助于交叉类型来实现继承的效果。
type Shape = {name:string};
type Circle = Shape & {radius:number};
function foo(circle:Circle) {
const name = circle.name;
const radius = circle.radius;
}
此例中的方法只适用于表示对象类型的类型别名。如果类型别名表示非对象类型,则无法使用该方法。
交叉类型的会详细介绍:
-
区别三:接口名总是会显示在编译器的诊断信息(例如,错误提示和警告)和代码编辑器的智能提示信息中,而类型别名的名字只在特定情况下才会显示出来。
-
区别四:接口具有声明合并的行为,而类型别名则不会进行声明合并
interface A { x:number } interface A { y:number }
最终合并如下:
interface A { x:number; y:number }
1.1.3 extends 泛型约束
除了接口中使用extends。在泛型约束中extends也是一种应用
-
形式类型参数-泛型约束声明
-
基约束
-
形式类型参数-泛型约束声明
在泛型的形式类型参数上允许定义一个约束条件,它能够限定类型参数的实际类型的最大范围。我们将
类型参数的约束条件称为泛型约束。
TypeParameter extends ConstraintType
该语法中,TypeParameter表示形式类型参数名;extends是关键字;ConstraintType表示一个类型,
该类型用于约束TypeParameter的可选类型范围。
interface Point {
x:number;
y:number;
}
function identity<T extends Point>(x:T):T {
console.log(x)
return x;
}
identity({x:0,y:0}) // {x:0,y:0}
identity({x:0,y:0,z:'0'}) // {x:0,y:0,z:'0'}
// identity({x:0}) ,缺少y,编译错误
对于一个形式类型参数,可以同时定义泛型约束和默认类型,但默认类型必须满足泛型约束
function identity<T extends number = 0|1>(){}
identity<0>()
在泛型约束中,约束类型允许引用当前形式类型参数列表中的其他类型参数。例如,下例中形式类型参
数U引用了在其左侧定义的形式类型参数T作为约束类型:
<T,U extends T>
下例中,形式类型参数T引用了在其右侧定义的形式类型参数U:
<T extends U, U> #一个被U限制的T,还有一个U,两个泛型。
需要注意的是,一个形式类型参数不允许直接或间接地将其自身作为约束类型,否则将产生循环引用的
编译错误。例如,下例中的泛型约束定义都是错误的:
<T extends T> //错误
<T extends U,U extends T>//错误
- 基约束
本质上,每个类型参数都有一个基约束(Base Constraint),它与是否在形式类型参数上定义了泛型约束无关。类型参数的实际类型一定是其基约束的子类型。对于任意的类型参数T,其基约束的计算规则有三个。
-
规则一:如果类型参数T声明了泛型约束,且泛型约束为另一个类型参数U,那么类型参数T的基约束为类型参数U。示例如下:
<T extends U> //类型参数T的基约束为类型参数U
-
规则二: 如果类型参数T声明了泛型约束,且泛型约束为某一具体类型Type,**那么类型参数T的基约束为类型Type。**示例如下:
<T extends boolean>
-
规则三: 如果类型参数T没有声明泛型约束,那么类型参数T的基约束为空对象类型字面量“{}”。除了undefined类型和null类型外,其他任何类型都可以赋值给空对象类型字面量。
<T>
常见错误:
interface Point {
x:number;
y:number;
}
function f<T extends Point>(args: T):T {
return {x:0,y:0} //报错
}
此例第7行,第一感觉可能是这段代码没有错误,因为返回值“{ x: 0, y: 0 }”的类型是泛型约束Point类型的子类型。实际上,这段代码是错误的,因为f函数的返回值类型应该与传入参数arg的类型相同,而不能仅满足泛型约束。
interface Point {
x:number;
y:number;
}
function f<T extends Point>(args: T):T {
return {x:0,y:0} as T
}
1.1.4 [P in K]: T
映射对象类型:
- 映射对象类型是一种独特的对象类型,它能够将已有的对象类型映射为新的对象类型。
- 例如,我们想要将已有对象类型T中的所有属性修改为可选属性,那么我们可以直接修改对象类型T的类
型声明,将每个属性都修改为可选属性。除此之外,更好的方法是使用映射对象类型将原对象类型T映射
为一个新的对象类型T‘,同时在映射过程中将每个属性修改为可选属性。
这里只讲解:映射对象类型声明
映射对象类型是一个类型运算符,它能够遍历联合类型并以该联合类型的类型成员作为属性名类型来构
造一个对象类型。映射对象类型声明的语法如下所示:
{ readonly [P in K]?:T }
在该语法中,readonly是关键字,表示该属性是否为只读属性,该关键字是可选的;“?”修饰符表示该属
性是否为可选属性,该修饰符是可选的;in是遍历语法的关键字;K表示要遍历的类型,由于遍历的结果
类型将作为对象属性名类型,因此类型K必须能够赋值给联合类型“string | number |symbol”,因为
**只有这些类型的值才能作为对象的键;**P是类型变量,代表每次遍历出来的成员类型;T是任意类型,表
示对象属性的类型,并且在类型T中允许使用类型变量P。
映射对象类型的运算结果是一个对象类型。
type K = 'x' | 'y';
type T = number;
type MapddedObjectType = {
readonly [P in K]?:T;
}
/*等同。
type MapddedObjectType = {
readonly x?: number | undefined;
readonly y?: number | undefined;
}
*/
1.1.5 汇总
type Record<K extends keyof any, T> = {
[P in K]: T;
};
-
Record的键名可以是
string | number | symbol
三种类型其中一种,比如{ "xx":T; 11:T; }
他的的键值是T,泛型。提供了上一层扩展的能力。
1.2 Recordable的展开讲解
declare type Recordable<T = any, K = string> =
Record<K extends null | undefined ? string : K, T>
1.2.1 declare
.d.ts 文件中的顶级声明必须以 “declare” 或 “export” 修饰符开头。
通过declare声明的类型或者变量或者模块,在include包含的文件范围内,都可以直接引用而不用去import或者import type相应的变量或者类型。
1.2.2 条件类型
条件类型与条件表达式类似,它表示一种非固定的类型。条件类型能够根据条件判断从可选类型中选择其一作为结果类型。
条件类型的定义:
T extends U ? X: Y
在该语法中,extends是关键字;T、U、X和Y均表示一种类型。若类型T能够赋值给类型U(T是U的子类型),则条件类型的结果为类型X,否则条件类型的结果为类型Y。条件类型的结果类型只可能为类型X或者类型Y。
实例:
//string
type T0 = true extends boolean ? string : number;
// number
type T1 = string extends boolean ? string : number;
此例中的条件类型实际意义很小,因为条件类型中的所有类型都是固定的,因此结果类型也是固定的
实例2:
type TypeName<T> = T extends string
? 'string'
: T extends number
? 'number'
: T extends boolean
? 'boolean'
: T extends undefined
? 'undefined'
: T extends Function
? 'function'
: 'object';
type T0 = TypeName<'a'> //type T0 = "string"
type T1 = TypeName<{}> //type T1 = "object"
还有一种就是分布式条件类型。这里略过。
1.2.3 总结
/*
type Record<K extends keyof any, T> = {
[P in K]: T;
};
*/
declare type Recordable<T = any, K = string> =
Record<K extends null | undefined ? string : K, T>
//等同
type Recordable<T = any, K = string> =
{ [P in K extends null | undefined ? string : K]: T; }
全局声明一个类型 Recordable
,K=string. 所以基本上可以得出
Recordable<T> = {
[string]:T;
}
1.3 AxiosResponse<Recordable>
展开讲解
import { AxiosResponse } from 'axios' //引入
//自定义的拦截器:axios的response中的使用。
service.interceptors.response.use(
(function(response: AxiosResponse<Recordable>) {
//...
}
)
//axios源码
export interface AxiosResponse<T = any, D = any> {
data: T;
status: number;
statusText: string;
headers: AxiosResponseHeaders;
config: AxiosRequestConfig<D>;
request?: any;
}
这个Recordable
占了T的位置,所以影响到了data,等同如下
//AxiosResponse<Recordable>
interface AxiosResponse<Recordable> {
data: {
[string]:T;
}
}
也就是说data的键名被约束了,只能是string.