引言:最近笔者在学习typescript,同时应用到项目开发中,除了简单的类型约束之外,因为typescript仍有许多让初学者不明确的点(官方文档也没有写清楚),故笔者整理此文章供初学者学习参考。
typescript是javascript的超集,在js的基础上,为你的变量参数等加上类型定义,因而就变成了typescript,在项目中使用typescript会有如下几点好处:javascript
number、string、boolean、object、null、undefined、symboljava
tuple元组指的是能够对数组内不一样项设置不一样的数值类型;
any类型不指定变量具体类型;
Array<T>中T为任意类型,也可为自定义类型;
enum为枚举类型。
注:有时候为了ts不报错,能够经过类型断言绕过检测。node
除了以上的基本类型值和ts中定义的特殊类型之外,使用者还能够自定义类型,经过interface定义一个未实现的接口,它能够用来限制变量所具有的数据格式,同理,class做为构造类,也可用来变量类型,type是类型别名,有点相似interface,但其主要用于类型别名和类型合并等。typescript
interface Person{ name: string } class Person{ name: string } type Person{ name: string } const person: Person;
以上三者的约束效果是一致的。那何时用哪一个呢?关键在于你对于接口和类的理解,当你提炼的只是一个通用接口的时候能够用interface,当你要提供一个能够实例化的class给外部模块使用的时候,你须要用class。type前面已经介绍了,主要用于类型别名或合并。因此只要你理解三者用途的区别,天然会知道何时使用哪一个了。数组
若是是普通函数,能够直接经过interface定义:数据结构
interface testfunction{ (name: string):number } const myfunc: testfunction = ()=>{return 1;}
定义函数的传参类型以及返回值类型。
固然你也能够直接在函数上加约束条件,不须要单独提取一个interface来定义函数类型:函数
const myfunc = (name: string):number{ return 1; }
若是是构造函数类的话,就不赘述了,构造函数类不一样于普通函数。
固然,ts中容许对函数重载,以及设置可选参数,即在参数key后加个问号。学习
你能够对class中的属性设置为只读属性,即设置readonly。
你能够对class中的方法添加public、private、protected修饰符。
你能够对class实现接口。类能够继承类,类实现接口,接口继承接口。
你能够实现抽象类abstract class,抽象类做为其它派生类的基类使用。 它们通常不会直接被实例化。 不一样于接口,抽象类能够包含成员的实现细节。 abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。this
1)、public修饰符表示属性是公开的,能够经过实例去访问该属性。类属性默认都是public属性。
2)、private修饰符表示属性是私有的,只有实例的方法才能访问该属性。
3)、protected修饰符表示属性是保护属性,只有实例的方法和派生类中的实例方法才能访问到。
4)、固然除此以外,还有static修饰符,表示只能经过类自己来调用方法,不能经过实例调用。spa
interface PersonInterface{ name: string } class Person implements PersonInterface{ readonly name: string constructor({name: string} = {}){ this.name = name } public getMyName(){ this.myselfTest() this.lastTest() return this.name } static test(){ console.log(`test`) } private myselfTest(){ console.log(`my test`) } protected lastTest(){ console.log(`protected`) } } Person.test() const person = new Person() person.getMyName() //myselfTest只能在实例方法中调用 //lastTest能够在父类和子类的实例方法中调用 class Runner extends Person{ public test(){ this.lastTest() } }
泛型泛型,就是比较空泛的类型,不明确的类型,不一样于any,any直接把全部东西都只带成any,不作任何的约束,泛型能够对参数和返回值之间设置关联约束,同时,也能够在设置变量的数据类型的时候,传入某种具体类型,去“具化泛型”。
函数中的使用:
function someFunction<T>(arg: T) : T { return arg; } someFunction('123') someFunction<number>(123)
接口 or type 中使用:
interface character<T>{ type: T name: string } type character<T> = { type: T name: string } const char: character<number>
经过传入number具化泛型的约束范围,同时,接口和type均可以再进行复用,这就是泛型在这里起到的做用。
注:泛型还能够添加额外的泛型约束,extend某个接口,让ts知道这个泛型T背后可能存在的额外属性。
ts中存在交叉类型,联合类型,is、keyof,感受除了联合类型用的比较多之外,其余不多用到,不赘述。
除此以外,还有一个是ts中的声明合并,interface的声明能够合并,namespace的声明也能够合并。因为能够重复声明,以后会自动合并,因此咱们也能够借此来对第三方库中的interface或namespace进行扩展。
interface Person{ name: string } interface Person{ address: string } ==> interface Person{ name: string address: string } namespace Test{ const name: string } namespace Test{ function getName(){} } ==> namespace Test{ const name: string function getName():void }
namespace是个神奇的概念,命名空间,你也能够当作这是一个挂载点,是一个实例化的对象{},它上面挂载了一些属性和方法。这会让你更好的理解它,它不是一个虚拟的存在,像interface、class都只是定义了类型,并无实例化对象,可是命名空间不同,你能够把它当作一个现实存在的实例对象。那么接下来你会更好地理解下面这个操做:
declare function globalFunc(name: string): void declare namespace globalFunc{ const version: string function otherFunc(name: string): void }
注:declare是类型定义文件中的语法,后面会介绍到。
在这里我declare一个全局的方法,同时我declare了一个命名空间和方法同名,其实这时候你就能够认为这个方法的实例对象上挂载了一些属性和方法。毕竟在js中,函数自己也是一个对象。就像这样子:
const globalFunc = ()=>{} globalFunc.version = '1.0.0' globalFunc.otherFunc = ()=>{}
这样,你就能很好地理解这种写法了。并且这种写法十分常见。
一、当你引入一个js文件的时候,ts会去查找对应文件目录下是否存在同名的d.ts文件,不管是相对路径仍是绝对路径仍是第三方模块的引入,都是如此;
二、若是你本身在书写一个第三方模块,那么直接用ts开发,编译产物中变回自动生成模块文件的d.ts文件;
三、在开发本身的项目或者模块过程当中,不可避免你要引入第三方模块,或者对全局的属性进行扩展,这个时候你须要书写本身项目中的全局类型定义文件,你能够将他们统一放置在typings文件夹下,ts会自动查找项目中的d.ts文件,(且不与其余文件存在同名的状况下),例如你能够在项目下建立一个global.d.ts文件,对第三方模块或全局模块作额外定义或扩展;
四、第三方模块都要本身来定义吗?非也,大部分的第三方库都有对应的类型定义文件,若是没有也不要担忧,有人帮你写好了类型定义文件,例如,在nodejs中开发的时候,引用原生模块时,你能够经过引入@types/node包来告诉ts原生模块中的属性方法,其余第三方库也是相似的。
五、declare必须是在全局声明文件中使用时,才能有效定义全局变量,若是使用了export import等,将会被断定为第三方模块,而不会当作全局定义文件。
六、当你在扩展第三方模块的时候,在你的声明文件中,使用import * as xx from 'xx'模块,你能获取到里面定义的interface、enum这种"虚拟"类型,但当你在业务代码ts文件中引用时,只能访问到模块实际挂载的属性的方法。
一个项目中的全局声明文件多是这样的:
//定义全局变量 declare const version: string //定义全局方法 declare function jQuery(selector: string): any; //定义全局类 declare class Person{ ... } //定义第三方模块 declare module 'fs'{ import * as fs from 'fs' function myTestFunc(): void //扩展方法 //自定义一个interface扩展fs内部的interface interface myTestInterface extends fs.xxinterface{ name: string } //定义一个模块上挂载的新属性,类型为自定义扩展后的interface const myTestName: myTestInterface }
接下来,一个模块定义文件多是这样的:(你能够查阅官方文档,里面会有更多的模板实例,这里会展现一个简单的栗子并解释)
/*当你的模块是一个UMD模块且是在外部加载好的时候,添加这一行,告诉ts全局有这个变量 */ export as namespace myClassLib; /*模块所暴露的对象 */ export = MyClass; /*你的构造函数 */ declare class MyClass { constructor(someParam?: string); someProperty: string[]; myMethod(opts: MyClass.MyClassMethodOptions): number; } /*你能够把你的类型定义放到Myclass命名空间下统一管理,也能够供给其余人扩展它 */ declare namespace MyClass { export interface MyClassMethodOptions { width?: number; height?: number; } }
看完了全局声明文件、模块声明文件以及声明文件注意事项,相信你对声明文件的使用书写以及了然于心!
笔者我的整理了额外的一些疑问,这里整理了一下罗列以下:
type和interface自己均可以用来定义类型,约束变量的数据结构,可是type和interface的应用场景不一样,须要你更好地理解这二者。type能够做为类型别名,也能够再type中使用联合类型,例如type myType = number|string,这是interface没法实现的。而interface不只能够定义类型,还定义一个待实现的接口,它的用处更普遍,而且在第三方模块的扩展中,通常都是对interface进行再扩展,你没法对type进行再扩展。
1)、抽象类要被子类继承,接口要被类实现。
2)、抽象类中能够声明和实现方法,接口只能申明。
3)、接口抽象级别更高
4)、抽象类用来抽象类别,接口用来抽象功能。
declare module是定义一个模块,module自己和namespace相似,都是一个挂载点,可是module > namespace,一个module的定义文件里能够含有多个namespace,namespace只是一个辅助的挂载点,在之前namespace被称为内部模块,可是后来不这么叫了,以防混淆。
三斜线指令仅可放在包含它的文件的最顶端,只可以使用在d.ts文件中。
看一下二者的使用:
/// <reference path="..." /> 用于声明对某个路径文件的依赖。 /// <reference types="..." />用于声明对某个包的依赖。 例如 /// <reference types="node" /> 代表这个文件使用了@types/node/index.d.ts里面声明的名字
path用于约束声明文件之间的依赖关系,types直接声明了对某个包的依赖,相信你已经明白了。
感谢你看完了这篇文章,这篇文章包含了入门的基础知识,可是若是你想了解更多有关基础知识,还请移步官网。其次,咱们概括总结了ts中进阶、难度较高的语法,同时对声明文件的定义进行了梳理,最后咱们对常见的疑惑点进行了概括整理。但愿这篇入门教程对你有所帮助~