都在推介 TS,但 TS 真的有必要上吗?来看看老司机怎么说
大家好,我是京都开发室的Lin。
在工作与私人专案中使用TypeScript开发已约两年,
想就导入TypeScript时的经验与大家分享。
近年TypeScript是前端领域最热门的一项技术。
根据The State of JavaScript的资料,越来越多开发者选择使用TypeScript且评价也趋向正面。
许多团队会考虑「下个专案应该用TypeScript 开发」、「把现有JavaScript 专案改为TypeScript 有助于提升专案品质」。
然而,关于导入TypeScript 的成本与报酬,我认为需要非常谨慎地评估。
不可轻忽引入TypeScript 所需的成本
以个人目前的经验来说,在熟悉TypeScript 之后开发的效率约略和使用JavaScript 是相近的。
虽然因为类型声明使代码更长了一些,但也因为自动补完以及定义查询而减少了一些查询文件与的时间。
在需要重构时相比JavaScript 专案更是省时而安全。
但对初次使用TypeScript 开发的团队而言,我认为需要有相较于以JavaScript 开发多花费一至两倍时间的觉悟。
学习成本
如果认为「TypeScript 就只是JavaScript 标记了类型的版本,学起来应该不难」的话,很可能会错估了团队为适应TypeScript 所需付出的成本。
对于熟悉JavaScript的人来说,因为静态类型的限制导致一些原本在JavaScript中能轻易达成的设计在TypeScript中难以实现。
而也由于类型系统和其他静态语言的设计有许多差异。有Java、C#或Haskell等其他语言经验的人,反而会时常感到错愕。
除了语言本身以外,也需要学习现有的框架与工具库对应TypeScript 时有别于JavaScript 的使用方法。
实际上从入门到熟悉会花上不少时间。
工具库的类型支持
TypeScript虽然可以使用JavaScript工具库。
但是现有的JavaScript工具库未必有提供TypeScript所需的类型信息。
在严格的编译设定下,使用未提供类型信息的工具库将编译失败。
较为热门的工具库大多可以从社群维护的types中找到对应的类型套件。
然而第三方所提供的类型未必正确,也可能遇到原工具库更新了版本但相应的类型套件却没有更新的情况。
在类型错误或不够完善的情况下,反而可能被错误的类型误导而错用工具库或在类型处理上花费大量的时间。甚至可能会因为缺乏TypeScript 的支援,而不得不选择放弃使用某些实用的JavaScript 工具库。
而对于有提供TypeScript 支援的框架或工具库,在使用TypeScript 也可能需要额外的设定,甚至API 的形式也有所不同。例如:
在Vue 2 中,为获得较好的TypeScript 支援而使用 class component 形式。
Redux Tool 在使用TypeScript 时需要额外的设定,且部分API 使用了与JavaScript 不同的形式。
Emotion需要修改
import
路径以取得theme
类型。
无法良好处理部分JavaScript 的常用模式
在JavaScript中常使用不定参数、组合模式与高阶函式等方法的话,在使用TypeScript的时候将遇到很多阻碍。
有一些想处理相关问题的提议(#1213、#16936)至今(TypeScript 4.0)都还未得解决。
例如lodash 的 flow 函式,就是一个TypeScript 难以处理的例子。
这类的问题导致开发上失去了原本JavaScript所提供的灵活性。
使人需要选择放弃一部分的设计模式,或是花费大量心力在撰写极为复杂的类型声明。
在宽松与严格中的挣扎
TypeScript有许多编译选项。此外,ESLint等工具也有TypeScript对应的扩充规则。
常见的建议是「使用最严格的设定」,例如禁用 any
、使用严格的nullable检查等。
但实际经验上,对初用TypeScript而言,在最严格的编译选项下开发是十分艰难而耗时的。
也存在许多现阶段不使用 any
或转型就无法解决的问题。
但若团队中没有对TypeScript 经验充足的成员,要做出「何时可以放宽限制」的抉择就十分困难。
可能在开发时不断尝试解决类型却失败而不得不妥协。
又或在Code Review时耗费许多时间在研究与争论「是否有不使用 any
的其他解决方法」、「是否应该在这行加上 @ts-ignore
」等问题。
编译流程
TypeScript需要编译才能执行,且目前的编译器并不十分迅速。
这导致在变更代码到可以重新测试时需等待一段时间。
以TypeScript 开发的知名专案 Deno ,就曾因编译时间过长等相关问题,将部分TypeScript 代码改以JavaScript 撰写(#6793)。
TypeScript 的代码风格是否符合喜好
有些使用了TypeScript 后的影响很难说是好或坏。而是取决于开发者的习惯与偏好。
例如类型的声明,有助于理解类型信息,但也有人认为会影响代码逻辑的阅读。
使用了TypeScript 除了必须加上的类型声明以外,在使用一些函式时也会因迁就类型支援而出现较为复杂的形式。
为了类型支援而写出较不自然的代码
以用原生API遍历 post
中的所有 img
标签的 src
属性为例。
document.querySelectorAll('.post img').forEach((image) => {
console.log(image.src);
// Error: ^^^Property 'src' does not exist on type 'Element'
});
在第二行因为 image
得到的类型是 Element
,无法确保 src
属性的存在而会有类型错误。
为了解决类型问题,需要加上转型:
(document.querySelectorAll<HTMLImageElement>('.post img').forEach(image => {
console.log(image.src)
})
但这样的转型实际上并不安全,比如将 img
改成也 div
也不会获得警告。
document.querySelectorAll<HTMLImageElement>('.post div').forEach((image) => {
console.log(image.src);
});
有一个可以不用额外转型的技巧是使用 querySelectorAll('img')
,如:
document.querySelectorAll('.post').forEach((post) => {
post.querySelectorAll('img').forEach((img) => {
console.log(img.src);
});
});
这样的写法虽能获得更好的类型信息,但却不如原本JavaScript 的形式简明。
在TypeScript 专案中,常有因为类似原因而产生的代码。
类型声明占据了比代码逻辑更多的版面
假设使用了一个外部工具库提供的函式,并想将部分参数固定,包装成另一个函式。
使用JavaScript的范例如下:
import { sendSomething } from 'some-lib';
export function sendInJson(options) {
return sendSomething({
...options,
type: 'json',
});
但在TypeScript中,如果这个工具库并未 export
出参数介面。
为了正确宣告 sendInJson
的类型,会需要如下的写法:
import { sendSomething } from 'some-lib';
type SendSomethingOptions = Parameters<typeof sendSomething>[0];
type SendInJsonOptions = Omit<SendSomethingOptions, 'type'>;
export function sendInJson(options: SendInJsonOptions) {
return sendSomething({
...options,
type: 'json',
});
在上例中,一半左右的代码只是为了声明类型信息而存在。而当遇到更复杂的类型时,这个问题则愈加严重。
TypeScript 带来的效益值得吗
选择使用TypeScript,期望带来的效益有:
静态类型检查,可以在编译时检测到部分的类型错误。
更好的编辑器功能,如重新命名、定义查询和自动补完等。
一目了然的类型声明,提升代码的可读性。
若是在开发供他人使用的工具库,提供类型定义会给使用者更良好的开发体验。
我认为TypeScript确实提升了编辑代码时的体验,在重构时也更加快速而安全。
这也是为什么许多人会推荐使用的原因。
但其效益使否有如预期,可再多加评估。
静态类型检查并不完全安全
静态类型检查可以减少动态类型检查,但并无法完全取代之。
若因为已有TypeScript的静态类型检查,而以为可以不用做再动态的类型检查反而会衍生更多问题。
例如使用如下的范例来取得资料:
const response =等待fetch(dataSource);
const数据:MyData =等待response.json();
由于 response.json()
回传的是 Promise
型态,在此不会有类型错误。
此后任何使用 data
的地方都会相信他确实属于 MyData
。
实际上却有可能是不符合预期的。
此外,即使禁用了 any
也妥善处理了每个来自外部或原生API中的 any
类型。TypeScript的类型系统仍非完全安全的(#9825)。
好的编辑体验,不一定需要写TypeScript
以自动补完来说,写TypeScript能够获得良好的支援。
但其实就算只写JavaScript也能做到,当然正确性可能不如TypeScript完美。
例如用WebStorm 开发JavaScript 时就有不错的推导功能。你也可以写JavaScript 和JSDoc,利用TypeScript Language Server 来获得接近编辑TypeScript 的体验。
虽然方便性略逊一筹,但导入成本相较低廉许多。
有限资源下未必是最好的投资
「TypeScript 能提升我们专案的软体品质」应该很多团队是抱持这样的想法而导入TypeScript 的吧。
现实中软体品质难以做到尽善尽美,追求的是在有限的人力与时间下做到最好。
能够改善软体品质的手段很多,导入TypeScript只是其中一个选项,未必是最值得的投资。
在经验不足的情况下,错估TypeScript 导入成本甚至可能带来反效果。
例如,因耗费过多时间在TypeScript 相关的问题上而导致:
能用以设计架构的时程被压缩。
测试与除错的时间不足。
错估时程导致后半段的开发变得匆忙而草率。
没有余力处理其他静态分析工具所检测到的问题。
那这笔交易其实未必划算。
结论
在导入TypeScript 时,一些可以参考的评估项目有:
产品的开发时程与未来的维护规划。
团队成员是否已对TypeScript 足够熟悉。
团队是否偏好使用静态类型语言
所使用的技术链是否有良好的TypeScript 支援。
公司对于工程师在学习上愿意提供的资源。
专案是否作为工具库供他人使用。
现在前端技术圈有「使用TypeScript 是现代潮流」的趋势。似乎要积极导入才符合时代。
然而它能解决部分问题但也会带来一些额外的困难。
今天的TypeScript相较于两年已经进步了不少,但也还仍有许多会造成开发时困扰的问题。
也期待未来这些问题能逐步被改善使之更易于使用。
我认为TypeScript现阶段是一项值得学习但并非必须使用的技术。
可应依据团队的需求与能力,谨慎评估再做出选择。
最后
如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:
点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)
欢迎加我微信「qianyu443033099」拉你进技术群,长期交流学习...
关注公众号「前端下午茶」,持续为你推送精选好文,也可以加我为好友,随时聊骚。