欢迎star我的github仓库一起学习~目前vue源码学习系列已经更新6篇了~https://github.com/yisha0307/...快速跳转:Vue的双向绑定原理(已完结)说说Vue中的VirtualDOM(已完结)Reactdiff与Vuediff实现异步更新策略在Vue中的区别(已完结)Vuex的实现理解(已完结)Typescript学习笔记(持续更新中)Vue源码中闭包的使用(已完成)Typescript众所周知,javascript是一门动态语言,与java这种静态语言最大的区别在于它允许改变的数据类型变量。比如在js中定义一个vara=123。此时,a是一个Number。再执行一次a=[1,2,3],a就变成了Array。这在js中是完全可以的,但是在JAVA这种一开始就定义了a的数据类型的静态语言中就不行了。动态语言有它的优点,就是写起来比较灵活,但是它的缺点也很明显。因为没有对数据类型进行检查,所以经常会导致bug。这时,打字稿出现了。TypeScript是Microsoft开发的一种免费开源编程语言。它是JavaScript的超集,本质上是为该语言添加了可选的静态类型和基于类的面向对象编程。由于vue的源码几乎都是用typescript写的,所以我在学习vue-src的时候,也学习了ts。更重要的是,vue3已经发布了pre-alpha版本,ts将在vue3中全面支持,学习typescript大有裨益。本文将根据源码中一些ts的写法来介绍typescript。系统学习ts,可以参考Typescript入门教程。TypeScript特性基本类型Typescript包括boolean/number/string/object/Array/tuple/enumeration/any/Undefined/Null/Void/Never例如vue-src中随处可见的类型检查:exportfunctionremove(arr:数组<任何>,项目:任何):数组<任何>|void{if(arr.length){constindex=arr.indexOf(item)if(index>-1){返回arr。splice(index,1)}}}remove这个函数接受两个参数arr和item,arr是一个数组,数组中的元素可以是任意类型(any),item也可以是任意类型,并且返回值可以是数组,也可以为空(|表示或的关系,称为“联合型”)。再比如vnode的代码:exportdefaultclassVNode{tag:string|空白;数据:VNodeData|空白;孩子们:?数组;文本:字符串|空白;榆树:节点|空白;ns:字符串|空白;上下文:组件|空白;//呈现在这个组件的作用域中functionalContext:Component|空白;//仅适用于功能组件根节点key:string|编号|空白;组件选项:VNodeComponentOptions|空白;componentInstance:组件|空白;//组件实例父级:VNode|空白;//组件占位符节点raw:boolean;//包含原始HTML?(仅限服务器)isStatic:boolean;//提升的静态节点isRootInsert:boolean;//进入转换检查所必需的isComment:boolean;//空注释占位符?已克隆:布尔值;//是克隆节点吗?isOnce:布尔值;//是一个v-once节点吗?constructor(tag?:string,data?:VNodeData,children?:?Array,text?:string,elm?:Node,context?:Component,componentOptions?:VNodeComponentOptions){/*当前节点的标签名*/this.tag=tag//省略各种属性的定义}//DEPRECATED:componentInstance的别名,用于向后兼容。/*istanbul忽略下一个*/getchild():Component|void{returnthis.componentInstance}}可见,VNode类在一开始就声明了this的各种属性的类型。大多数情况下,高级类型是对对象类型做更详细的注解,主要是使用接口(interface),除了对类的部分行为进行抽象外,还常用于描述对象的“形状(Shape)”接口的一个简单例子:interfacePerson{name:string;age:number;}lettom:Person={name:'Tom',age:25};上面代码中,定义了一个接口Person,然后定义了一个变量tom,类型为Person。这样,我们就约束了tom的形状与接口Person保持一致。如果某个属性缺少或moreintom,则不允许。如果我们不确定是否有某些属性,我们可以使用?指示可选属性:interfacePerson{name:string;age?:number;}lettom:Person={name:'Tom'};//如果我们允许将一些未知属性添加到接口,则为真,可以使用:interfacePerson{name:string;年龄?:数字;[propName:string]:any;}lettom:Person={name:'Tom',gender:'male'};在上面的代码中,我们定义了一个string类型的propName,它的类型可以是任意属性any。看一个vue-src中的例子:interfaceISet{has(key:string|number):boolean;添加(键:字符串|数字):混合;clear():void;}当使用这个接口时:classSetimplementsISet{set:Object;构造函数(){this.set=Object.create(null)}有(key:string|number){returnthis.set[key]===true}add(key:string|number){this.set[key]=true}clear(){this.set=Object.create(null)}}补充一下,implements(实现)是面向对象中的一个重要概念。一般来说,一个类只能继承另一个类。有时不同的类可以有一些共同的特征。这时候可以将特征提取到接口中,用implements关键字实现。这个特性大大提高了面向对象的灵活性。-----《Typescript》入门教程上面代码中,ISet接口定义了has、add、clear三个方法,Set类实现了ISet并定义了这三个方法。泛型在源码中给出了一个例子(因为泛型在vue2中几乎没有用到,这里的例子来自vue3,来自友达的vue-next项目):functionlast(xs:T[]):T|undefined{returnxs[xs.length-1]}这个方法的意思是传入一个数组(但是不清楚数组每个值的类型),返回数组的最后一个值。这样,我们就有了一个概念。所谓泛型(Generics)是指在定义函数、接口或类时不事先指定具体类型,而是在使用时指定类型的特性。在上面的例子中,我们在函数名后面添加了,其中T用来指代任何输入类型,可以在后面的输入(xs:T[])和输出T或undefined中使用。然后在调用的时候,可以指定它的具体类型为string。类型会自动推导出T的类型。另一段高级代码,同样来自vue3:exportfunctionwithScopeId(id:string):(fn:T)=>T{if(__BUNDLER__){return((fn:Function)=>{returnfunction(this:any){pushScopeId(id)constres=fn.apply(this,arguments)popScopeId()returnres}})asany}else{returnundefinedasany}}这段代码也使用了泛型,但是泛型类型是有限制的,只允许传入的fn继承自Function。当然你也可以自定义接口,例如(例子来自TypeScript入门教程-泛型):interfaceLengthwise{length:number}functionloggingIdentity(arg:T):T{console.log(arg.length)returnarg}这确保泛型T必须符合接口Lengthwise的形状,也就是说,它将具有长度属性。分片数组的不同表示letfibonacci:number[]=[1,1,2,3,5]letfibonacci:Array=[1,1,2,3,5]元组和数组不同的数组组合对象相同的类型,而元组组合不同类型的对象。也就是说,数组中的每个值必须是相同的数据类型,但元组无关紧要。例如://arraylettom:number[]=[1,'2']//error//tuplelettom:[number,string]=[1,'2']//passedassertion有两种语法对于断言,它是值或值作为类型。在tsx中,只能使用后者。例如在vue3中:exportfunctionprovide(key:InjectionKey|string,value:T){if(!currentInstance){if(__DEV__){warn(`provide()canonlybeusedinsidesetup().`)}else{letprovides=currentInstance.providesconstparentProvides=currentInstance.parent&¤tInstance.parent.providesif(parentProvides===provides){provides=currentInstance.provides=Object.create(parentProvides)}//TSdoesn'tallowsymbolasindextypeprovides[keyasstring]=value}}断言适用于当类型不确定时确实需要访问其中一种类型的属性或方法,比如上面的例子,keyasstring表示key断言变成了字符串类型。之后key可以根据string类型使用string的一些方法。需要注意的是,类型断言不是类型转换,断言联合类型中不存在的类型是不允许的。