附录 D:React 源码里的 Flow 类型——读源码时怎么忽略它
读 React 源码,你可能被这类东西劝退过:
function updateReducerImpl<S, A>(hook: Hook, current: Hook, reducer: (S, A) => S): [S, Dispatch<A>] {
先放心:这些类型标注全是"给工具看的注释",不影响运行时逻辑,阅读时整段跳过即可。 但要跳得舒服,得先知道几个记号。
先记住这一句:React 内部用 Flow(不是 TypeScript)做类型标注,语法和 TS 九成相似;
$FlowFixMe、__DEV__、: any是三类最常见的"干扰项",认出来就不会卡壳。
为什么是 Flow 不是 TypeScript
React 在 2014 年前后定型类型方案,当时 TypeScript 对 JSX 的支持还很弱,React 选择了 Facebook 自家(现已独立)的 Flow。十几年沿用下来,源码里的类型标注全是 Flow 语法。这不影响你:npm 包自带 .d.ts,你在项目里用 TypeScript 完全正常;只是读 React 源码时,你看到的是 Flow 而不是 TS。
Flow 与 TypeScript 速查对照
九成语法一样,只需记住几个 Flow 独有/不同的记号:
| Flow | TypeScript | 含义 |
|---|---|---|
const x: number | 相同 | 类型标注,通用写法 |
type Props = {...} | 相同 | 类型别名 |
mixed | unknown | 任意类型的"安全"版本 |
any | any | 放弃类型检查 |
$FlowFixMe | @ts-ignore | 抑制类型报错 |
// $FlowFixMe[comment] | // @ts-expect-error | 带原因的抑制 |
component(...props: any) | 相似 | 组件类型 |
(value: any) | (value as any) | 显式逃生舱(注意 Flow 在值后面) |
前几行和你写的 TS 没区别。重点是最后两行:Flow 的逃生舱是把 : any 写在值后面,而 TS 是 as any。
三个最常见的干扰项
① $FlowFixMe:源码里会出现 // $FlowFixMe[missing-this-annot] 这类注释。它的意思是"这里 Flow 报错了,作者确认没问题、手动压掉"。读到直接无视——它恰恰说明"这段不是重点"。
② __DEV__:这不是类型,是构建期变量。React 打包时用 Rollup 的 define 把 __DEV__ 替换成 true(开发版)或 false(生产版)。所以:
if (__DEV__) {
// 只在开发版运行的校验、警告代码
}
阅读时,__DEV__ 包裹的整块可以直接跳过,它们和核心逻辑无关,纯粹是开发期的安全网。
③ 泛型参数:updateReducerImpl<S, A> 这种 <S, A> 是泛型,等同于 TS 的 <S, A>。知道"S 是 state 类型、A 是 action 类型"这类约定,函数签名就基本能猜个大概。
阅读建议
- 类型标注整段忽略,只盯着运行时逻辑(函数体、变量赋值、调用链)。
- 遇到
$FlowFixMe、: any、mixed,当作"这里作者故意不深究类型",不用纠结。 - 想确认某个函数的参数形状时,再回来读签名,对照上面的对照表。
__DEV__块一律跳过,那是开发期校验,不影响你理解主线。
小结
- React 用 Flow,语法和 TS 九成相同,只多了
$FlowFixMe、: any等记号。 - 类型标注不影响运行时,阅读时整段跳过。
$FlowFixMe= 作者压掉的报错,直接无视;__DEV__= 构建期变量,包住的开发期代码跳过。- 你在项目里用 TS 完全不受影响,React 自带
.d.ts。 - 从此翻开任何一页源码,都不会再被类型标注绊住。