1

TypeScript 真的值得吗?

 4 years ago
source link: https://juejin.im/post/5e2556c96fb9a0301461ca40
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
2020年01月20日 阅读 2895

TypeScript 真的值得吗?

作者:Paul Cowan

翻译:疯狂的技术宅

原文:blog.logrocket.com/is-typescri…

未经允许严禁转载

img

在开始之前,希望大家知道,我是 TypeScript 爱好者。它是我在前端 React 项目和基于后端 Node 工作时的主要编程语言。但我确实有一些疑惑,所以想在本文中进行讨论。迄今为止,我已经用 TypeScript 写了至少三年的代码,所以 TypeScript 做得的确不错,而且满足了我的需求。

TypeScript 克服了一些很难解决的问题,并成为前端编程领域的主流。 TypeScript 在这篇列出了最受欢迎的编程语言的文章中排名第七位。

无论是否使用 TypeScript,任何规模的开发团队都应该遵循以下惯例:

  • 编写良好的单元测试——应在合理范围内涵盖尽可能多的生产代码
  • 结对编程——额外的审视可以捕捉到的错误远远超过语法错误
  • 良好的同行评审流程——正确的同行评审可以检查出许多机器无法捕获的错误
  • 使用 linter,例如 eslint

TypeScript 可以在这些基础之上增加额外的安全性,但我认为这在编程语言需求列表中应该排在后面。

TypeScript 不是健全的类型系统

我认为这可能是 TypeScript 当前版本的主要问题,但是首先让我定义 健全非健全 的类型系统。

健全的类型系统是能够确保你的程序不会进入无效状态的系统。例如,如果表达式中的静态类型为 string,则在运行时,要保证在评估它时仅获得 string

在健全的类型系统中,绝对不会在编译时或运行时产生表达式与预期类型不匹配的情况。

当然 TypeScript 有一定程度的健全性,并捕获以下类型错误:

// 'string' 类型不可分配给 'number' 类型
const increment = (i: number): number => { return i + "1"; }

// Argument of type '"98765432"' is not assignable to parameter of type .
// 无法将参数类型 '"98765432"' 分配给参数类型'number'。
const countdown: number = increment("98765432");
复制代码

100% 的健全性不是 Typescript 的目标,这是在 non-goals of TypeScript 列表中第 3 条中明确指出的事实:

...适用健全或“证明正确的”类型的系统。相反,要在正确性和生产率之间取得平衡。

这意味着不能保证变量在运行时具有定义的类型。我可以用下面的例子来说明这一点:

interface A {
    x: number;
}

let a: A = {x: 3}
let b: {x: number | string} = a; 
b.x = "unsound";
let x: number = a.x; // 不健全的

a.x.toFixed(0); // 什么鬼?
复制代码

上面的代码是 不健全 的,因为从接口 A 中能够知道 a.x 是一个数字。不幸的是,经过一系列重新分配后,它最终以字符串形式出现,并且以下代码能够编译通过,但是会在运行时出错。

不幸的是,这里显示的表达式可以正确编译:

a.x.toFixed(0);
复制代码

我认为这可能是 TypeScript 最大的问题,因为健全性不是目标。我仍然会遇到许多运行时错误,tsc 编译器不会标记这些错误。通过这种方法,TypeScript 在健全和不健全的阵营中脚踏两只船。这种半途而废的现象是通过 any 类型强制执行的,我将在后面提到。

我仍然需要编写很多的测试,这让我感到沮丧。当我第一次开始使用 TypeScript 时错误地得出结论:可以不必编写这么多单元测试了。

TypeScript 挑战了现状,并声称降低使用类型的认知开销比类型健全性更重要。

我能够理解为什么 TypesScript 会走这条路,并且有一个论点指出,如果健全类型系统能够得到 100% 的保证,那么对 TypeScript 的使用率讲不会那么高。这种观点随着 dart 语言的逐渐流行( Flutter 现已被广泛使用)被反驳了。健全性是 dart 语言的目标,这里是相关的讨论(dart.dev/guides/lang…

不健全以及 TypeScript 暴露在严格类型之外的各种转义符使它的有效性大大降低,不过这总比没有强一些。我的愿望是,随着 TypeScript 的流行,能够有更多的编译器选项可供使用,从而使高级用户可以得到 100% 的可靠性。

TypeScript 不保证运行时的类型检查

运行时类型检查不是 TypeScript 的目标,因此这种愿望可能永远不会实现。例如在处理从 API 调用返回的 JSON 时,运行时类型检查将是有好处的。如果可以在类型级别上进行控制,则不需要那么多的错误种类和单元测试。

正是因为无法在运行时保证所有的事情,所以可能会发生:

const getFullName = async (): string => {
  const person: AxiosResponse = await api();
  
  //response.name.fullName 可能会在运行时返回 undefined
  return response.name.fullName
}
复制代码

尽管有一些很棒的支持库,例如 io-ts,但这可能意味着你必须复制自己的model。

可怕的 any 类型和严格性选项

any 类型就是这样,编译器允许任何操作或赋值。

TypeScript 在一些小细节上往往很好用,但是人们倾向于在 any 类型上花费很多时间。我最近在一个 Angular 项目中工作,看到很多这样的代码:

export class Person {
 public _id: any;
 public name: any;
 public icon: any;
复制代码

TypeScript 让你忘记类型系统。

你可以用 any 强制转换任何一种类型:

("oh my goodness" as any).ToFixed(1); // 还记得我说的健全性吗?
复制代码

strict 编译器选项启用了以下编译器设置,这些设置会使事情听起来更加合理:

  • --strictNullChecks
  • --noImplicitAny
  • --noImplicitThis
  • --alwaysStrict

还有 eslint 规则 @typescript-eslint/no-explicit-any

any 的泛滥会破坏你类型的健全性。

必须重申,我是 TypeScript 爱好者,而且一直在日常工作中使用它,但是我确实认为它出现的时间还很短,而且类型还并不完全合理。 Airbnb 声称 TypeScript 可以阻止 38% 的错误。我非常怀疑这个数字的准确性。 TypeScript 不会对现有的做法有良好的提高。我仍然必须编写尽可能多的测试。你可能会不同意,不过我一直在编写更多的代码,并且不得不去编写类型测试,同时仍然会遇到意外的运行时错误。

TypeScript 提供了基本的类型检查,但健全性和运行时类型检查不是它的目标,这使 TypeScript 在美好的世界和我们所处的现状中采取折衷。

TypeScript 的亮点在于有良好的 IDE 支持,例如 vscode,如果我们输入了错误的内容,将会获得很好的视觉反馈。

vscode中的TypeScript错误

vscode中的TypeScript错误

通过 TypeScript 还可以增强重构的功能,并且在对修改后的代码进行编译时,可以立即识别出代码的改变(例如方法签名的更改)。

TypeScript 启用了良好的类型检查,并且绝对要比没有类型检查或仅使用普通的 eslint 更好,但是我认为它还可以做更多的事情。对于那些想要更多的人来说,还能够提供足够多的编译器选项。

欢迎关注前端公众号:前端先锋,免费领取 Vue、React 性能优化教程。

1

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK