THE LAST TIME
The last time, I have learned
【THE LAST TIME】 一直是我想写的一个系列,旨在厚积薄发,重温前端。
也是给自己的查缺补漏和技术分享。
笔者文章集合详见:
前言
JavaScript 毋庸置疑是一门非常好的语言,但是其也有很多的弊端,其中不乏是作者设计之处留下的一些 “bug”。当然,瑕不掩瑜~
话说回来,JavaScript 毕竟是一门弱类型语言,与强类型语言相比,其最大的编程陋习就是可能会造成我们类型思维的缺失(高级词汇,我从极客时间学到的)。而思维方式决定了编程习惯,编程习惯奠定了工程质量,工程质量划定了能力边界,而学习 Typescript,最重要的就是我们类型思维的重塑。
那么其实,Typescript 在我个人理解,并不能算是一个编程语言,它只是 JavaScript 的一层壳。当然,我们完全可以将它作为一门语言去学习。网上有很多推荐 or 不推荐 Typescript 之类的文章这里我们不做任何讨论,学与不学,用或不用,利与弊。各自拿捏~
再说说 typescript(下文均用 ts 简称),其实对于 ts 相比大家已经不陌生了。更多关于 ts 入门文章和文档也是已经烂大街了。此文不去翻译或者搬运各种 api或者教程章节。只是总结罗列和解惑,笔者在学习 ts 过程中曾疑惑的地方。道不到的地方,欢迎大家评论区积极讨论。
其实 Ts 的入门非常的简单:.js to .ts; over!

但是为什么我都会写 ts 了,却看不懂别人的代码呢? 这!就是入门与进阶之隔。也是本文的目的所在。
首先推荐下 ts 的编译环境:typescriptlang.org
再推荐笔者收藏的几个网站:
下面,逐个难点梳理,逐个击破。
可索引类型
关于ts 的类型应该不用过多介绍了,多用多记 即可。介绍下关于 ts 的可索引类型。准确的说,这应该属于接口的一类范畴。说到接口(interface),我们都知道 ts 的核心原则之一就是对值所具有的结构进行类型检查。 它有时被称之为“鸭式辩型法”或“结构性子类型”。而接口就是其中的契约。可索引类型也是接口的一种表现形式,非常实用!
interfaceStringArray{[index: number]: string;}
let myArray: StringArray;myArray=["Bob","Fred"];
let myStr: string=myArray[0];上面例子里,我们定义了StringArray接口,它具有索引签名。 这个索引签名表示了当用number去索引StringArray时会得到string类型的返回值。
Typescript支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。
这是因为当使用number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。
classAnimal{name: string;}classDogextendsAnimal{breed: string;}// 错误:使用数值型的字符串索引,有时会得到完全不同的Animal!interfaceNotOkay{[x: number]: Animal;[x: string]: Dog;}下面的例子里,name的类型与字符串索引类型不匹配,所以类型检查器给出一个错误提示:
interfaceNumberDictionary{[index: string]: number;
length: number;// 可以,length是number类型
name: string// 错误,`name`的类型与索引类型返回值的类型不匹配}当然,我们也可以将索引签名设置为只读,这样就可以防止给索引赋值
interfaceReadonlyStringArray{readonly[index: number]: string;}
let myArray: ReadonlyStringArray=["Alice","Bob"];myArray[2]="Mallory";// error!interface 和 type 关键字
stackoverflow 上的一个高赞回答还是非常赞的。typescript-interfaces-vs-types
interface 和 type 两个关键字的含义和功能都非常的接近。这里我们罗列下这两个主要的区别:
interface:
- 同名的
interface 自动聚合,也可以跟同名的 class 自动聚合 - 只能表示
object、class、function 类型
type:
- 不仅仅能够表示
object、class、function - 不能重名(自然不存在同名聚合了),扩展已有的
type 需要创建新 type - 支持复杂的类型操作
举例说明下上面罗列的几点:
Objects/Functions
都可以用来表示 Object 或者 Function ,只是语法上有些不同而已
interfacePoint{x:number;y:number;}interfaceSetPoint{(x:number,y:number):void;}typePoint={x:number;y:number;}typeSetPoint=(x:number,y:number)=>void;其他数据类型
与 interface 不同,type 还可以用来标书其他的类型,比如基本数据类型、元素、并集等
typeName=string;typePartialPointX={x:number;};typePartialPointY={y:number;};typePartialPoint=PartialPointX|PartialPointY;typeData=[number,string,boolean];Extend
都可以被继承,但是语法上会有些不同。另外需要注意的是,interface 和 type 彼此并不互斥。
interface extends interface
interfacePartialPointX{x:number;};interfacePointextendsPartialPointX{y:number;};type extends type
typePartialPointX={x:number;};typePoint=PartialPointX&{y:number;};interface extends type
typePartialPointX={x:number;};interfacePointextendsPartialPointX{y:number;};type extends interface
interfaceParticalPointX={x:number;};typePoint=ParticalPointX&{y:number};implements
一个类,可以以完全相同的形式去实现interface 或者 type。但是,类和接口都被视为静态蓝图(static blueprints),因此,他们不能实现/继承 联合类型的 type
interfacePoint{
x: number;
y: number;}classSomePointimplementsPoint{x: 1;y: 2;}typePoint2={x: number;
y: number;};classSomePoint2implementsPoint2{x: 1;y: 2;}typePartialPoint={x: number;}|{y: number;};// FIXME: can not implement a union typeclassSomePartialPointimplementsPartialPoint{x: 1;y: 2;}
声明合并
和 type 不同,interface 可以被重复定义,并且会被自动聚合
interfacePoint{x:number;};interfacePoint{y:number;};constpoint:Pint={x:1,y:2};only interface can
在实际开发中,有的时候也会遇到 interface 能够表达,但是 type 做不到的情况:给函数挂载属性
interfaceFuncWithAttachment{(param: string): boolean;
someProperty: number;}consttestFunc: FuncWithAttachment=function(param: string){returnparam.indexOf("Neal")>-1;};constresult=testFunc("Nealyang");// 有类型提醒testFunc.someProperty=4;
& 和 | 操作符
这里我们需要区分,| 和 &并非位运算符。我们可以理解为&表示必须同时满足所有的契约。|表示可以只满足一个契约。
interfaceIA{
a:string;
b:string;}typeTB{
b:number;
c:number[];}typeTC=TA|TB;// TC 的 key,包含 ab 或者 bc 即可,当然,包含 bac 也可以typeTD=TA&TB;// TD 的 可以,必须包含 abc交叉类型
交叉类型,我们可以理解为合并。其实就是将多个类型合并为一个类型。
- 同时是 Man 和 Woman
- 同时拥有 Man 和 Woman 这两种类型的成员
interfaceObjectConstructor{assign<T,U>(target:T,source:U):T&U;}以上是 ts 的源码实现,下面我们再看一个我们日常使用中的例子
interfaceA{
name:string;
age:number;
sayName:(name:string)=>void}interfaceB{name:string;
gender:string;
sayGender:(gender:string)=>void}
let a:A&B;// 这是合法的a.agea.sayGender注意:16446
extends
extends 即为扩展、继承。在 ts 中,extends 关键字既可以来扩展已有的类型,也可以对类型进行条件限定。在扩展已有类型时,不可以进行类型冲突的覆盖操作。例如,基类型中键a为string,在扩展出的类型中无法将其改为number。
typenum={num:number;}interfaceIStrNumextendsnum{
str:string;}// 与上面等价typeTStrNum=A&{str:string;}在 ts 中,我们还可以通过条件类型进行一些三目操作:T extends U ? X : Y
typeIsEqualType<A,B>=AextendsB ? (BextendsA ? true : false) : false;typeNumberEqualsToString=IsEqualType<number,string>;// falsetypeNumberEqualsToNumber=IsEqualType<number,number>;// true
keyof
keyof 是索引类型操作符。用于获取一个“常量”的类型,这里的“常量”是指任何可以在编译期确定的东西,例如const、function、class等。它是从 实际运行代码 通向 类型系统 的单行道。理论上,任何运行时的符号名想要为类型系统所用,都要加上 typeof。
在使用class时,class名表示实例类型,typeof class表示 class本身类型。是的,这个关键字和 js 的 typeof 关键字重名了 。
假设 T 是一个类型,那么keyof T产生的类型就是 T 的属性名称字符串字面量类型构成的联合类型(联合类型比较简单,和交叉类型对立相似,这里就不做介绍了)。
注意!上述的 T 是数据类型,并非数据本身。
interfaceIQZQD{
cnName:string;
age:number;
author:string;}typeant=keyofIQZQD;在 vscode 上,我们可以看到 ts 推断出来的 ant:

注意,如果 T 是带有字符串索引的类型,那么keyof T是 string或者number类型。
索引签名参数类型必须为 "string" 或 "number"
interfaceMap<T>{[key: string]: T;}//T[U]是索引访问操作符;U是一个属性名称。
let keys: keyofMap<number>;//string | number
let value: Map<number>['antzone'];//number泛型
泛型可能是对于前端同学来说理解起来有点困难的知识点了。通常我们说,泛型就是指定一个表示类型的变量,用它来代替某个实际的类型用于编程,而后再通过实际运行或推导的类型来对其进行替换,以达到一段使用泛型程序可以实际适应不同类型的目的。说白了,泛型就是不预先确定的数据类型,具体的类型在使用的时候再确定的一种类型约束规范。
泛型可以应用于 function、interface、type 或者 class 中。但是注意,泛型不能应用于类的静态成员
几个简单的例子,先感受下泛型
functionlog<T>(value: T): T {console.log(value);returnvalue;}// 两种调用方式log<string[]>(['a',',b','c'])log(['a',',b','c'])log('Nealyang')typeLog=<T>(value: T) =>T
let myLog: Log=loginterfaceLog<T>{(value: T): T}letmyLog: Log<number>=log// 泛型约束了整个接口,实现的时候必须指定类型。如果不指定类型,就在定义的之后指定一个默认的类型myLog(1)我们也可以把泛型变量理解为函数的参数,只不过是另一个维度的参数,是代表类型而不是代表值的参数。
classLog<T>{// 泛型不能应用于类的静态成员run(value: T){console.log(value)returnvalue}}letlog1=newLog<number>()//实例化的时候可以显示的传入泛型的类型log1.run(1)letlog2=newLog()log2.run({a: 1})//也可以不传入类型参数,当不指定的时候,value 的值就可以是任意的值类型约束,需预定义一个接口
interfaceLength{length: number}functionlogAdvance<TextendsLength>(value: T): T {console.log(value,value.length);returnvalue;}// 输入的参数不管是什么类型,都必须具有 length 属性logAdvance([1])logAdvance('123')logAdvance({length: 3})泛型的好处:
- 函数和类可以轻松的支持多种类型,增强程序的扩展性
- 不必写多条函数重载,冗长的联合类型声明,增强代码的可读性
- 灵活控制类型之间的约束
泛型,在 ts 内部也都是非常常用的,尤其是对于容器类非常常用。而对于我们,还是要多使用,多思考的,这样才会有更加深刻的体会。同时也对塑造我们类型思维非常的有帮助。
小试牛刀
functionpluck<T,KextendskeyofT>(o: T,names: K[]): T[K][]{returnnames.map(n=>o[n]);}interfacePerson{
name: string;
age: number;}
let person: Person={name: 'Jarid',age: 35};letstrings: string[]=pluck(person,['name','name','name']);//["Jarid", "Jarid", "Jarid"]所谓的小试牛刀,就是结合上面我们说的那几个点,分析下pluck方法的意思
<T, K extends keyof T>约束了这是一个泛型函数
keyof T 就是取 T 中的所有的常量 key(这个例子的调用中),即为:"name" | "age"K extends keyof Person 即为 K 是 "name" or "age"
- 结合以上泛型解释,再看形参
K[] 即为 只能包含"name" or "age"的数组
- 再看返回值
T[K][] 后面的[]是数组的意思。而 T[K]就是去对象的 T 下的key: K的 value
infer
infer 关键字最早出现在 PR 里面,表示在 extends 条件语句中待推断的类型变量
是在 ts2.8 引入的,在条件判断语句中,该关键字用于替换手动获取类型。
typePramType<T>=Textends(param : inferp)=>any ? p : T;
在上面的条件语句中,infer P 表示待推断的函数参数,如果T能赋值给(param : infer p) => any,则结果是(param: infer P) => any类型中的参数 P,否则为T.
interfaceINealyang{
name:'Nealyang';
age:'25';}typeFunc=(user:INealyang)=>void;typeParam=ParamType<Func>;// Param = INealyangtypeTest=ParamType<string>;// string工具泛型
所谓的工具泛型,其实就是泛型的一些语法糖的实现。完全也是可以自己的写的。我们也可以在lib.d.ts中找到他们的定义
Partial
Partial的作用就是将传入的属性变为可选。
由于 keyof 关键字已经介绍了。其实就是可以用来取得一个对象接口的所有 key 值。在介绍 Partial 之前,我们再介绍下 in 操作符:
typeKeys="a"|"b"typeObj={[pinKeys]: any}// -> { a: any, b: any }然后再看 Partial 的实现:
typePartial<T>={[PinkeyofT]?: T[P]};翻译一下就是keyof T 拿到 T 所有属性名, 然后 in 进行遍历, 将值赋给 P, 最后 T[P] 取得相应属性的值,然后配合?:改为可选。
Required
Required 的作用是将传入的属性变为必选项, 源码如下
typeRequired<T>={[PinkeyofT]-?: T[P]};Readonly
将传入的属性变为只读选项, 源码如下
typeReadonly<T>={readonly[PinkeyofT]: T[P]};Record
该类型可以将 K 中所有的属性的值转化为 T 类型,源码实现如下:
/** * Construct a type with a set of properties K of type T */typeRecord<Kextendskeyofany,T>={[PinK]: T;};可以根据 K 中的所有可能值来设置 key,以及 value 的类型,举个例子:
typeT11=Record<'a'|'b'|'c',Person>;// -> { a: Person; b: Person; c: Person; }Pick
从 T 中取出 一系列 K 的属性
/** * From T, pick a set of properties whose keys are in the union K */typePick<T,KextendskeyofT>={[PinK]: T[P];};Exclude
Exclude 将某个类型中属于另一个的类型移除掉。
/** * Exclude from T those types that are assignable to U */typeExclude<T,U>=TextendsU ? never : T;
以上语句的意思就是 如果 T 能赋值给 U 类型的话,那么就会返回 never 类型,否则返回 T,最终结果是将 T 中的某些属于 U 的类型移除掉
举个栗子:
typeT00=Exclude<'a'|'b'|'c'|'d','a'|'c'|'f'>;// -> 'b' | 'd'
可以看到 T 是 'a' | 'b' | 'c' | 'd' ,然后 U 是 'a' | 'c' | 'f' ,返回的新类型就可以将 U 中的类型给移除掉,也就是 'b' | 'd' 了。
Extract
Extract 的作用是提取出 T 包含在 U 中的元素,换种更加贴近语义的说法就是从 T 中提取出 U,源码如下:
/** * Extract from T those types that are assignable to U */typeExtract<T,U>=TextendsU ? T : never;
Demo:
typeT01=Extract<'a'|'b'|'c'|'d','a'|'c'|'f'>;// -> 'a' | 'c'
Omit
Pick 和 Exclude 进行组合, 实现忽略对象某些属性功能, 源码如下:
/** * Construct a type with the properties of T except for those in type K. */typeOmit<T,Kextendskeyofany>=Pick<T,Exclude<keyofT,K>>;
Demo:
// 使用typeFoo=Omit<{name: string,age: number},'name'>// -> { age: number }更多工具泛型

其实常用的工具泛型大概就是我上面介绍的几种。更多的工具泛型,可以通过查看 lib.es5.d.ts里面查看。
毕竟。。。搬运几段声明着实没啥意思。

类型断言
断言这种东西还是少用。。。。不多对于初学者,估计最快熟练掌握的就是类型断言了。毕竟 any 大法好
Typescript 允许我们覆盖它的推断(毕竟代码使我们自己写的),然后根据我们自定义的类型去分析它。这种机制,我们称之为 类型断言
constnealyang={};nealyang.enName='Nealyang';// Error: 'enName' 属性不存在于 ‘{}’nealyang.cnName='一凨';// Error: 'cnName' 属性不存在于 '{}'interfaceINealyang={enName:string;cnName:string;}constnealyang={}asINealyang;// const nealyang = <INealyang>{};nealyang.enName='Nealyang';nealyang.cnName='一凨';类型断言比较简单,其实就是“纠正”ts对类型的判断,当然,是不是纠正就看你自己的了。
需要注意一下两点即可:
- 推荐类型断言的预发使用
as关键字,而不是<> ,防止歧义 - 类型断言并非类型转换,类型断言发生在编译阶段。类型转换发生在运行时
函数重载
在我刚开始使用 ts 的时候,我一直困惑。。。为什么会有函数重载这么鸡肋的写法,可选参数它不香么?

函数重载的基本语法:
declarefunctiontest(a: number): number;declarefunctiontest(a: string): string;constresS=test('Hello World');// resS 被推断出类型为 string;constresN=test(1234);// resN 被推断出类型为 number;这里我们申明了两次?!为什么我不能判断类型或者可选参数呢?后来我遇到这么一个场景,
interfaceUser{name: string;age: number;}declarefunctiontest(para: User|number,flag?: boolean): number;在这个 test 函数里,我们的本意可能是当传入参数 para 是 User 时,不传 flag,当传入 para 是 number 时,传入 flag。TypeScript 并不知道这些,当你传入 para 为 User 时,flag 同样允许你传入:
constuser={name: 'Jack',age: 666}// 没有报错,但是与想法违背constres=test(user,false);使用函数重载能帮助我们实现:
interfaceUser{name: string;age: number;}declarefunctiontest(para: User): number;declarefunctiontest(para: number,flag: boolean): number;constuser={name: 'Jack',age: 666};// bingo// Error: 参数不匹配constres=test(user,false);Ts 的一些实战
我之前在公众号里面发表过两篇关于TS在实战项目中的介绍:
参考文献
点击阅读原文可查外链
THE LAST TIME
【THE LAST TIME】 一直是我想写的一个系列,旨在厚积薄发,重温前端。
也是给自己的查缺补漏和技术分享。
笔者文章集合详见:
前言
JavaScript毋庸置疑是一门非常好的语言,但是其也有很多的弊端,其中不乏是作者设计之处留下的一些 “bug”。当然,瑕不掩瑜~话说回来,
JavaScript毕竟是一门弱类型语言,与强类型语言相比,其最大的编程陋习就是可能会造成我们类型思维的缺失(高级词汇,我从极客时间学到的)。而思维方式决定了编程习惯,编程习惯奠定了工程质量,工程质量划定了能力边界,而学习 Typescript,最重要的就是我们类型思维的重塑。那么其实,
Typescript在我个人理解,并不能算是一个编程语言,它只是JavaScript的一层壳。当然,我们完全可以将它作为一门语言去学习。网上有很多推荐 or 不推荐 Typescript 之类的文章这里我们不做任何讨论,学与不学,用或不用,利与弊。各自拿捏~再说说 typescript(下文均用 ts 简称),其实对于 ts 相比大家已经不陌生了。更多关于 ts 入门文章和文档也是已经烂大街了。此文不去翻译或者搬运各种 api或者教程章节。只是总结罗列和解惑,笔者在学习 ts 过程中曾疑惑的地方。道不到的地方,欢迎大家评论区积极讨论。
其实 Ts 的入门非常的简单:
.jsto.ts; over!但是为什么我都会写 ts 了,却看不懂别人的代码呢? 这!就是入门与进阶之隔。也是本文的目的所在。
首先推荐下 ts 的编译环境:typescriptlang.org
再推荐笔者收藏的几个网站:
下面,逐个难点梳理,逐个击破。
可索引类型
关于ts 的类型应该不用过多介绍了,多用多记 即可。介绍下关于 ts 的可索引类型。准确的说,这应该属于接口的一类范畴。说到接口(interface),我们都知道 ts 的核心原则之一就是对值所具有的结构进行类型检查。 它有时被称之为“鸭式辩型法”或“结构性子类型”。而接口就是其中的契约。可索引类型也是接口的一种表现形式,非常实用!
上面例子里,我们定义了
StringArray接口,它具有索引签名。 这个索引签名表示了当用number去索引StringArray时会得到string类型的返回值。Typescript支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。
这是因为当使用
number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。下面的例子里,name的类型与字符串索引类型不匹配,所以类型检查器给出一个错误提示:
当然,我们也可以将索引签名设置为只读,这样就可以防止给索引赋值
interface 和 type 关键字
stackoverflow 上的一个高赞回答还是非常赞的。typescript-interfaces-vs-types
interface和type两个关键字的含义和功能都非常的接近。这里我们罗列下这两个主要的区别:interface:interface自动聚合,也可以跟同名的class自动聚合object、class、function类型type:object、class、functiontype需要创建新type举例说明下上面罗列的几点:
Objects/Functions
都可以用来表示
Object或者Function,只是语法上有些不同而已其他数据类型
与
interface不同,type还可以用来标书其他的类型,比如基本数据类型、元素、并集等Extend
都可以被继承,但是语法上会有些不同。另外需要注意的是,interface 和 type 彼此并不互斥。
interface extends interface
type extends type
interface extends type
type extends interface
implements
一个类,可以以完全相同的形式去实现
interface或者type。但是,类和接口都被视为静态蓝图(static blueprints),因此,他们不能实现/继承 联合类型的type声明合并
和
type不同,interface可以被重复定义,并且会被自动聚合only interface can
在实际开发中,有的时候也会遇到
interface能够表达,但是type做不到的情况:给函数挂载属性& 和 | 操作符
这里我们需要区分,
|和&并非位运算符。我们可以理解为&表示必须同时满足所有的契约。|表示可以只满足一个契约。交叉类型
交叉类型,我们可以理解为合并。其实就是将多个类型合并为一个类型。
以上是 ts 的源码实现,下面我们再看一个我们日常使用中的例子
注意:16446
extends
extends即为扩展、继承。在 ts 中,extends 关键字既可以来扩展已有的类型,也可以对类型进行条件限定。在扩展已有类型时,不可以进行类型冲突的覆盖操作。例如,基类型中键a为string,在扩展出的类型中无法将其改为number。在 ts 中,我们还可以通过条件类型进行一些三目操作:
T extends U ? X : Ykeyof
keyof 是索引类型操作符。用于获取一个“常量”的类型,这里的“常量”是指任何可以在编译期确定的东西,例如
const、function、class等。它是从 实际运行代码 通向 类型系统 的单行道。理论上,任何运行时的符号名想要为类型系统所用,都要加上typeof。在使用
class时,class名表示实例类型,typeof class表示class本身类型。是的,这个关键字和 js 的typeof关键字重名了 。假设 T 是一个类型,那么
keyof T产生的类型就是T的属性名称字符串字面量类型构成的联合类型(联合类型比较简单,和交叉类型对立相似,这里就不做介绍了)。注意!上述的 T 是数据类型,并非数据本身。
在
vscode上,我们可以看到ts推断出来的ant:注意,如果
T是带有字符串索引的类型,那么keyof T是string或者number类型。索引签名参数类型必须为 "string" 或 "number"
泛型
泛型可能是对于前端同学来说理解起来有点困难的知识点了。通常我们说,泛型就是指定一个表示类型的变量,用它来代替某个实际的类型用于编程,而后再通过实际运行或推导的类型来对其进行替换,以达到一段使用泛型程序可以实际适应不同类型的目的。说白了,泛型就是不预先确定的数据类型,具体的类型在使用的时候再确定的一种类型约束规范。
泛型可以应用于
function、interface、type或者class中。但是注意,泛型不能应用于类的静态成员几个简单的例子,先感受下泛型
我们也可以把泛型变量理解为函数的参数,只不过是另一个维度的参数,是代表类型而不是代表值的参数。
类型约束,需预定义一个接口
泛型的好处:
泛型,在 ts 内部也都是非常常用的,尤其是对于容器类非常常用。而对于我们,还是要多使用,多思考的,这样才会有更加深刻的体会。同时也对塑造我们类型思维非常的有帮助。
小试牛刀
所谓的小试牛刀,就是结合上面我们说的那几个点,分析下
pluck方法的意思<T, K extends keyof T>约束了这是一个泛型函数keyof T就是取 T 中的所有的常量 key(这个例子的调用中),即为:"name" | "age"K extends keyof Person即为 K 是"name"or"age"K[]即为 只能包含"name"or"age"的数组T[K][]后面的[]是数组的意思。而T[K]就是去对象的 T 下的key:K的valueinfer
infer 关键字最早出现在 PR 里面,表示在 extends 条件语句中待推断的类型变量
是在 ts2.8 引入的,在条件判断语句中,该关键字用于替换手动获取类型。
在上面的条件语句中,
infer P表示待推断的函数参数,如果T能赋值给(param : infer p) => any,则结果是(param: infer P) => any类型中的参数P,否则为T.工具泛型
所谓的工具泛型,其实就是泛型的一些语法糖的实现。完全也是可以自己的写的。我们也可以在
lib.d.ts中找到他们的定义Partial
Partial的作用就是将传入的属性变为可选。由于
keyof关键字已经介绍了。其实就是可以用来取得一个对象接口的所有 key 值。在介绍Partial之前,我们再介绍下in操作符:然后再看 Partial 的实现:
翻译一下就是
keyof T拿到T所有属性名, 然后in进行遍历, 将值赋给P, 最后T[P]取得相应属性的值,然后配合?:改为可选。Required
Required的作用是将传入的属性变为必选项, 源码如下Readonly
将传入的属性变为只读选项, 源码如下
Record
该类型可以将
K中所有的属性的值转化为T类型,源码实现如下:可以根据
K中的所有可能值来设置key,以及value的类型,举个例子:Pick
从
T中取出 一系列K的属性Exclude
Exclude 将某个类型中属于另一个的类型移除掉。
以上语句的意思就是 如果
T能赋值给U类型的话,那么就会返回never类型,否则返回T,最终结果是将T中的某些属于U的类型移除掉举个栗子:
可以看到
T是'a' | 'b' | 'c' | 'd',然后U是'a' | 'c' | 'f',返回的新类型就可以将U中的类型给移除掉,也就是'b' | 'd'了。Extract
Extract的作用是提取出T包含在U中的元素,换种更加贴近语义的说法就是从T中提取出U,源码如下:Demo:
Omit
Pick和Exclude进行组合, 实现忽略对象某些属性功能, 源码如下:Demo:
更多工具泛型
其实常用的工具泛型大概就是我上面介绍的几种。更多的工具泛型,可以通过查看
lib.es5.d.ts里面查看。毕竟。。。搬运几段声明着实没啥意思。
类型断言
断言这种东西还是少用。。。。不多对于初学者,估计最快熟练掌握的就是类型断言了。毕竟 any 大法好
Typescript 允许我们覆盖它的推断(毕竟代码使我们自己写的),然后根据我们自定义的类型去分析它。这种机制,我们称之为 类型断言
类型断言比较简单,其实就是“纠正”
ts对类型的判断,当然,是不是纠正就看你自己的了。需要注意一下两点即可:
as关键字,而不是<>,防止歧义函数重载
函数重载的基本语法:
这里我们申明了两次?!为什么我不能判断类型或者可选参数呢?后来我遇到这么一个场景,
在这个 test 函数里,我们的本意可能是当传入参数 para 是 User 时,不传 flag,当传入 para 是 number 时,传入 flag。TypeScript 并不知道这些,当你传入 para 为 User 时,flag 同样允许你传入:
使用函数重载能帮助我们实现:
Ts 的一些实战
我之前在公众号里面发表过两篇关于TS在实战项目中的介绍:
参考文献