不,TypeScript 并不是浪费时间。
标题图片由Chris Leggat在 Unsplash 上提供。
另一天,又发生了一场激烈的“讨论”,关于 JavaScript 中的静态类型既是自切片面包以来最伟大的事物,也是整个人类所经历的最糟糕的事情。
让我们回顾一下最近 dev.to上一篇再次引发这场争论的帖子。我会尝试澄清一些误解,并希望能够以一种理性的方式看待这个问题。
在开始之前,我想修改一下我使用的一些术语,尤其是标题里的那个。我不再特指TypeScript,而是使用“类型化 JavaScript”这个术语。因为在类型化 JS 领域还有另一个工具Flow,我不想漏掉 Flow 用户。毕竟,我们的目标是在应用程序中强制执行类型健全性/安全性。
我想添加到词汇表中的另一个术语是“动态类型” JS,简称“动态”。尽管原帖试图让你相信这一点,但编写没有类型注释的 JS 代码并不意味着你的代码没有类型。用动态 JS 编写的字符串仍然具有 类型。 ,string
也是如此,你明白我的意思。你只是不必显式地表达这些类型。number
boolean
是的,短期内开始编写静态类型 JS 需要更长的时间……
我要坦诚地告诉你:短期来看,编写动态类型的 JS 速度更快。你可能会惊讶地听到 TypeScript 的拥趸这么说,但我是认真的。真的!
假设你正在编写一个 Node.js 库。如果你用动态类型的 JS 编写,那么你无需使用任何构建工具,就可以编写并发布库。就是这么快!对于只做一件事的小型库来说,由于速度更快,这样编写是最有效的。
但现在,假设你正在用 Node.js 编写一个完整的后端。它只是一个包含几个端点的微型 API 服务。你已经用 JavaScript 编写了身份验证系统、中间件和控制器。由于它是一个功能较少的小端点,所以你选择了普通的 JavaScript。
现在,想象一下,一个小小的 API 服务发展成一个功能齐全的平台 API,包含数千行代码,甚至可能有数万行代码。然后你意识到在某个端点中发现了一个 bug。天哪!单元测试没能发现它,所以你不得不花费数小时来追踪你的应用,寻找问题所在。你甚至可能需要设置断点,甚至进行老式的console.log
驱动调试。
然后,你发现了问题所在。还记得有一次你重构了那个中间件吗?你还同时修改了导出函数的名称。当然,你已经对那个中间件进行了单元测试,但你的单元测试仅限于该中间件。
然后你偶然发现了一个导入了该中间件的文件。当然。你修改了导出的函数名,但忘记重命名导入的函数了。
仅仅因为拼写错误或文件丢失就损失了数小时的生产力!
......但长期影响是真实存在的!
当然,您也可以使用 linting 工具检查不匹配的导入。但您可能还想重命名某个函数,以及更新所有导入该函数的文件的函数名称,只需单击一下按钮即可完成。毕竟,人难免会犯错,漏掉这类错误并不罕见。TypeScript 对快速重构和查找替换功能的支持可以帮助您解决这个问题。这样,您就可以将更多精力放在编写代码上,而不是手动进行毫无意义的查找替换。
像 TypeScript 和 Flow 这样的静态类型检查器也能通过在编译时检测此类错误来帮助减少代码中的 bug 数量。这方面也有一些统计数据证明。一般来说,在 JavaScript 代码中使用静态类型可以帮助避免提交代码中大约 15%的 bug。
当然,这会让使用 TypeScript 启动项目的速度慢得多,因为你需要在应用程序的早期阶段定义类型、接口等等。但我认为,以类型和/或接口的形式编写实现模型,可以让你尽早考虑应用程序的数据结构。
从长远来看,这极大地提升了应用程序的可靠性。而且,当你很好地运用这些类型时,很多情况下你甚至不需要类型,这要归功于 TypeScript基于控制流的类型分析。TypeScript 在大型应用程序上的优势,远超启动 TypeScript 项目所需时间的代价。
这是一项你未来会进行的投资吗?对我来说当然会,但我不会对你的应用做出任何预先的评判。这项投资是否值得,最终还是取决于你自己。
你可以逐步采用 TypeScript
也许你正在维护一个中大型应用程序,它已经用纯 JavaScript 编写。你想迁移到 TypeScript,但又担心那些红色的波浪线会困扰你。你会如何迁移你的代码呢?
关于迁移到 TypeScript 有很多指南。Basarat Ali Syed 的《TypeScript 深度探索》手册里有一本非常棒的指南。我还在这里写了一份全面的指南。
TypeScript 的另一个巧妙之处在于能够通过 JSDoc 注释推断普通 JS 文件的类型,因此如果您编写有效的 JSDoc 注释,并打开 JS 类型检查,那么您将很容易进行迁移。

虽然不可否认,TypeScript 的迁移体验确实不足。我之所以链接到第三方指南,是因为 TypeScript确实有官方的迁移指南,尽管它已经非常过时了。官方文档也严格假设用户对静态类型语言有所了解,所以我不推荐新手使用。
不过请放心,TypeScript 团队一直在努力重新编写文档,以及一本新的手册,希望能更逐步地教授 TypeScript。
但是动态的运行时值又如何呢?
诚然,TypeScript 团队明确表示,将静态类型检查扩展到运行时并非 TypeScript 编译器本身的目标。但实际上,我们仍然需要处理这些运行时边界。一个常见的例子是从 API 读取 JSON 输出,或者使用 HTTP 请求负载。
由于 TypeScript 社区拥有强大的支持,社区已经开发出针对此问题的优雅解决方案。您可以使用io-ts等工具来确定 TypeScript 中的运行时表示。flow -runtime是 Flow 的一个合适替代方案。
静态类型和测试齐头并进!
到目前为止,我们已经做了很多工作来确保应用的类型安全,并使用静态类型。尽管如此,静态类型仍然无法捕获某些错误。举一个简单的例子,测试切换按钮在点击时是否以正确的方式显示其相反的状态。
我是 Kent C. Dodds 的测试奖杯模型的忠实粉丝。在他的模型中,linting/静态分析/静态类型检查和单元测试都位于奖杯的“底部”。这意味着它们都是构建测试体验的不可或缺的部分,能够提升您对代码的信心。因此,我想说,静态类型和单元测试可以相辅相成,帮助您编写出更少 Bug 的代码!
让我们将上面的切换按钮示例写成代码。我们使用 TypeScript 作为静态类型,并使用 Jest + react-testing-library 来测试代码。
这是用 React 实现的该组件的一个示例。
import * as React from 'react'
interface ToggleButtonProps {
enabledText: string
disabledText: string
}
function ToggleButton({ enabledText, disabledText }: ToggleButtonProps) {
const [toggle, setToggle] = React.useState(false)
const handleToggle = () => {
setToggle(!toggle)
}
return (
<div>
<span>{toggle ? enabledText : disabledText}</span>
<button onClick={handleToggle}>Toggle</button>
</div>
)
}
export default ToggleButton
表面上看,静态类型似乎已经完成了它的工作。然而,如果我们仔细观察,就会发现我们可以为切换按钮设置自定义状态文本。当然,TypeScript 可以检查我们传递给enabledText
和disabledText
属性的字符串是否是字符串。但这只是成功的一半。
毕竟,如果我们分别将按钮的启用和禁用状态设置为'On'
和,我们希望它在禁用和启用时'Off'
能够正确显示。而不是反过来。'Off'
'On'
由于我们已经通过 TypeScript 检查了组件及其道具的类型,因此我们可以专注于测试按钮的行为。
以下示例使用Jest作为我们的测试运行器,并使用react-testing-library作为我们的 React 测试实用程序。
import * as React from 'react'
import { render, cleanup, fireEvent } from '@testing-library/react'
import ToggleButton from './ToggleButton'
describe('ToggleButton', () => {
afterEach(cleanup)
test('correctly renders the state of button', () => {
const { getByText, queryByText } = render(<ToggleButton enabledText="on" disabledText="off" />)
// Test the initial state of the button.
expect(getByText('Off')).toBeDefined()
expect(queryByText('On')).toBeNull()
// Fires a click event to the button.
fireEvent.click(getByText('Toggle'))
// Test if toggle state is correctly modified.
expect(getByText('On')).toBeDefined()
expect(queryByText('Off')).toBeNull()
})
})
这里发生了两件事。
- 静态类型通过检测类型错误并允许开发人员通过优秀的 IDE 工具自信地重构来提供健全性并改善开发人员体验。
- 单元测试使我们确信我们的代码能够按照预期的方式运行。
让我们清醒一下
原始帖子包含很多主观观点,这很遗憾,因为我很想听一些客观的理由来解释为什么静态类型不值得花时间。
我对原帖的反驳……也包含了很多主观观点。不过没关系!因为我写这篇文章的目的并非要论证一种技术“客观上”比另一种技术更好。我试图概述一种技术如何比另一种技术更利于用户,反之亦然。同时,我也试图找到两者之间的共通之处。或者至少,我试图……
与其构建煽动性的主观观点,伪装成“客观”“事实”,不如冷静地看待问题,理解某些工具的存在有其特定的意义。建设性的批评是一股强大的力量,能够提升我们所有人,无论你在这场辩论中站在哪一边。
由于我自己就是一名前端开发人员,所以我想选的一个很好的例子就是三大框架(Angular、React和Vue)之间无休止的争论,以及为什么一个比另一个更好。
例如,Vue 和 React 的开发者经常互相攻击,在 Medium 上发表一些毫无意义的评论文章,声称其中一个比另一个更好。我自己也是 React 的开发者,但我仍然理解尤雨溪在使用Vue 时遇到的问题,因此他所解决的问题成为了 Vue 框架的主要卖点。其中最突出的就是 Vue 的学习曲线和易上手性。
TypeScript 和 Flow 背后的开发者们非常聪明,他们解决了 JavaScript 编写中的一个痛点。他们希望编写的 JavaScript 代码能够在大型项目中扩展。他们解决这个问题的方法是提供一个静态类型超集,以确保类型的健全性和安全性,并通过静态类型强大的开发者工具来提高生产力。对一些人来说,这种方法效果很好。TypeScript 和 Flow 都在运行许多中大型项目(包括我所在的项目),我可以想象它们将如何帮助工程师编写出更少 bug 的代码。
TypeScript对你来说可能是浪费时间,但对我来说肯定不是浪费时间。
别误会,写纯 JS 也没什么错!也许你想在项目早期阶段更快地迭代,所以选择了纯 JS,而不是直接使用 TS。也许你想从一开始就尝试 TypeScript。这两种方式都可以。毕竟,只有你知道如何最好地开发你的应用。只有你知道如何提供更好的用户/开发者体验。
因为无论我们选择什么工具、语言和框架(或缺乏),最终都会得到一个可以运行的应用程序。
正确的?
文章来源:https://dev.to/resir014/no-typescript-is-not-a-waste-of-time-2hpk