腾讯IMWEB团队《未来可期的TypeScript》
IMWeb 团队隶属腾讯公司,是国内最专业的前端团队之一。专注前端领域多年,负责过 QQ 资料、QQ 注册、QQ 群等亿级业务。目前聚焦于在线教育领域,精心打磨 腾讯课堂、企鹅辅导及ABCmouse三大产品。
讲述了自己从一名 TypeScript 抵制者转变为支持者的心路历程,以及 TypeScript 在在线教育团队中的实践经验,并对团队新项目是否该引入 TypeScript、如何快速搭建、以及现有项目如何向 TypeScript 迁移提供策略性建议与方案。
2019年,越来越多的知名前端开源项目选择使用 TypeScript 做为其新版本的开发语言,TypeScript 的社区支持也日趋完善,VS Code + TypeScript 的组合拳让不少原本持怀疑,甚至抵制态度的前端开发者们不禁喊出“真香!”。
然而,无论是在大公司还是中小型公司中,依旧有相当多的前端工程师对TS有着理解偏差。TS的官方文档也过于技术性,并没有对TS的诞生原因,所要解决的问题等做细致的解释。所以本次演讲并不会关注太多TS本身的语法,高级用法这类话题,因为在这些方面,官方文档是更好的资料来源。而希望听众在分享结束后,能更明白TS成功的背后,究竟是解决了哪些问题?它的核心是什么?它的与众不同之处在哪?团队是否该引入TS以及如何引入?
1.1 官方定义
TS官网下的定义为“JavaScript that scales”,这句话如果翻译为中文不是一件简单的事。Scales意味着“规模扩大”。而对应的中文官网直接翻译成“JavaScript的超集",显然是有点文不对题的。
然而,这却恰好从两个不同的层面解释了什么是TypeScript。
英文版告诉你,TS与JS的能力区分在哪:即TS更能适应项目规模不断扩大的场景。
中文版告诉你,TS的本质是什么:即它是一种具有类型系统的JS的超集。
1.2 不仅仅是一门语言
如果你觉得定义本身比较空洞,想要更简单直接的了解TS是什么,不如看看它带来了什么。
作为一门语言本身来说,首先带来的是类型安全,可以类比java,c++。
另外,TS包含一个编译器,通过来你可以使用最新最稳定的JS特性,功能类似babel。
最后,最核心的部分,由于ts带来类型,所以你的工具,IDE可以更容易的理解你的代码。从而可以创造很多工具,来帮助你更高效的写高质量的代码。
所以总结起来TS不仅仅是一门语言,而是生产力工具。
说到TS,就不得不提JS,JS作为一门非常有争议的语言,发展到今天成为当下最流行的语言的过程是非常戏剧性的。
有很多必然,也有很多偶然。前端社区有关js的争论,也一天没有消停过,那我们就来看一下js的世界里有哪些有趣的话题。
2.1 开发者的偏见
在过去,很多人把JS当做是一个玩具语言。我们也会发现在某些场景,使用JS的人员并不一定是开发者(譬如早期qq空间通过脚本装饰页面)。更可以毫不夸张的说,很多开发者觉得,JS是一门,等真正需要用到的时候,再去随意学学,就能上手的语言。种种原因导致了JS在很多时候被滥用。
2.1.1 偏见的背后
当然JS本身的确是存在非常多的问题,尤其在早期的时候。没有模块、class、类型。还有一堆为人所诟病的语言设计,null、undefined、NaN等等。
2.1.2 开发者是如何应对JS的种种问题的
虽然JS有诸多糟粕,但是在浏览器里,你没办法摆脱他。“聪明”的开发者们想出了“用我喜欢的语言来编译生成JS”这样的点子,把JS仅仅当作媒介语言。你几乎可以找到任何语言的版本,这其中有像Google的GWT,微软的Script#都是被大规模在正式环境中应用的产品。
慢慢的,一些“更聪明”的开发者觉得这种方式有点不伦不类,不如彻底一点,干脆发明一些新的语言来取代JS。这里面最为出名的就是Google的Dart与CoffeeScript。
2.1.3 取代的结果如何
在google trend中,如果把CoffeeScript、Dart、TypeScript一起搜索。可以看到,从2014年至今,CoffeeScript与Dart基本没掀起任何波浪,而TS迎头直上。
2.1.4 从失败中吸取教训
为什么TS可以,而其他这些语言却没法成为主流呢?这其中主要有三个原因:
1. 没有严格遵从ECMAScript的规范。语法层面他们和JS是完全割裂的。譬如CoffeeScript用的是接近于ruby的语法,当使用这样的语言的时候,你会感觉你是完全在学一门新语言。有一定的学习成本。在ECMAScript规范不断演进的情形下,这类语言的语法就会越来越成为累赘。
2. 性能问题。这在早期的时候更为明显。图中的一段Dart代码,在用Dart的编译器转化为JS后,不做任何优化的情况下,居然产生了10000多行代码。这显然是难以接受的。
3. 生成代码的可读性差,没有办法回退。毕竟只是把JS做为媒介语言,JS的可读性不是这类语言的考量。这也意味着一旦项目启动,就没法很轻松的回退回原生JS。
2.2 来自开发者的傲慢
2.2.1 坊间流言
基于种种原因,开发者们越来越倾向于写原生JS。HTML5,Nodejs,还有像Angular,React,Vue这样优秀的前端框架又把JS的使用推向另一个高度。每一个JS开发者都无比的振奋。于是,渐渐的,我们又听到了另外一种声音。
1. 凡是可以用JS来写的应用,最终都会用JS来写--At Woods定律。
2. js是最好的语言,反正你们饶了一圈最后都会回来。
3. 微软出的东西,能行么?
2.2.2 被微软狠狠打脸
目前在github上热度前十的项目中,微软直接占了两席。而排在第八位的,也是社区中TS的周边产物。
StackOverFlow的报告,在程序员最喜欢的语言中,TS与Python目前并列第二,仅次于Rust。
Npm包的开发者中,高达62%的开发者在使用TS。
从这些数字中可以很明显的看出,当下TS已经取得了相当惊人的成就。
2.2.3 为何TS会取得如此成功
TS会取得如此成功主要有5个原因:
1. 对类型安全的诉求。无论在浏览器还是服务端,前端项目规模越来越大,越来越复杂。而规模越大,对静态类型语言的诉求就越强烈。
2. 严格遵守ECMAScript规范。与那些把JS当作媒介语言的语言是不同的。TS选择改进了JS,而不是取代它。学习ts语法并不会增加额外成本。
3. 采用Structural Typing而不是Nominal Typing。面向对象的语言,一般使用nominal typing(C++, Java, Swift),而函数式语言更习惯使用Structural Typing(OCaml, Haskell, Elm)。JS里面,你即可以使用面向对象,又可以使用函数式。但js的开发者通常更倾向于使用函数式编程。这种情况下,TS选择了使用结构类型,也更符合js开发者的编程习惯。
4. 强大的开发工具。正如我前面提到的,通过工具提高生产力才是TS的核心目标。TS本身提供了非常棒的工具支持,他的TS Server机制是非常有创造性的(后面会详述)。
5. Open Source, Open Development。TS是以100%的开放开发的方式来运营的。也就是说有关与ts的一切,都是对开发者100%透明的。在过去,当你给一门语言提一个bug的之后,可能等一两年才会出新版本,而到那个时候你才会发现,你提的bug可能根本没被修复。通过TS的roadmap你可以清晰的看到具体哪些bug会被修复,哪些feature会被新增,以及所有关于这些技术点的讨论。这样拉近了核心开发团队与使用者的距离,让TS的社区非常的活跃。
3.1 理由一:更少的Bug
3.1.1 研究与实践
要具体的量化这个减少bug的百分比,不是一件容易的事。
不过伦敦大学与微软研究院的一些学者,发表了一篇相当有影响力的论文。文中指出,15%的github 公开bug,是可以通过Typescript来规避的。而根据在线教育TS使用的实际经验,数据统计,我们发现超过30%的bug都可以通过类型安全来规避。
3.1.2 为什么达到30%
业务中的通常bug可以被分为两类,一类是类型错误(type error)引发的bug,这部分错误中的大多数是可以通过ts来避免的。
另一类是实现错误(spec error)引发的bug,比如对需求没有理解透彻,导致做出来的功能不符合预期,或者有逻辑bug。所以TS能减少的具体的bug比例数字,与类型错误引起的bug在项目总体中的比例有关。
3.2 理由二:提高生产力
3.2.1 如何提高生产力
静态类型,除了保证了类型安全。更重要的是,让你的电脑、软件可以更懂你的代码涵义。从而使得制作更好的生产力工具成为可能。生产力工具的提升,让开发者可以更加愉悦的写代码。从而最终提高生产力。
3.2.2 常用功能
此处主要通过demo展示带TS在VS Code中一些能力。代码提示、引用查询、自动import、代码即文档等。
3.2.3 生产力提高的背后
工具能力的提升只是在表面。相信很多人会有这样的疑问:没错在VS Code里写TS很方便。但是这种不都是IDE本身提供的吗?
事实上,这些能力其实是由TS本身提供的:
1. 与其他编译器不同,TS的编译器不是一个黑盒,而是完全对外开放的。
2. TS的编译器架构,包括了底层了核心Ts编译器,语言服务,并且通过ts server把他所支持的功能都通过api暴露出来。
3. 第三方的工具就可以通过这个api来直接使用ts语言服务的各种功能。
4. 通过这种方式,其他的ide,工具,也可以快速的集成TS的相关能力。这也是为什么即使你使用webStorm也可以获得与VS Code类似的TS开发体验。
3.3 理由三:端到端的类型安全
3.3.1 使用TS之前
图中是一个比较常见的架构。我们有运行在浏览器中的页面代码,它与中间的接入层nodejs服务进行通信,在后台我们还有一个C++服务,它nodejs服务之间用protobuf, 以rpc的形式来进行通信。
这个时候,一旦后台的协议发生更改,是一件非常棘手的事情,因为这里面没有任何类型追踪,我们需要在前端浏览器代码和nodejs代码中全局搜索对应的字段来修改,非常容易改错改漏。
3.3.2 使用TS之后
通过protobuf的TS插件,将请求、返回结构,方法等全都转换为TS。并且在页面代码与node服务代码中进行共享。
当proto协议文件发生更改的时候,只需要重新生成新的TS类型文件,无论是页面代码,还是node服务代码都会自动报错,提示开发者去做对应字段的修改。
3.4 理由四:强大的社区支持
3.4.1 第三方库
因为现如今任何一个前端项目都会引入大量的第三方库。对于这些第三方库,如果作者本身没有提供类型定义怎么办呢?
社区早就有了非常成熟解决方案,DenitelyTyped这个仓库目前是github排名第八的项目,有着超过10000名的代码贡献者,这里面有超过5000个前端库的类型定义。基本上你会用的,都已提供了类型定义。
3.4.2 框架反哺
我们也发现越来越多的,非常有影响力的前端框架的新版本都开始用ts来进行重写了。Angular 2, Vue3等等。
而这些社区中的开发者,都是开源社区最活跃的参与者,他们会将开发过程中遇到的ts的一些问题,通过PR或者反馈的方式再反哺到ts社区中,让ts的生态越来越好。
4.1 确定是否有必要引入
在正式引入之前,第一点还是需要明确一下,你的团队的具体需求。
1. 你的项目规模是怎样的?当然规模越大TS的帮助也就越大。
2. 项目是由存在多人合作或者经常有新人员加入呢?如果回答是yes,TS的使用,可以让你的代码更规范,让新人更快速的熟悉代码。
3. 是否需要长期维护?TS100%可以让你的项目结构更健康,更容易的去重构。
4.2 定点试验
1. 成立实验小组,让这部分人员先行,在项目中进行试点。
2. 这段时间内,要定期的对这部分人员进行问卷调查,看看他们的满意度。
3. 将遇到的疑问,譬如像第三方依赖怎么办之类的问题,梳理成FAQ,以便后续做更大范围的宣导。
4.3 构建迁移
构建迁移,其实非常的简单。通常我们需要处理的就是babel和webpack。这两者现在都利用了TS 的语言服务能力。
1. 如果你的代码需要用babel转换,你可以直接加上babel-ts插件,这样你编译出来的代码就自动会去除掉ts的类型,其他的功能还是走babel的转化逻辑。
2. 更多的时候,你会选择第二种,直接放弃掉babel,使用webpack的ts-loader直接对ts进行编译。
4.3.1 构建优化
TS3.0增加了project reference功能。
1. 你只需要在子文件夹里面,分别建一个新的tsconfg文件。在要引用的tsconfig中加上reference引用到另一个项目。在被引用的文件夹的tsconfig中增加composite为true。
2. 通过这样的配置,当ts在做类型检查的时候,只会检查当面项目的ts文件。保证了检查过程的高效。
3. 当你运行tsc build的时候,它也会进行增量的构建,只有当引用文件发生修改过期的时候,才会重新构建。
4.3.2 Lint迁移
1. 首先在你eslintrc.js文件中引入TS的parser与plugin。
2. Extends里面,建议就使用typescirpt-eslint这个包推荐的几种配置。
3. 接下来把你团队原来的eslint规则包引入进来,基本上你会发现原来所有eslint规则都是可以正常工作的。你也可以继续使用rules属性来覆盖继承的规则。
4.3.3 依赖迁移
1. 对于那些本身已经用TS来写的依赖,你不需要做任何改变。正常import进来就可以了。但是如果库本身没有自带类型,那你就需要使用denitelyTyped这个库,来把类型当作dev依赖装上就可以了。
2. 然而如果DT里面也找不到类型定义。那你需要做的就是,在d.ts里面declare一下这个module,可以自己给它添加类型定义,最终发布到DT上。
3. 更棘手的情况是类型找到了,但是有错。导致明明我传出正确的参数,还是会看到恼人红线,这里有两种做法:1. 依赖类型声明本身的写法,你可以在import 类型后,通过extends或者merge的能力对源类型进行扩展。2. 修改类型错误,给DT提PR。
4.3.4 代码迁移:混合模式
TS的迁移有两种方式,一种是混合模式,也就是说,把allowJs开关打开,然后一个个的把.js改为.ts,再修复类型。
4.3.5 代码迁移:激进模式
一次性的把所有js后缀全部改成ts。遇到比较难写的类型,或者文件过大的时候。你可以分别使用@ts-ignore或者@ts-nocheck(TS3.7)来忽略掉下一行的类型检查,或者整个文件的类型检查。
TypeScript之所以取得成功,有一个很重要的原因就是他的定位明确:让JavaScript变得更好,而非取代。
很多没有真正使用过TS的人,对TS的印象往往还停留在“静态类型的JS”。而忽略了“提高生产力”才是TS目前的核心设计目标。
56%的npm开发者都在使用TS,惊人的数字背后是确凿的事实。
所以你还在等什么呢?
如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:
点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)
关注我的官网 https://muyiy.cn,让我们成为长期关系