都在推 TS,但是你有考虑使用 TS 会产生什么问题嘛?

点击上方“蓝色字体”,选择“设为星标

做积极向上的前端人!



文章转载自港台作者:Ting-Shu Lin
https://engineering.linecorp.com/zh-hant/blog/benefits-and-costs-to-consider-when-installing-typescript


大家好,我是京都开发室的 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,期望带来的效益有:

  1. 静态型别检查,可以在编译时检测到部分的型别错误。

  2. 更好的编辑器功能,如重新命名、定义查询和自动补完等。

  3. 一目了然的型别宣告,提升程式码的可读性。

  4. 若是在开发供他人使用的函式库,提供型别定义会给使用者更良好的开发体验。

我认为 TypeScript 确实提升了编辑程式时的体验,在重构时也更加快速而安全。
这也是为什么许多人会推荐使用的原因。

但其效益使否有如预期,可再多加评估。

静态型别检查并不完全安全

静态型别检查可以减少动态型别检查,但并无法完全取代之。
若因为已有 TypeScript 的静态型别检查,而以为可以不用做再动态的型别检查反而会衍生更多问题。

例如使用如下的范例来取得资料:

const response = await fetch(dataSource);
const data: MyData = await response.json();

由于 response.json() 回传的是 Promise<any> 型态,在此不会有型别错误。

此后任何使用 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 现阶段是一项值得学习但并非必须使用的技术。
可应依据团队的需求与能力,谨慎评估再做出选择。



点击下方卡片,就能关注我啦👇




Vue3.0 高频出现的几道面试题
从入门时月薪2K到大厂40K,他是怎么做到的?
尤雨溪:TypeScript不会取代JavaScript
使用TypeScript两年后,还值得吗?
没有一个人可以瘦着过完春节
漫画 | 北上广打工人月薪五万回老家“ 注意事项
整理了500万+微信红包封面,速抢!
基于 Vue 的前端架构,我做了这 15 点
Vue 首页秒开实践指南
漫画 | 阿姨,我不想努力了
漫画 | 半夜,我差点揍了十年前的自己!

如果觉得这篇文章还不错,来个【分享、点赞、在看】三连吧,让更多的人也看到~

本文分享自微信公众号 - 前端布道师(honeyBadger8)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章