Typescript当中的T,K,V到底是个啥 有时候,我们看到下面的代码,当然,这里是简单例子来说 1 2 3 function identity <T> (value :T) : T { return value; }
其实泛型就是使用字母来代替将要接收的类型,这里的”T”是代表类型的缩写,表示对将要接收类型的一个占位符,占位符可以是任意字母,下面是一些常用的占位符
T(Type) 表示类型K(Key) 表示对象中键的类型V(value) 表示对象中值的类型E(Element) 表示元素类型如果在函数中使用了泛型,那么我们可以在使用的时候指明类型,也可以不显式指明类型
1 2 3 4 5 6 7 8 9 function identity <T , U>(value : T ,message : U) : T { console .log (message) return value; } console .log (identity (20 ,'动感超人' ));console .log (identity<number ,string >(20 ,'动感超人' ))
declare 假如我们在html当中引入了jquery插件,那么就会在全局当中增加一个关键字$
,此时如果我们在ts文件当中的书写关键字$
就会发现ts会提示找不到$,ts2304
,也就是说ts不认识这个全局变量$
所以我们可以使用declare
来定义这个全局变量declare const $ = xxxx
,这样子ts就认识这个全局变量$了
注意点
declare
声明不包含具体的实现,也就是说我们只是声明,不做具体处理declare可以定义全局变量,全局函数,全局枚举,全局类等 你看到的xxx.d.ts就是用于放置声明文件的 declare和一些声明文件查询:@地址
疑问 为什么我们可以在ts中直接写Math
,JSON
,Object
这些全局对象呢?那是英文typescript已经在文件当中声明了 在vie当中,我们可以在node_modules/vite/client.d.ts
当中就可以看到declare的声明,部分代码如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 declare module '*.module.css' { const classes : CSSModuleClasses export default classes } declare module '*.css' { const css : string export default css } declare module '*.jpg' { const src : string export default src }
vite为什么要这样子做呢,因为如果它不这样子做,那么我们在使用下面代码就会报错 1 2 3 4 5 import css from "./file.css" ;import logo from "./abao.jpg" ;
从ts2.0开始,declare支持通配符了,就如上声明一样使用了通配符 any类型和unknown类型 any
:我不在乎它的类型
unknown
:我不知道它的类型(可以理解为类型安全的any),使用了unknown,必须要自己进行类型检测后才可以对变量进行操作,否则会报警告或错误
对于下列函数,如果是使用any
类型,是不会有任何报错提示的
1 2 3 4 5 6 7 8 function invokeCallBack (callback:any ){ try { callback (); }catch (e){ console .log (e) } } invokeCallBack (1 )
而对于我们使用了unknown
,则会提示TS2571: Object is of type 'unknown'.
1 2 3 4 5 6 7 8 9 function invokeCallBack (callback:unknown ){ try { callback (); }catch (e){ console .log (e) } } invokeCallBack (1 )
所以对于unknown来说,我们在使用前就必须要判断是否可以执行后才可以没有警告 1 2 3 4 5 6 7 8 9 10 function invokeCallBack (callback:unknown ){ try { if (typeof callback === 'function' ){ callback (); } }catch (e){ console .log (e) } } invokeCallBack (1 )
需要注意的是,unknown类型的变量只可以赋值给unknown类型或者是any类型
typescript当中的类型 never 是空集,所以never类型无法被其他类型所赋值1 2 3 4 5 let num : never = 123 ;let name : never = '超人' ;
interface
接口定义对象类型,可以使用extends进行扩展1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 interface Vector1D {x :number } interface Vector3D { x :number , y :number , } interface Vector2D extends Vector1D {y :number } interface Vector3D { x :number , y :number , z :number } interface Vector3D extends Vector2 d {z :number };
type和interface的异同 前置知识 type 类型别名 interface 接口 interface只能定义对象类型 定义接口类型时,可以同时声明对象身上的属性和方法 相同点 类型别名和接口都可以用来描述对象或函数 1 2 3 4 5 type Point = { x : number y :number } type SetPoint = (x:number ,y:number ) => void ;
1 2 3 4 5 6 7 8 interface Point { x : number ; y : number ; } interface SetPoint { (x : number , y : number ): void ; }
类型别名和接口都支持扩展类型别名扩展使用&
交叉运算符进行合并运算,注意,交叉类型中的交叉,并不是指两个类型的交集,而是并集 接口扩展使用关键字extends
1 2 3 4 5 6 7 8 9 10 11 type Animal = { name : string ; }; type Bear = Animal & { honey : boolean ; }; const bear : Bear = { name : "熊大" , honey : false , };
1 2 3 4 5 6 7 8 9 10 11 interface Animal { name : string ; } interface Bear extends Animal { honey : boolean ; } const bear : Bear = { name : "熊大" , honey : false , };
类型别名和接口都支持相互扩展,但需要注意的是接口只支持使用extends
关键字来继承,类型也只支持使用&
来完成扩展 1 2 3 4 5 6 7 8 9 10 type Animal = { name : string ; }; interface Bear extends Animal { honey : boolean ; } const bear : Bear = { name : "熊大" , honey : false , };
1 2 3 4 5 6 7 8 9 10 interface Animal { name : string ; } type Bear = Animal & { honey : boolean ; }; const bear : Bear = { name : "熊大" , honey : false , };
不同点 类型别名可以为基本类型,联合类型或元组类型定义别名,接口不行 1 2 3 type MyNumber = number ; type StringOrNumber = string | number ; type Point = [number , number ];
同名接口会自动合并,而类型别名不会 1 2 3 4 5 6 7 8 9 10 11 12 interface User { name : string ; } interface User { age : number ; } let user : User = { name : "李白" , age : 1000 , }; user.name ; user.age ;
1 2 3 4 5 6 7 type User = { name : string ; }; type User = { age : number ; };
类型别名和接口的一些使用场景 使用类型别名的场景
定义基本类型的别名时,使用type 定义元组类型时,使用type 定义函数类型时,使用type 定义联合类型时,使用type 定义映射类型时,使用type 使用接口的场景
需要利用接口自动合并特征的时候,使用interface 定义对象类型且无需使用type的时候,调用interface 索引签名和Record内置工具类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface selfName1 { [key :string ] : string } const test1 :selfName1 = { name :'李白' , hobby :'吃饭' , } const selfName2 :{[key :string ] : string } = { name :'李白' , hobby :'吃饭' , age :1000 , }
需要注意的是KeyType
只能为string
或者 number
或者symbol
不能为其他的值 1 2 3 4 5 interface selfName3 { [key :boolean] : string }
使用索引签名也可以和别的已知的key,value使用 1 2 3 4 5 6 7 8 9 interface Options { [key :string ]:string | number | boolean , timeout :number , } const option :Options = { timeout :1000 , errorMessae :'The request timed out!' , isSuccess :false , }
1 2 3 4 5 6 7 8 9 10 11 interface PropChangeHandler { [key :`${string } Changed` ]: () => void ; } let handlers :PropChangeHandler = { idChanged : () => {}, nameChanged : () => {}, ageChange : () => {}, }
Record内置工具类型和索引签名 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type User1 = { [key :"id" ]:string , } type User2 = { [key :"id" | "name" ] :string } interface PropChangeHandler { [key :`${string } Changed` ]: () => void ; }
1 2 3 4 5 6 7 8 9 10 type User3 = Record <"id" , string >const a :User3 = { id :'2tjawjtiaowt' , } type User4 = Record <'id' | 'name' , string >;const b :User4 = { id :'2tjawjtiaowt' , name :'动感超人' }
原来映射类型是这样子工作的 ts的一些工具类型,比如说Pick
,就是从某一个类型当中挑选一部分 ts中的工具类型操作的是类型 ,而js当中工具类型操作的是值 ,下面用ts的pick用js来做下解释,调用ts工具类型(类似函数)使用的是尖括号,js函数则是小括号(一句话,ts用尖括号,js用小括号) 1 2 3 4 5 6 7 8 9 10 11 12 type Pick <T,K extends keyof T> = { [P in K]:T[P] } function Pick (obj,keys) { const result = {}; for (const key of keys){ result[key] = obj[key]'' } return result; }
ts内置工具类型中的keyof操作符有啥用 首先来说下js当中的Object.keys
函数作用,Object.keys
会返回对象身上所有可枚举key组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致。 1 2 3 4 5 6 7 8 const object1 = { a : 'somestring' , b : 42 , c : false }; console .log (Object .keys (object1));
那么ts当中的keyof
也是,返回对象身上key值组成的联合类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Person { year :number = 2022 ; hobby :string = '吃饭' ; } type P0Types = keyof Person ; const P01 :P0Types = 'year' ;const P02 :P0Types = 'hobby' ;interface Person1Inter { id :number, name :string, } type P1Types = keyof Person1Inter ; const P11 :P1Types = 'id' ;const P12 :P1Types = 'name' ;enum HttpMethods { Get , Post , } type Methods = keyof typeof HttpMethods ; const P21 :Methods = 'Get' ;const P22 :Methods = 'Post' ;
1 2 3 4 5 6 7 type K1 = keyof boolean ;type K2 = keyof number ;type K3 = keyof any ; string | number | symbol
ts为什么keyof typeof可以拿到枚举的联合类型 ? 1 2 3 4 5 6 7 8 export enum ab { 'a' , 'b' } type uni = keyof typeof ab
TypeScript 中分“类型”和“值”,类型是 TypeScript 认的,一般编译后会消失(不存在于 JS 中)。枚举是比较特殊的定义,虽然定义成类型,但实际是值,它在编译成 JS 之后是一个对象。
TypeScript 中的枚举还分情况,有数值型枚举,也有字符串型枚举,还有混合型的……不讨论复杂了,这里就说数值型的。
1 2 3 4 5 6 enum Hello { A, B } type X = keyof Hello ;
你猜 X
是什么呢?你会发现它包含 toFixed
和 toPrecision
等,是不是感觉像是个 Number 类型的 Key 呢?
再来看看 Number 类型的 …… 果然一样
如果不加 Exclude
运算,会看到 keyof Number
看不到键列表
想想,实际上也是,如果这样使用
1 const a : Hello = Hello .A ;
a
的值实际上是一个 Number(仅数值型枚举的情况)
所以 TypeScript 中需要使用 typeof Hello
来取实际的枚举类型(不然就是 Number 的子类型),实际上它是一个接口。
这个类型取出来之后,枚举值名称是被当作类型的 Key 的,所以可以用 keyof
把键值取出来。
ts的映射和泛型 ts的映射个人觉得有点像是js当中的map吧,操作都是传入a,经过处理后返回b
语法:
一些工具库,比如说Partial
,Required
,Pick
就是通过映射来实现的
比如现在有一个需求,需要把这个类型全部改为可选的,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 type User = { name : string , password : string , address : string , phone : string , } type User1 = { name?:string , password?:string , address?:string , phone?:string , } type selfPartial<T> = { [K in keyof T]?:T[k] }
在映射的过程当中,可以通过添加+
,-
来添加.移除修饰符(+(加号为默认)) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type Item ={ a : string ; b : number ; c : boolean }; type T1 = { [P in 'x' | 'y' ]: number };type T2 = { [P in 'x' | 'y' ]: P };type T3 = { [P in 'a' | 'b' ]: Item [P] }type T4 = { [P in keyof Item ]: Item [P] }
ts条件类型Conditional Types ts的条件类型和js当中的三元运算符差不多
还是一句话,ts操作的是类型,js操作的是值
语法
T extends U ? X : Y
T,U,X,Y都是类型占位符 解释:当类型T可以赋值给类型U的时候,就返回X,否则就返回Y 先来看一个简单的例子
type I2 = IsString<any>
://输出类型为boolean,是因为any这二个值都可以满足,所以就为boolean1 2 3 4 5 6 7 type IsString <T> = T extends string ? true :false ;type I0 = IsString <number >;type I1 = IsString <'abc' >;type I2 = IsString <any >;type I3 = IsString <never >;
除了判断单一类型之外,利用条件类型和条件链,我们还可以同时判断多种类型 当传入any的时候,会返回联合类型,因为都符合,所以在ts的三元是一直运算下去的 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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 <string > type T1 = TypeName <'a' > type T2 = TypeName <true > type T3 = TypeName <() => void > type T4 = TypeName <string []> type T5 = TypeName <any >; function example (… ) { return condition1 ? value1 : condition2 ? value2 : condition3 ? value3 : value4; } function example (… ) { if (condition1) { return value1; } else if (condition2) { return value2; } else if (condition3) { return value3; } else { return value4; } }
如果传入的是联合类型会发生什么结果 1 2 3 4 5 6 7 8 9 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 T10 = TypeName <string | (() => void )>;type T11 = TypeName <string | string [] | undefined >
为什么T10 和 T11类型返回的是联合类型呢,因为TypeName属于分布式条件类型,在条件类型中,如果被检查的是一个”裸”类型参数,就是没有被数组,元组,或者Promise等包装过,则该条件类型被称为分布式条件类型 ,对于分布式条件类型来说,当传入的被检查类型是联合类型的时候,在运算过程中就会被依次运算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type Naked <T> = T extends boolean ? "Y" : "N" ; type T0 = Naked <number | boolean>; type WrappedTuple <T> = [T] extends [boolean] ? "Y" : "N" ; type WrappedArray <T> = T[] extends boolean[] ? "Y" : "N" ; type WrappedPromise <T> = Promise <T> extends Promise <boolean> ? "Y" : "N" ; type T1 = WrappedTuple <number | boolean>; type T2 = WrappedArray <number | boolean>; type T3 = WrappedPromise <number |boolean>; type T4 = WrappedTuple <true | false >; type T5 = WrappedArray <true | false >;
由以上结果可知,如果条件类型中的类型参数 T 被包装过 ,该条件类型就不属于分布式条件类型 ,所以在运算过程中就不会被分解成多个分支 。说通俗点就是分布式条件运算返回值为联合类型,会对每一个值进行判断 非分布式条件运算就是对整体每一个值进行判断,返回值为普通的类型 Exclude内置工具类型执行流程 ts内置工具类型Exclude
作用是传入T,U,将这两个相同的值消除,不同的值提取(利用了条件类型) 1 2 3 4 5 6 7 type Exclude <T,U> = T extends U ? never : T;type T4 = Exclude <'a' | 'b' | 'c' ,'a' | 'b' >;type T5 = Exclude <'a' | 'b' ,'a' | 'b' >;
ts中的infer 现在有一个需求,获取数组当中的类型,和函数返回的类型 1 2 3 4 5 6 7 type T0 = string [];type T1 = () => string ;type UnpackedArray <T> = T extends (infer U)[] ? U : T;type T0Result = UnpackedArray <T0 >;
infer
是什么呢 ?
T extends (infer U)[] ? U : T
:是条件类型的语法,而extends字句中的infer U 引入了一个新的类型变量U,用于存储被推断的类型,可以理解为后面这个U将用于存储类型 infer
注意的点?
infer
只能在条件类型extends
字句中使用,同时infer
声明的类型变量只能在条件类型的true分支中可用1 2 3 type Wrong1 <T extends (infer U)[]> = T[0 ] type Wrong2 <T> = (infer U)[] extends T ? U : T type Wrong3 <T> = T extends (infer U)[] ? T : U
那么infer
到底要怎么用呢?
一句话:要判断的是什么样子,infer
就长什么样子 我们再来看看怎么判断函数的返回类型
1 2 3 4 5 6 7 8 9 type T1 = () => string ;type T0 = string [];type UnpackedFunction <T> = T extends (...args :any []) => (infer U) ? U : T;type T1Types = UnpackedFunction <T1 >type Test = UnpackedFunction <T0 >
当遇到函数重载的场景,TypeScript 将使用最后一个调用签名进行类型推断 1 2 3 4 5 6 7 8 9 declare function foo (x:string ):number ;declare function foo (x:number ):string ;declare function foo (x:string | number ):string | number ;type UnpackedFn <T> = T extends (...args :any []) => (infer U) ? U : T ;type U2 = UnpackedFn <typeof foo>;
利用条件链我们可以实现功能更加强大的 Unpacked 工具类型。 还是那句话,使用infer
,要判断的长什么样子,infer
就长什么样子(括号包起来) 1 2 3 4 5 6 7 8 9 10 type Unpacked <T> = T extends (...args :any []) => (infer U) ? U : T extends (infer U)[] ? U : T extends Promise <(infer U)> ? U : T; type T0 = Unpacked <string >; type T1 = Unpacked <string []>; type T2 = Unpacked <() => string >; type T3 = Unpacked <Promise <string >>; type T4 = Unpacked <Promise <string >[]>; type T5 = Unpacked <Unpacked <Promise <string >[]>>;
利用条件类型和 infer,我们还可以推断出对象类型中键的类型 1 2 3 4 5 6 7 type User = { id : number ; name : string ; } type PropertyType <T> = T extends {id : (infer U),name : (infer R) } ? [U,R] : T;type U3 = PropertyType <User >
在 PropertyType 工具类型中,我们通过 infer 声明了两个类型变量 U 和 R,分别表示对象类型中 id 和 name 属性的类型。若类型配,我们就会以元组的形式返回 id 和 name 属性的类型 。那么现在问题来了,在 PropertyType 工具类型中,如果只声明一个类型变量 U ,那结果又会是怎样呢?下面我们来验证一下: 1 2 3 type PropertyType <T> = T extends { id : infer U, name : infer U } ? U : Ttype U4 = PropertyType <User >
由以上代码可知,U4 类型返回的是 string 和 number 类型组合成的联合类型 。为什么会返回这样的结果呢?这是因为在协变位置上,若同一个类型变量存在多个候选者,则最终的类型将被推断为联合类型 。 然而,在逆变位置上,若同一个类型变量存在多个候选者,则最终的类型将被推断为交叉类型。同样,我们来实际验证一下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 export interface tempType { a :(x:string ) => void , b :(x:number ) => void , } type Bar <T> = T extends { a :(x:infer U ) => void , b :(x:infer U ) => void } ? U : never ; type U5 = Bar <tempType>;
ts的常用工具库 @部分转载CSDN织_网
Partial 1 2 3 type Partial <T> = { [P in keyof T]?: T[P] }
作用: Partial;把T当中的所有属性都变为可选
详细:生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为可选项
1 2 3 4 5 6 7 8 9 10 interface Foo { name : string age : number } type Bar = Partial <Foo >type Bar = { name?: string age?: number }
Required 1 2 3 type Required<T> = { [K in keyof T]-?:T[K] }
作用:Required:将T所有属性变为必填的
详细:生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为必选项
1 2 3 4 5 6 7 8 9 10 interface Foo { name : string age?: number } type Bar = Required <Foo >type Bar = { name : string age : number }
Pick 1 2 3 type Pick<T,U extends keyof T> = { [K in U]:T[K] }
作用:Pick(A,B);从A当中挑选B并返回
详细:生成一个新类型,该类型拥有 T 中的 K 属性集 ; 新类型 相当于 T 与 K 的交集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 interface Foo { name : string ; age?: number ; gender : string ; } type Bar = Pick <Foo , 'age' | 'gender' >type Bar = { age?: number gender : string } const todo : Bar = { age?: 3 , gender : 男 };
Exclude 源码分布式条件类型来说,当传入的被检查类型是联合类型的时候,在运算过程中就会被依次运算 1 type Exclude <T,U> = T extends U ? never : T;
作用:Exclude<A,B>; 排除A当中的B
详细: 如果 T 是 U 的子类型则返回 never 不是则返回 T(never可以理解为丢弃值不会返回)
1 2 3 4 5 6 type A = number | string | boolean type B = number | boolean type Foo = Exclude <A, B>type Foo = string
1 type Extract <T,U> = T extends U ? T : never ;
作用:Extract<A,B> 从A中提取B
详细: 如果 T 是 U 的子类型则返回 T 不是则返回 never (never可以理解为丢弃值不会返回)
1 2 3 4 5 6 type A = number | string | boolean type B = number | boolean type Foo = Extract <A, B>type Foo = number | boolean
Omit 1 type Omit <T,K extends keyof any > = Pick <T,Exclude <T,K>>
作用:Exclude<A,B>; 排除A当中的B
详细: 如果 T 是 U 的子类型则返回 never 不是则返回 T(never可以理解为丢弃值不会返回)
1 2 3 4 5 6 7 8 9 10 type Foo = { name : string age : number } type Bar = Omit <Foo , 'age' >type Bar = { name : string }
NonNullable 1 type NonNullable <T> = T extends null | undefined ? never : T;
作用:NonNullable;从T中排除null 和 undefined
详细: 从泛型 T 中排除掉 null 和 undefined
1 2 3 type t = NonNullable <'name' | undefined | null >;
Parameters 1 2 type Parameters <T extends (...args :any ) => any > = T extends (...args :infer P) => any ? P : never ;
作用:Parameters< (形参) => 返回值 > 以元组的形式返回形参
详细: 以元组的方式获得函数的入参类型
1 2 3 type t = Parameters <(name: string ) => any >; type t2 = Parameters <((name: string ) => any ) | ((age: number ) => any )>;
ReturnType 1 2 3 export type ReturnType <T extends (...args :any ) => any > = T extends (...arg :any ) => infer R ? R : any ;
作用:ReturnType< (形参) => 返回值 >
详细: 获得函数返回值的类型
1 2 type t = ReturnType <(name: string ) => string | number >
Uppercase 1 type Uppercase <S extends string > = intrinsic
Uppercase将StringType转为大写,TS以内置关键字intrinsic来通过编译期来实现。
1 2 3 type a = Uppercase <'abcDEF' >type a = 'ABCDEF' ;
Lowercase 1 type Lowercase <S extends string > = intrinsic;
Lowercase将StringType转为小写,TS以内置关键字intrinsic来通过编译期来实现。
1 2 3 type a = Lowercase <'abcDEF' >type a = 'abcdef' ;
Capitalize 1 type Capitalize <S extends string > = intrinsic;
Capitalize将StringType首字母转为大写。
1 2 3 type CapitalizeExample = Capitalize <"abc" >;type CapitalizeExample = "Abc"
Uncapitalize 1 type Uncapitalize <S extends string > = intrinsic;
Uncapitalize将StringType首字母转为小写
1 2 3 type a = Uncapitalize <'AbcDEF' >type a = 'abcDEF' ;