01|如果第二篇讲的是 TypeScript 为什么能进场,那第三篇要回答的就是:为什么当年看起来同样很能打的 Flow,最后却没有把这场仗打成自己的

如果你是后来的前端开发者,

今天回头看类型史,很容易产生一种错觉:

好像 TypeScript 一出来,

事情就已经差不多定了。

可真实历史并不是这样。

TypeScript 真正长成今天这种“几乎默认”的位置之前,

JavaScript 世界里其实有过一个非常像样、甚至一度在很多人眼里更“贴身”的对手:

Flow

而且它不是那种边缘试验品。

它有几层特别强的初始优势:

  • 它来自 Facebook
  • 它和 React 语境天然更近
  • 它一开始就把自己讲成“别丢掉 JavaScript feel”
  • 它强调强分析、强推断、后台实时检查

更重要的是,

它出现的时间点也很有意思。

2014 年 Facebook 正式开源 Flow 时,

React 的影响力正在迅速上升,

前端社区也越来越认真地面对大型 JavaScript 应用的维护问题。

所以第三篇最值得讲的,绝不是:

“Flow 不行,所以输了。”

恰恰相反。

第三篇真正要讲的是:

Flow 当年不是不强,而是它最后输掉的,并不是单一的类型能力,而是整场生态入口之争。


02|Flow 最早看起来很有希望,不只是因为它来自 Facebook,而是因为它在姿态上特别会讨 JavaScript 世界喜欢

Facebook 2014 年发布 Flow 时,官方介绍里有两句特别值得注意。

一句是:

Flow is a new static type checker for JavaScript

另一句是:

we have designed Flow so developers can reap its benefits without losing the “feel” of coding in JavaScript

这两句放一起看,你会立刻发现它的路数非常聪明。

因为它其实在同时回应两边需求:

一边回应大型应用的现实压力:

  • 提前发现错误
  • 更安全地重构
  • 让大代码库更可维护

另一边又在安抚 JavaScript 世界最敏感的神经:

  • 不要失去 JavaScript 的手感
  • 不要突然变成另一门语言
  • 不要把日常写法全部推翻

也就是说,Flow 一开始根本不是走“重语言征服 JS”那条路。

它更像是在说:

我不想替换你的 JavaScript,我想更聪明地理解你的 JavaScript。

这点非常重要。

因为这让 Flow 一出来时,天然就带着一种很强的“内行感”。

它不像一个从外部来给 JavaScript 上课的人。

它更像一个从 JavaScript 世界内部长出来的、更懂日常写法的人。

这也是为什么当年很多人会觉得:

Flow 也许比 TypeScript 更“像给 JavaScript 自己准备的类型系统”。


03|Flow 真正强的地方,在于它一开始卖的不是“多写类型”,而是“少改写法,也能拿到静态分析收益”

Facebook 当年的官方文章里,把 Flow 说得很清楚:

它的目标不是逼你全面改写代码风格。

它希望通过更强的程序分析,

去适应开发者“already know and love”的那些 JavaScript idioms。

这和很多人对类型系统的刻板印象并不一样。

很多人一听静态类型,第一反应是:

  • 更多注解
  • 更多声明
  • 更多仪式
  • 更多提前规定

可 Flow 当时特别吸引人的地方,恰恰在于它试图反着来。

它更想卖的是:

我尽量多理解一点,你尽量少负担一点。

这种路线为什么会很有吸引力?

因为它特别符合 React / Facebook 那套工程文化。

那套文化长期很重视:

  • 代码可维护
  • 重构安全
  • 工具反馈快
  • 不想为了制度牺牲太多日常表达流动性

所以从产品气质上说,Flow 当时其实非常能打。

它不是靠“我是更正统的类型语言”来打。

它靠的是:

我是更像 JavaScript 内部升级的人。


04|而且别忘了,当年 React 世界本身就给了 Flow 一块天然主场

这也是第三篇必须讲清楚的一层。

因为 Flow 的优势从来不只是技术层面的。

它还有非常现实的阵地优势。

官方 React 旧文档里,专门有 Static Type Checking 页面,

明确把 FlowTypeScript 一起列为大型代码库的推荐路线之一,

同时又明确写到:

  • Flow 由 Facebook 开发
  • Flow 很常和 React 一起使用

这在当时是很有分量的。

因为 React 本身正在成为前端最重要的新中心之一。

而一门类型系统如果能和 React 这套组件世界形成天然亲和,

它就等于先拿到了一个极强的扩张跳板。

所以从历史位置上说,

Flow 并不是站在荒地里和 TypeScript 打。

它一开始站的,其实是一块很肥的地。

这也是为什么如果只用今天结果倒推,

会低估它当年的真实威胁。


05|但问题也恰恰从这里开始:Flow 很强,可它越强,越像一套需要你进入“它的项目制度”里才能充分发挥的系统

Flow 的官方使用方式,其实很能说明问题。

你要用它,通常需要:

  • flow-bin
  • 运行 flow init
  • 生成 .flowconfig
  • 在文件顶部写 // @flow
  • 通过 Babel 或其他工具把类型语法去掉

它当然是可用的。

而且它还有很强的后台检查体验。

可问题在于,这整套使用方式其实透露出一个很关键的事实:

Flow 更像一套项目制度。

它不是那种“你随手就已经部分进入”的系统。

它更像是在说:

“如果你愿意进入我的工作流,我会给你很强的分析能力。”

这和 TypeScript 的扩张方式有一个微妙但非常重要的区别。

TypeScript 更像:

“你先别管信不信我,你把现有 JavaScript 先带进来,我先帮你一点。”

而 Flow 更像:

“你来我的检查体系里,我给你高质量静态分析。”

这差异在小范围里不一定明显。

可一旦你把它放到整个生态 adoption 上,影响就会越来越大。

因为现代前端世界最后拼的,不只是“项目里能不能用”。

更是:

谁更容易成为默认入口。


06|Flow 后来真正吃亏的,不是它不够聪明,而是它太依赖“进入它的语境”这件事

这就是第三篇最关键的判断。

Flow 有很聪明的地方。

甚至它官方都明确把自己和 TypeScript 区分开,说它希望通过更复杂的分析去适应 JavaScript 习惯,而不是要求开发者先写很多注解。

可这种优势有一面非常漂亮,

另一面也有代价。

因为当你特别强调“我来理解你的 JavaScript”时,

你往往也更依赖:

  • 项目配置
  • 文件标记
  • 工具链协同
  • 特定生态里的最佳实践

而且随着 Flow 自身后来推进 Types-First,它也越来越明确要求模块边界上有足够注解,才能建立稳定的 file signatures。

这当然是为了性能和错误定位。

但它也说明一件事:

Flow 并不是没有制度成本,它只是把制度成本放在了另一种地方。

所以它的问题不是“不够先进”。

而是它的先进性,常常更依赖你已经处在它熟悉的工程环境里。

这就让它特别容易在强语境里很强,

却不一定最适合变成整个开放生态的默认入口。


07|而 TypeScript 这时占到的便宜,不只是类型设计,而是它更容易被脚手架、编辑器、库类型和混合项目一路收编

把前两篇和这一篇连起来看,差别就会越来越清楚。

TypeScript 一直在拼命降低 adoption 门槛:

  • 现有 JavaScript 就能进来
  • .js.ts 可以共存
  • 可以先 allowJs
  • 可以先 checkJs
  • 类型是可擦除的
  • 运行时现实不被改写

这意味着它特别适合被更大范围的工具链和脚手架收编成默认设置。

而一旦这件事开始发生,

胜负逻辑就会从“哪套系统更懂 JavaScript”慢慢转向另一件事:

哪套系统更容易成为大家根本不用单独决定、项目创建时就已经在那里的默认前提。

这时候 Flow 的处境就会开始变得微妙。

因为它哪怕在一些 React / Facebook 语境里依然很好用,

也不代表它更容易被整个生态打包成默认入口。

而后来的官方 React 新文档已经直接提供 Using TypeScript 页面,

Create React App 也长期提供官方 --template typescript 模板。

这类入口一旦形成,事情就会发生根本变化。

因为很多开发者根本不是通过“深入比较类型理论”做选择。

他们往往是通过:

  • 文档首页怎么写
  • 脚手架默认给什么
  • 团队现成模板带什么
  • 编辑器默认支持什么

来进入现实。

而在这件事上,TypeScript 后来越来越占上风。


08|所以 Flow 输掉的,本质上不是一场“谁更像正确类型系统”的比赛,而是一场“谁更容易成为开放生态默认空气”的比赛

把前面这些线叠一起看,第三篇最重要的判断可以压成一句:

Flow 最后输掉的,不是一场“谁更像正确类型系统”的比赛,而是一场“谁更容易成为开放生态默认空气”的比赛。

这句话里有几层意思。

第一,Flow 当年并不弱,甚至一度非常强,尤其在 Facebook / React 语境里很有主场感。

第二,它最吸引人的地方,恰恰是它看起来更懂 JavaScript 习惯,更想少改写法、多做分析。

第三,它的问题也恰恰和这套优势有关:

它更依赖进入自己的项目制度和语境,没那么容易被整个开放生态无缝收编成默认入口。

第四,TypeScript 则越来越适合被文档、脚手架、编辑器、库类型声明和混合迁移方案一起打包成“大家先用着再说”的默认前提。

所以理解 Flow 的兴衰,最不该用的写法就是:

“因为它不如 TypeScript,所以自然失败。”

更准确的写法应该是:

它在局部语境里非常强,但类型之争最后赢下来的,往往不是局部最强的那一个,而是最容易被整个生态拿去当默认入口的那一个。


09|Flow 输掉的,不只是比赛,它证明了当年并非只有一条路

TypeScript 江湖 的第三篇,最值得记住的,不是“Flow 输了”这句表层结论。

更值得记住的是:

Flow 的存在本身就说明,JavaScript 世界当年并不是只有 TypeScript 一条路。

而且它一度是一条非常像样、非常有威胁的路。

可也正因为有 Flow 这条线,

我们才更能看清楚后来真正决定胜负的东西到底是什么:

不是哪套系统看起来更酷,

也不只是哪个理论更漂亮。

而是:

谁更容易沿着 React、文档、脚手架、编辑器、项目模板和团队迁移路径,一点点长成整个生态几乎不用再讨论的默认现实。

从这个意义上说,

Flow 这条线最重要的历史价值,甚至不只是“它曾经和 TypeScript 竞争过”。

更是:

它帮整个前端世界把这场战争的真正判定标准暴露了出来。

类型之争最后拼的,从来不只是类型。

还拼:

生态。


编者注(事实核对):文中关于 Flow 于 2014-11-18 由 Facebook 正式开源发布、并被定义为“a new static type checker for JavaScript”的描述,主要依据 Facebook 官方工程博客文章 Flow, a new static type checker for JavaScript。关于 Flow 最初强调“在不失去 JavaScript 手感的前提下获得静态类型收益”“通过更复杂的程序分析适应开发者 already know and love 的 JavaScript idioms”,主要依据同一篇官方发布文,其中明确写到 “without losing the ‘feel’ of coding in JavaScript”“does not force you to change how you code”,并直接把自己与 TypeScript 这类更依赖显式注解的系统做区分。关于 Flow 的典型使用方式,如 flow init.flowconfig// @flow、与 Babel 配合去除类型语法,主要依据 Flow 官方 UsageGetting Started 与 React 集成文档。关于 Flow 后来 Types-First 架构需要更多模块边界注解以建立 signatures,主要依据 Flow 官方 File Signatures (Types-First) 文档。关于 React 旧文档曾并列推荐 Flow / TypeScript,而 React 新文档已单独提供 Using TypeScript 页面、Create React App 长期提供官方 --template typescript 模板,主要依据 React 官方 Static Type Checking 旧文档、react.dev/learn/typescript 与 CRA 官方 TypeScript 模板文档。正文将这些线索综合概括为“Flow 输掉的,最终不是局部能力,而是开放生态默认入口之争”,属于对工具路线、文档入口和生态扩张方式的综合判断。


关键人物速览

  • Jordan Walke:这里的重要性不在于他直接做了 Flow,而在于他所代表的 React / 静态类型 / 函数式语境,本身就是 Flow 能占到主场感的重要背景。
  • Avik Chaudhuri:Flow 官方发布文作者之一。理解 Flow 最初想解决什么问题、又如何把自己和 TypeScript 区分开,绕不开他。
  • Anders Hejlsberg:这里依然绕不开他,因为只有把 Flow 放在 TypeScript 对面看,才能看清后来到底是谁更容易沿着开放生态入口长成默认现实。

参考与延伸阅读

  1. Flow, a new static type checker for JavaScript
    https://engineering.fb.com/2014/11/18/web/flow-a-new-static-type-checker-for-javascript/

  2. Flow: Usage
    https://flow.org/en/docs/usage/

  3. Flow with React: Getting Started
    https://flow.org/en/docs/react/

  4. Flow: File Signatures (Types-First)
    https://flow.org/en/docs/lang/types-first/

  5. Static Type Checking - React (legacy docs)
    https://reactjs.org/docs/static-type-checking.html

  6. Using TypeScript - React
    https://react.dev/learn/typescript

  7. Adding TypeScript - Create React App
    https://create-react-app.dev/docs/adding-typescript/

  8. Getting Started - Create React App
    https://create-react-app.dev/docs/getting-started/

  9. TypeScript: JavaScript Development at Application Scale
    https://learn.microsoft.com/en-us/archive/blogs/somasegar/typescript-javascript-development-at-application-scale

  10. TypeScript Design Goals
    https://github.com/microsoft/TypeScript/wiki/TypeScript-Design-Goals