TypeScript 是 JavaScript 的一个超集,或者说 TS 是基于 JS 的,主要提供了类型系统和对 ES6 的支持,它由 Microsoft 开发,代码开源于 GitHub 上,它可以编译成纯 JavaScript;编译出来的 JavaScript 可以运行在任何浏览器上(不能直接运行 TS)。
我对其简单的理解就是强类型的 JS,强类型可以给我们带来静态语言的一些好处,比如可读性和可维护性,也更加适合 IDE 做语法分析和代码提示,同时它也有强大的类型推断,缺点就是学习成本和开发成本(挺明显的)。
这样看来 TS 对写后端项目可是极其友好,学 TS 也是只看与 JS 的不同点即可,对于 JS 和 Java 基础不错的人,在很多方面真的都是似曾相识,理解起来简单很多。
现在的前端项目使用 TS 的越来越多,不管怎么样,还是了解一下为好。
安装
需要 Node 的环境,并且 Node 无法直接运行 TS 文件,所以使用下面的命令安装 ts 工具:
1 | npm install typescript -g |
然后就可以使用 tsc、ts-node 这些命令了。
常用类型
静态类型带来的好处不光是强约束,还有代码提示,因为类型固定,所以此类型的方法也都明确知道了,通过代码来说明:
1 | // 基础类型 |
像类似 const num: number
的定义叫做类型注解,也就是我们告诉 TS 它是什么类型;如果直接不写类型直接写值,那么会自动推断出类型来,当无法推断的时候,只能使用类型注解的方式了。
特殊的,如果返回值空,使用关键字 void 标注,最好还是标注一下,虽然有自动推断;其他特殊的有 never 表示永远不会执行到最后,例如抛出异常或者无限循环之类。
对于元组类型,可以理解为固定长度,固定顺序的数组,需要你精确控制各个类型的顺序,例如读取 CSV 的时候。
接口
概念不多说,跟静态语言里的基本一致,也是抽取共性:
1 | interface Point { |
接口非常灵活,能用接口就用接口,实在做不了再考虑类型别名之类。
其他的都差不多,都是给 class 实现用的,可以多实现,接口也可以继承。
这里补充一下,当你使用结构赋值直接传递的时候 TS 是强校验,就是说多一个少一个都不行;但是你通过对象引用来传递的时候,就不会那么强了,只要不少就行,可以多;亦或者定义中允许其他类型,例如上面的接口。
在 TS 编译过程中,接口会被消除,也就是接口只是在 TS 开发阶段帮助我们的工具。
类
TypeScript 除了实现了所有 ES6 中的类的功能以外,还添加了一些新的用法。
类在面向对象语言中非常常见,用来封装的好工具,继承、重写等概念之类的不多说,熟悉静态语言的都很熟了,在这里也都基本一样。
1 | class Animal { |
相比 Java,getter 和 setter 方法定义有些许不同,调用的时候也不需要按照方法的形式,不过总体来说基本一致。
静态方法可以直接调用,也可以通过实例调用,这一点上一致;可以定义抽象类,可以使用 public、private 和 protected 访问修饰符,不写的话默认是 public;并且可以通过 readonly 关键字定义只读属性。
在构造方法中通过类似 public/private tag: string
的语法可以快速定义类似上例中 name 的属性,也就是省去了定义和赋值的代码。
在 ES7 中,还加入了实例属性和静态属性,真是越来越熟悉了。
类型别名
在定义比较复杂的类型声明时,可以使用类型别名抽取,常用于联合类型(叠加):
1 | type Teacher = { |
别名与接口的一个区别也就是类型别名可以做联合类型,另外例如上面的将 name 设置为 string 的别名,接口就做不到,这也很符合别名这个概念。
枚举和泛型
枚举概念上与 Java 中类似,不过幸好远远没有 Java 中那么复杂;也终于看到了泛型,熟悉 Java 的我使用起来没有什么障碍:
1 | enum Status { |
枚举里的项默认就是连续数字,默认从 0 开始,如果你手动赋值,之前的从 0 开始,之后就按你赋值的数依次加一;当然你也可以手动设置为字符串类型。
TS 中的泛型类型推断要强得多,很多情况虽然可以不写,但是建议写上清晰一点;也可以使用 extends 和 supper 关键字,甚至可以这样写:T extends number | string
,灵活性是更多一些。
尤其注意 TS 中泛型的 keyof 用法,这相对来说是个新语法,就是展开对象的 key。
装饰器
使用之前需要在配置文件中开启 experimentalDecorators,这是 ES7 的东西,好用是好用。
首先,装饰器本身也是一个函数,毕竟 JS 中函数是一等公民;装饰器的参数是一个(作用在)构造函数,通过 @ 来调用,示例:
1 | // new 表示是构造函数,可简单理解为传入一个有构造的对象(类)返回一个装饰后的类 |
执行时机为类『加载』,同时只会执行一次,无论你 new 没 new 都会执行;一个类可以使用多个装饰器,顺序按照定义的顺序。
上面介绍的是类装饰器,同样的,也有方法装饰器,作用在类的 prototype(如果是静态方法就是构造函数):
1 | // 方法装饰器,target 表示类的 prototype,静态方法为构造函数 |
对于方法装饰器的调用时机,跟类一样,在定义的时候就完成了,不需要等到实例化的时候。
特别的,在 setter 和 getter 方法上,只允许一个有装饰器。
属性的装饰器跟方法装饰器基本一致,只是不再有 PropertyDescriptor 这个参数了,在装饰器里修改也是改的原型上的值,并不能修改实例的值。
而参数装饰器就是多个一个参数位置的参数,其他的基本一致, 我感觉跟注解挺像的。
命名空间
假设在 Web 中运用 TS 编译后的文件,如果就最基本的写法,很多类、函数之类的都会变成全局变量,这肯定是混乱糟糕的。
1 | // 命名空间 |
如果不使用 export 导出,默认是调用不到的,当命名空间互相引用的时候,建议是写清楚注释,虽然这并不是必须的。
但是可以看出这种方式并不优雅,为了可读性,建议使用 ES6 的模块化语法,这样就不需要使用 namespace 关键字了。
在 Web 场景下使用 ES6 的模块化打包为一个 JS 文件,可能需要使用 amd 标准,但是浏览器不支持,有需要引入其他的支持库,语法也很繁琐,正是如此 Webpack、parcel 之类的打包工具才崛起了。
在不同的文件中如果存在相同的命名空间,那么 TS 会做融合处理,也就是取并集。
附:配置文件
使用 tsc --init
会在当前目录生成 TS 的编译配置文件,当运行 tsc 的时候自动读取,示例:
1 | { |
在官方的文档中,各个配置项有详细的说明。如果你使用 ts-node 工具,也会使用这个编译配置文件的。
其他
我们约定使用 TypeScript 编写的文件以 .ts
为后缀,用 TypeScript 编写 React 时,以 .tsx
为后缀。
在 TS 中不能直接 import JS 库,如果需要,要额外安装对应的翻译库,这个 VSC 之类的 IDE 都有提示,会转换到一个 .d.ts
中间文件;这个文件仅仅是用 declare var
语法将对应库的 JS 语法合理化,也就是为了避免 IDE 的错误提示,即使不用也可以使用 parcel 之类正确的打包。
相比 Webpack,parcel 更快,也无需配置即可使用,做 demo 是很适合用的。
评论框加载失败,无法访问 Disqus
你可能需要魔法上网~~