注意: 筆者はMetaの関係者でもReact Compilerの関係者でもありません.本記事はあくまで外部から観測できる情報に基づいた考察です.
前回の記事では,React Compilerの登場によってReact Componentが「Just JavaScript」ではなくなったのではないか,という議論をした. 今回はその続きとして,Metaのエコシステム全体を俯瞰し,Reactが「言語」として進化していく様子をより深く考察する.
MetaとJavaScriptインフラ
Meta (旧Facebook)はJavaScriptのエコシステムに対して長年にわたり大きな貢献をしてきた.
Babel
Babelは,もともと6to5という名前で始まったJavaScriptのトランスパイラだ. ES6+やJSXなど,ブラウザがネイティブにサポートしていない構文を変換し,後方互換性を保つ役割を担っている.
JSX was created as an extension to ECMAScript with the purpose of designing a more concise and easy-to-understand syntax for building DOM tree structures and attributes. — The Role of Babel in React
Babelは独立したオープンソースプロジェクトだが,Reactのエコシステムにおいて不可欠な存在だ. そして,React CompilerもBabel Pluginとして実装されている.
Flow
FlowはMetaが開発しているJavaScriptの静的型チェッカーだ.
FlowもBabel Plugin (@babel/preset-flow)として実装されており,型注釈や独自構文を実行時に削除する役割を担っている.
そして今日の主題の一つとして,Flowは型チェッカーを超えて「言語」としての拡張を行っている.
FlowのComponent SyntaxとHook Syntax
2024年4月,FlowチームはComponent Syntaxを発表した. これはReactのプリミティブであるコンポーネントとフックに対する 一級言語サポート だ.
なぜ追加されたのか
Component Syntax was designed in coordination with the React team, and is already being used across Meta's codebases. — New Flow Language Features for React
These features bring improved ergonomics, expressiveness, and static enforcement for many of the Rules of React. — Flow Blog
つまり,FlowのComponent SyntaxはReactチームと連携して設計 され,Rules of Reactを静的に強制する ことを目的としている.
Component Syntaxの構文
通常の関数コンポーネントではなく,componentキーワードを使って定義する:
// 従来の関数コンポーネント
function Introduction(props: { name: string, age: number }) {
return (
<h1>
My name is {props.name} and I am {props.age} years old
</h1>
);
}
// Flow の Component Syntax
component Introduction(name: string, age: number) {
return (
<h1>
My name is {name} and I am {age} years old
</h1>
);
}
Component Syntaxでは:
propsが直接パラメータになる: ボイラープレートの削減
refの自動処理: 内部で
React.forwardRefでラップされる明示的なreturnが必須: すべてのブランチでreturnが必要
thisの使用禁止: コンポーネント内でthisは使えない
Hook Syntaxの構文
同様に,hookキーワードを使ってフックを定義する:
// 従来のカスタムフック
function useOnlineStatus(initial: boolean): boolean {
const [isOnline, setIsOnline] = useState(initial);
// ...
return isOnline;
}
// Flow の Hook Syntax
hook useOnlineStatus(initial: boolean): boolean {
const [isOnline, setIsOnline] = useState(initial);
// ...
return isOnline;
}
Flowが検出するもの
FlowのComponent/Hook Syntaxは,Rules of Reactに違反するコードを 静的に 検出する:
Propsのmutation検出: コンポーネント内でpropsを変更しようとするコード
条件付きフック呼び出しの禁止: if文内でのフック呼び出しをエラーとして報告
レンダリング中のrefアクセス検出: レンダリング中にrefを読み書きするコード
ネストしたcomponent/hookの検出: componentやhook内で別のcomponent/hookを定義するコード
Flow is able to detect mutations of props in a component or hook. Another pattern enforced in component bodies is that refs are not read or written to during render. — Flow Documentation
これが意味すること
FlowのComponent SyntaxとHook Syntaxは,Reactを「言語」として明示的に定義する試み だ.
従来,Reactコンポーネントは「JavaScriptの関数」として定義されていた.
しかし,Flowは新しいキーワード(component, hook)を導入することで,Reactのセマンティクスを言語レベルで表現 している.
これは前回の記事で議論した「ReactはReactだ」という結論を,より明確な形で示している.
React Compiler: 暗黙的なアプローチ
Flowが「明示的」に新しい構文を導入しているのに対し,React Compilerは「暗黙的」なアプローチを取っている.
React Compilerの歴史
React Compilerは約10年 にわたる研究の成果だ:
2017年: FacebookがPrepackでコンパイラを初めて探索
2021年: Xuan Huangが新しいアプローチの最初のイテレーションをデモ
2024年: ベータ版リリース,React Conf 2024で発表
2025年10月: React Compiler v1.0安定版リリース
参考: React Compiler v1.0 – React
Hooks: Reactを「言語」として定義した瞬間
ここで一つの重要な視点を提示したい.
Reactを「言語」として定義したのはHooksの登場時点だった.
HooksにはRules of Hooksという,JavaScriptには存在しない制約がある:
トップレベルでのみ呼び出す (ループ,条件分岐,ネストした関数内では呼ばない)
React関数からのみ呼び出す (通常のJavaScript関数からは呼ばない)
これらはJavaScriptの関数には存在しないルールだ. Hooksを使う関数は,もはや「JavaScriptの関数」ではなく 「Reactの関数」 になる.
つまり,Hooksの導入こそが,Reactを独自のセマンティクスを持つ「言語」として定義した瞬間だった と言えるかもしれない.
Hooksの設計とコンパイラ
そして,Hooksは最初からコンパイラによる最適化を前提として設計されていた.
React Compiler v1.0のブログには以下の記述がある:
Hooksの設計自体がコンパイラの将来を見据えて設計されました. — React Compiler v1.0
これは後付けの話ではない.2018年に公開されたHooksの公式ドキュメント には,既にこの意図が明記されていた:
As Svelte, Angular, Glimmer, and others show, ahead-of-time compilation of components has a lot of future potential. Especially if it's not limited to templates.
We wanted to present an API that makes it more likely for code to stay on the optimizable path. To solve these problems, Hooks let you use more of React's features without classes.
つまり,Hooksは最初からコンパイラによる最適化を前提として設計されていた.
Dan Abramovの発言
当時ReactチームにいたDan Abramovも,コンパイラによる自動メモ化について早い段階から言及していた.
2021年2月に公開された記事"Before You memo()"では,手動でのメモ化の手間について触れた上で,以下のように述べている:
This last step is annoying, especially for components in between, and ideally a compiler would do it for you. In the future, it might.
— Dan Abramov, Before You memo() (2021)
「将来的には,コンパイラがこれを自動で行うかもしれない」——この発言はReact Compilerが一般公開される3年以上前 のものだ.
Reactチームは長年にわたり,コンパイラによる自動最適化 という明確なビジョンを持っていた. これは,Reactが「Just JavaScript」から「独自のセマンティクスを持つ言語」へと進化していく過程が,計画的なものであったことを示している.
React Compilerが行うこと
前回の記事でも触れたが,React Compilerは以下のような最適化を自動で行う:
// 入力
function VideoTag({ heading, videos, filter }) {
const filteredVideos = [];
for (const video of videos) {
if (applyFilter(video, filter)) {
filteredVideos.push(video);
}
}
// ...
}
// 出力 (コンパイル後)
function VideoTab(t36) {
const $ = useMemoCache(12);
const { heading, videos, filter } = t36;
let filteredVideos;
if ($[0] !== videos || $[1] !== filter) {
filteredVideos = [];
for (const video of videos) {
if (applyFilter(video, filter)) {
filteredVideos.push(video);
}
}
$[0] = videos;
$[1] = filter;
$[2] = filteredVideos;
} else {
filteredVideos = $[2];
}
// ...
}
参考: Understanding Idiomatic React – Joe Savona, Mofei Zhang, React Advanced 2023
条件付きメモ化: useMemoでは不可能なこと
React Compiler v1.0のブログでは,手動のuseMemo/useCallbackでは実現できない最適化 の例が示されている:
export default function ThemeProvider(props) {
if (!props.children) {
return null;
}
// コンパイラは条件付きリターン後のコードもメモ化できる
// (手動の useMemo/useCallback では不可能)
const theme = mergeTheme(props.theme, use(ThemeContext));
return <ThemeContext value={theme}>{props.children}</ThemeContext>;
}
これは
useMemo/useCallbackでは実装できない,コンパイラ固有の能力です. — React Compiler v1.0
Rules of Reactの静的強制
React CompilerにはRules of Reactを検証するパス が含まれており,ESLintプラグインを通じて診断が提供される:
React Compiler's ESLint plugin helps developers proactively identify and correct Rules of React violations. The team strongly recommends everyone use the linter today. — React Compiler Beta Release
Metaでの成果
React Compilerは既にMetaの本番環境で運用されている:
Instagram: 全ページで平均3%改善
Quest Store: ロード時間が少なくとも4%改善,一部のインタラクションは2倍以上 高速化,初期ロードは最大12%改善
React CompilerとFlowの連携
ここで重要なのは,React CompilerはFlowのComponent/Hook Syntaxを明示的にサポートしている ということだ.
compilationModeオプション
React CompilerにはcompilationModeというオプションがあり,どの関数をコンパイル対象とするかを制御できる:
'infer'(デフォルト): コンポーネント命名規則(PascalCase)とフック検出(useプレフィックス) を使用'annotation':"use memo"ディレクティブでマークされた関数のみ'syntax': Flowのcomponent/hook構文を使用した関数のみ'all': すべてのトップレベル関数 (非推奨)
'syntax'モード
'syntax'モードを使用すると,Flowの専用構文で定義されたコンポーネントとフックのみがコンパイル対象となる:
// babel.config.js
{
compilationMode: "syntax";
}
// ✅ コンパイル対象:Flow component 構文
component Button(label: string) {
return <button>{label}</button>;
}
// ✅ コンパイル対象:Flow hook 構文
hook useCounter(initial: number) {
const [count, setCount] = useState(initial);
return [count, setCount];
}
// ❌ コンパイル対象外:通常の関数構文
function helper(data) {
return process(data);
}
これが意味すること
FlowとReact Compilerは 別々のプロジェクトではなく,連携して設計されている.
FlowのComponent SyntaxはReactチームと連携して設計された
React CompilerはFlowの構文を明示的にサポートしている
Metaの内部では,この二つが組み合わせて使用されている
つまり,Metaは「Reactを言語として定義する」という一貫した戦略のもとで,FlowとReact Compilerの両方を開発している.
Reactドキュメントとfunctionキーワード
ここで一つの観察を共有したい.
Reactの公式ドキュメントでは,コンポーネントの定義に 一貫してfunctionキーワード が使われている:
export default function Profile() {
return <img src="https://i.imgur.com/MK3eW3Am.jpg" alt="Katherine Johnson" />;
}
参考: Your First Component – React
アロー関数(const Profile = () => {}) についての言及は 一切ない.
なぜアロー関数ではないのか
Dan Abramovはoverreacted.ioで以下のように述べている:
it doesn't matter whether I use arrows or function declarations — Dan Abramov
技術的には,アロー関数でも関数宣言でもReactコンポーネントとして問題なく動作する.
しかし,Reactドキュメントがfunctionキーワードを使い続けていることには,いくつかの考察ができる.
1. FlowのComponent Syntaxとの親和性
FlowのComponent Syntaxはcomponentキーワードを使う:
component Introduction(name: string, age: number) {
// ...
}
これはfunctionキーワードを置き換える形で自然に導入できる.
もしReactドキュメントがアロー関数を推奨していたら,この移行は不自然になる.
2.構文の一貫性
functionキーワードによる宣言は,将来的に専用構文(FlowのComponent Syntaxのようなもの) へ移行する場合にも自然な形で置き換えられる.
アロー関数からの移行よりも,関数宣言からの移行の方が構文的に一貫性がある.
二つのアプローチの対比
FlowとReact Compilerは,同じ問題に対して異なるアプローチを取っている:
Flow:
方法: 新しいキーワード(
component,hook)明示性: 明示的 (新構文)
対象: Metaの内部コードベース
目的: Rules of Reactの静的強制
React Compiler:
方法: 既存の構文を変換
明示性: 暗黙的 (変換)
対象: 全Reactユーザー
目的: 自動メモ化 + Rules of Reactの検証
しかし,どちらも「Reactは独自のセマンティクスを持つ」という方向性では一致している.
Rules of React
ここで改めてRules of Reactを確認しておこう:
Rulesは,アイディオマティック(慣用的)で高品質なReactコードを書くためのルールです.単なるガイドラインではなく,これに従わないとバグが生じます. — Rules of React – React
主要なルール:
ComponentsとHooksはPure(純粋)である必要がある
冪等性: 同じ入力に対して常に同じ出力
Side Effectsはレンダリングの外側でのみ実行
props, state, Hookの戻り値は不変
ReactがComponentsとHooksを呼び出す
コンポーネント関数を直接呼ばない
Hookを通常の値として渡さない
Rules of Hooks
トップレベルでのみ呼び出す
React関数からのみ呼び出す
これらのルールは「React的意味論」を形成している. そして,FlowのComponent SyntaxもReact Compilerも,これらのルールを 静的に強制する ことを目指している.
結論: Reactは「言語」として進化している
前回の記事で私は以下のように結論した:
Reactは,Reactだ. 構文がJavaScriptと同じで,意味が異なる別の存在だ.
今回の調査で,この結論はさらに強化された:
FlowのComponent Syntaxは,Reactのセマンティクスを 明示的に言語として定義 している
React Compilerは,Reactのセマンティクスを 暗黙的にJavaScriptに変換 している
Hooksは最初からコンパイラを前提として設計されていた
Reactドキュメントは
functionキーワードを一貫して使用 しており,将来の構文拡張に備えている可能性がある
Reactが「Just JavaScript」であった時代は,徐々に終わりを迎えつつある. ReactはReactだ ——この言葉の意味は,今後ますます重要になっていくだろう.
これは私の単なるエッセイであり,React TeamやMetaの公式見解ではありません.