12k
All articles

3种非TypeScript的类型系统

比较 JSDoc + @ts-check、ReScript 和 Flow 作为 JavaScript 的 TypeScript 替代方案,涵盖类型安全、构建步骤和采用现状。

OpenReplay Team
OpenReplay Team
3种非TypeScript的类型系统

TypeScript是为JavaScript添加类型的默认方式,但并非唯一选择。如果你曾经为了在小项目中获得几个类型注解而添加构建步骤,或者看着tsc在大型代码库中缓慢爬行,你可能会想:有没有更轻量的方式来获得类型安全?有的,而且不止一种。

2026年,JavaScript领域最具可信度的三种非TypeScript类型系统分别是:JSDoc配合// @ts-checkReScriptFlow。其中一种无需构建步骤即可进行类型检查,一种提供比TypeScript更强的类型保证,还有一种是遗留检查器,仅适合用于维护现有代码。其他常见”TypeScript替代方案”文章中列出的选项(如Deno、Dart、Kotlin/JS)本质上是运行时或跨平台语言,而非叠加在JavaScript之上的类型系统。

本文是一篇面向已熟悉TypeScript的开发者的对比调研,旨在直接阐明各方案的权衡取舍:语言与检查器的区别、是否需要构建步骤、与TypeScript刻意不健全模型相比的健全性,以及当前的生态系统状况。

核心要点

  • JSDoc配合// @ts-check使用与TypeScript相同的语言服务对普通.js文件进行类型检查,无需构建步骤,无需.ts文件——该能力自TypeScript 2.3起已随版本发布。
  • ReScript是一门独立的编译至JavaScript的语言,具有源自OCaml家族的健全、完全推断式、名义类型系统,通过rescript.json进行配置,而非已废弃的bsconfig.json
  • Flow和ReScript均以OCaml编写,但Flow在外部的采用率已大幅下降,目前主要在Meta内部生产环境中使用。
  • 选择JSDoc用于渐进式、无构建步骤的类型标注;选择ReScript用于全新项目中追求最高健全性;选择Flow基本上仅限于维护现有Flow代码库。

2026年三种真正的TypeScript替代方案

真正意义上的”JavaScript类型系统”,要么对JavaScript源码进行类型检查(即检查器或注解层),要么将有类型的语言编译为JavaScript。这一定义涵盖了JSDoc加@ts-check、ReScript和Flow,但排除了Deno(一个恰好能运行TypeScript的运行时),以及Dart和Kotlin/JS(以JavaScript为多个编译目标之一的独立语言)。这些方案值得了解,但它们回答的是不同的问题。

作为基准背景:截至2026年中,TypeScript 7.0是当前稳定版本。它于2026年7月8日发布,是编译器的原生Go语言移植版,微软报告称其在完整构建上通常比TypeScript 6.0快8到12倍。TypeScript 6.0是基于旧JavaScript代码库的最后一个版本,而7.0保留了其类型检查行为。目前存在一个缺口:7.0发布时尚未提供稳定的程序化API,因此Vue、Svelte和Angular模板类型检查的工具支持需等待TypeScript 7.1,预计于2026年10月前后发布。请记住这一点:它会影响下文JSDoc部分的相关内容。

评估维度JSDoc + @ts-checkReScriptFlowTypeScript(基准)
本质注解层,由TS语言服务检查编译至JS的语言静态类型检查器编译至JS的超集
构建步骤无,类型为注释形式必需(.res.js运行时无需;Babel剥离类型必需
健全性与TS相同的不健全结构化模型健全、推断式、名义类型比TS更严格,但仍非完全健全刻意不健全,结构化类型
2026年生态上升中;无构建步骤的默认选择小而活跃外部采用率下降;Meta内部使用主导地位
适用场景渐进式类型标注,零工具链最高健全性,全新项目维护现有Flow代码库

JSDoc + @ts-check:无需构建步骤的TypeScript类型系统

本质: 基于JSDoc的类型标注允许你用结构化注释为普通.js文件添加类型,并使用与TypeScript完全相同的引擎——TypeScript语言服务——进行类型检查。这与作为文档生成器的JSDoc不同;在这里,注解驱动的是类型检查。TypeScript手册记录了--checkJs标志,用于报告.js文件中的错误,自TypeScript 2.3起可用。

如何采用: 在文件顶部添加一行注释,或在tsconfig中为整个项目开启两个标志。VS Code的JavaScript文档// @ts-check描述为在不全局启用的情况下,对少数文件进行类型检查的方式。

// @ts-check

/**
 * @typedef {{ id: number, name: string }} User
 */

/**
 * @param {User} user
 * @returns {string}
 */
function greet(user) {
  return `Hi, ${user.name}`;
}

对于整个项目,可省略逐文件注释:

{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true
  }
}

由于JSDoc类型是可擦除的注释,文件无需编译步骤即可在任何浏览器或运行时中作为标准JavaScript运行,这也是你可以逐文件渐进采用的原因。权衡之处在于:你完全继承了TypeScript的模型,包括其刻意的不健全性,且注解语法比.ts更为冗长。关于7.0时代的一个重要说明:微软为TypeScript 7.0从头重写了JavaScript类型检查,并移除了一些较少使用的标签,因此TypeScript 7.0不再识别@enum@constructor标签。

适用场景: 当你希望对现有JavaScript代码库进行渐进式类型安全改造,或维护一个发布纯JS的库,且不希望引入编译步骤时。

ReScript:健全、名义、完全推断的类型系统

本质: ReScript是一门独立的编译至JavaScript的语言,而非注解层,其类型系统继承自OCaml。TypeScript的类型系统刻意不健全:为了与JavaScript的运行时语义保持兼容,它接受某些不正确的程序。这正是ReScript所弥补的差距:它是完全推断式的,正如该项目所描述的,没有any,没有魔法类型,没有意外的undefined。与TypeScript的结构化类型不同——在TypeScript中,任何具有正确形状的对象都满足某个类型——ReScript的记录(record)和变体(variant)是名义类型,因此两个结构上相同的类型,除非显式声明,否则不可互换。

如何采用: 安装编译器并配置rescript.json,这是ReScript项目所需的唯一强制构建文件(在ReScript 11之前的版本中为bsconfig.json)。ReScript 12于2025年11月25日发布,完全移除了bsconfig.json支持,并将模块输出默认设置为ES模块。

{
  "name": "my-app",
  "sources": { "dir": "src", "subdirs": true },
  "package-specs": { "module": "esmodule", "in-source": true },
  "suffix": ".res.js"
}

你可以自由选择生成的JS文件的后缀;官方团队推荐使用.res.js.res.mjs,这也是官方create-rescript-app模板所采用的。一个最简模块示例:

let add = (a: int, b: int): int => a + b
let result = add(1, 2)
Console.log(result)

代价是真实存在的:它是一门有独立语法的不同语言,使用JavaScript库时需要编写互操作绑定,且生态系统远小于TypeScript。但ReScript在设计上考虑了渐进式采用:如果你想回归纯JavaScript,只需删除源文件,保留干净的JavaScript输出即可。

适用场景: 当你启动一个全新项目,希望获得最强的类型保证,并愿意承诺使用一门语言而非仅仅是注解时。

Flow:仍然存在,但已基本被取代

本质: Flow是一个开源的JavaScript静态类型检查器,由Facebook/Meta构建,以OCaml编写。与Flow一样,ReScript也源自OCaml,但两者目前在采用率上处于截然相反的两端。Flow是一个检查器,而非一门语言:你为.js文件添加注解,用// @flow标记它们,并在构建时通过Babel剥离类型。

// @flow
function add(a: number, b: number): number {
  return a + b;
}

2026年的采用现状: Flow在Meta内部数百万行JavaScript和React代码的生产环境中持续使用,但其外部生态系统已大幅收缩:库定义更少,教程更少,工具链支持也远不及TypeScript。社区讨论中曾提及将Meta相关主要项目从Flow迁移至TypeScript,但没有任何主要信息源确认具体计划。请将较早的”Flow前景光明”类对比文章视为过时内容;势头早已转向TypeScript。

适用场景: 实际上,仅限于维护现有Flow代码库。对于任何新项目,另外两个选项都是更稳妥的选择。

结论:根据场景选择合适的工具

当你希望以零构建工具链实现渐进式类型标注,并与纯JavaScript完全兼容时,选择JSDoc配合@ts-check。它是最被低估、也最实用的方案,本质上是TypeScript自己的检查器指向.js文件。当你从零开始一个新项目,追求最高类型健全性,并愿意接受独立语言和互操作绑定作为代价时,选择ReScriptFlow仅保留用于维护已用其编写的代码。如果你今天就想在不离开JavaScript的前提下获得类型安全,在一个文件中添加// @ts-check,看看错误如何浮现。

常见问题

可以在不将TypeScript作为依赖项安装的情况下使用JSDoc类型检查吗?

VS Code等编辑器内置了TypeScript语言服务,因此在.js文件中添加// @ts-check即可获得类型检查,无需任何npm install。若要在命令行或CI中运行相同的检查,则需要安装typescript包,并在启用allowJs和checkJs的情况下运行tsc。类型本身保留在JSDoc注释中,因此无论哪种方式,都不会向你发布的JavaScript中添加任何内容。

TypeScript的结构化类型与ReScript的名义类型有何区别?

TypeScript使用的结构化类型将任何具有正确形状的对象视为满足某个类型,因此两个字段完全相同的不相关类型可以互换使用。ReScript的记录和变体是名义类型,这意味着两个结构上相同的类型被视为不同类型,除非你显式声明它们之间的关系。名义类型防止了形似类型的意外替换,这也是ReScript类型系统健全而TypeScript刻意不健全的部分原因。

为什么Deno不被视为本文对比中的TypeScript替代方案?

Deno是一个JavaScript和TypeScript运行时,而非叠加在JavaScript之上的类型系统。它通过内置TypeScript编译器直接运行TypeScript,因此依赖于TypeScript而非取代它。真正的替代方案必须能够对JavaScript源码进行类型检查,或将有类型的语言编译为JavaScript。同样的理由也排除了Dart和Kotlin/JS,它们是以JavaScript为多个编译目标之一的独立语言。

升级到TypeScript 7.0会破坏现有的JSDoc类型化JavaScript项目吗?

大体上不会,但基于Go的TypeScript 7.0从头重写了JavaScript类型检查,并移除了一些较少使用的JSDoc标签,具体来说是@enum和@constructor,7.0不再识别这两个标签。依赖这些标签的项目需要将其迁移至受支持的写法。常用标签如@param、@returns、@typedef和@template仍然有效,因此大多数JSDoc类型化代码库可以无缝延续。TypeScript 7.0自2026年7月起已成为稳定版本,建议在该版本上运行你的项目,以查看具体的差异。

Open-source session replay

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.