Part 8 · パフォーマンス
45 / 64 章
React Compiler
上の 3 つが、そのうち要らなくなる話
この Part で 3 つの道具を見てきました。memo、useMemo、useCallback。
正直なところ、面倒だと思いませんでしたか。それは正しい感覚です。
React チームも同じことを考えました。 そしてこの 3 つを自動で書いてくれる仕組みを作りました。 それが React Compiler です。
何をしてくれるのか
React Compiler は、ビルドのときにコードを読んで、必要な場所に自動でメモ化を入れます。
// 自分で書くコード
function Counter() {
const [count, setCount] = useState(0);
const handleSave = () => save(count);
return <Child onSave={handleSave} />;
}
// コンパイラが変換したあと(イメージ)
// handleSave は count が変わったときだけ作り直されるつまり、この Part でやってきたことを、書かなくてよくなります。useCallback も useMemo もmemo も、自分で書く必要がなくなります。
with-callback.tsx
"use client";
import { useTrackDemoRender } from "@/components/lesson/demo-card";
import { RenderBox } from "@/components/lesson/render-box";
import { Button } from "@/components/ui/button";
import { memo, useCallback, useState } from "react";
// 子はさっきとまったく同じ
const Child = memo(function Child({ onSave }: { onSave: () => void }) {
return (
<RenderBox title="memo した子" tone="highlight">
<Button size="sm" variant="outline" onClick={onSave}>
保存
</Button>
</RenderBox>
);
});
export function WithCallback() {
useTrackDemoRender();
const [count, setCount] = useState(0);
const [saved, setSaved] = useState(0);
// 同じ関数を使い回す。依存配列が空なので、作り直されることがない
const handleSave = useCallback(() => setSaved((s) => s + 1), []);
return (
<div className="flex flex-col gap-4">
<p className="rounded-md border p-3 font-mono">
count: {count} / 保存した回数: {saved}
</p>
<Button size="sm" onClick={() => setCount((c) => c + 1)}>
count を増やす(子には関係ない)
</Button>
<Child onSave={handleSave} />
</div>
);
}では、この 3 章は無駄だったのか
まったく無駄ではありません。理由は 3 つあります。
1. まだ全部のプロジェクトで使えるわけではない
導入されていないプロジェクトはたくさんあります。 既存のコードを読むときにも、memo や useCallback は出てきます。読めなければ話になりません。
2. 効かない場所がある
コンパイラは、React のルールを守って書かれたコードにしか適用されません。 レンダリング中に値を書き換えているような、ルール違反のコードは対象外になります。
「なぜかコンパイラが効いていない」となったとき、 原因を探せるかどうかは、この 3 章を理解しているかで決まります。
3. 何が起きているか分からないまま使うことになる
自動化された仕組みは、うまく動いている間は考えなくて済みます。 問題はうまく動かなかったときです。
with-callback.tsx
"use client";
import { useTrackDemoRender } from "@/components/lesson/demo-card";
import { RenderBox } from "@/components/lesson/render-box";
import { Button } from "@/components/ui/button";
import { memo, useCallback, useState } from "react";
// 子はさっきとまったく同じ
const Child = memo(function Child({ onSave }: { onSave: () => void }) {
return (
<RenderBox title="memo した子" tone="highlight">
<Button size="sm" variant="outline" onClick={onSave}>
保存
</Button>
</RenderBox>
);
});
export function WithCallback() {
useTrackDemoRender();
const [count, setCount] = useState(0);
const [saved, setSaved] = useState(0);
// 同じ関数を使い回す。依存配列が空なので、作り直されることがない
const handleSave = useCallback(() => setSaved((s) => s + 1), []);
return (
<div className="flex flex-col gap-4">
<p className="rounded-md border p-3 font-mono">
count: {count} / 保存した回数: {saved}
</p>
<Button size="sm" onClick={() => setCount((c) => c + 1)}>
count を増やす(子には関係ない)
</Button>
<Child onSave={handleSave} />
</div>
);
}使ってみるには
# コンパイラ本体を入れて
bun add -D babel-plugin-react-compiler// next.config.ts — 設定はこの 1 行
const nextConfig: NextConfig = {
reactCompiler: true,
};React Compiler は 1.0 で安定版になり、 Next.js 16 では設定項目から experimental が取れました。もう実験的な機能ではありません。 (古い記事にある experimental: { reactCompiler: true } は Next.js 15 までの書き方です)
導入したら、既存の useMemo や useCallback は、動作を確かめながら少しずつ外していけます。 一度に全部消さないでください。 さきほど見たとおり、コンパイラはReact の決まりを守れていない場所では手を出しません。 そこだけは自分のメモ化が効いていた、ということがあり得ます。
with-callback.tsx
"use client";
import { useTrackDemoRender } from "@/components/lesson/demo-card";
import { RenderBox } from "@/components/lesson/render-box";
import { Button } from "@/components/ui/button";
import { memo, useCallback, useState } from "react";
// 子はさっきとまったく同じ
const Child = memo(function Child({ onSave }: { onSave: () => void }) {
return (
<RenderBox title="memo した子" tone="highlight">
<Button size="sm" variant="outline" onClick={onSave}>
保存
</Button>
</RenderBox>
);
});
export function WithCallback() {
useTrackDemoRender();
const [count, setCount] = useState(0);
const [saved, setSaved] = useState(0);
// 同じ関数を使い回す。依存配列が空なので、作り直されることがない
const handleSave = useCallback(() => setSaved((s) => s + 1), []);
return (
<div className="flex flex-col gap-4">
<p className="rounded-md border p-3 font-mono">
count: {count} / 保存した回数: {saved}
</p>
<Button size="sm" onClick={() => setCount((c) => c + 1)}>
count を増やす(子には関係ない)
</Button>
<Child onSave={handleSave} />
</div>
);
}理解できたか確かめる
確認クイズ
React Compiler を導入すると何が変わる?
確認クイズ
Compiler があるなら、memo や useCallback は学ばなくてよい?
with-callback.tsx
"use client";
import { useTrackDemoRender } from "@/components/lesson/demo-card";
import { RenderBox } from "@/components/lesson/render-box";
import { Button } from "@/components/ui/button";
import { memo, useCallback, useState } from "react";
// 子はさっきとまったく同じ
const Child = memo(function Child({ onSave }: { onSave: () => void }) {
return (
<RenderBox title="memo した子" tone="highlight">
<Button size="sm" variant="outline" onClick={onSave}>
保存
</Button>
</RenderBox>
);
});
export function WithCallback() {
useTrackDemoRender();
const [count, setCount] = useState(0);
const [saved, setSaved] = useState(0);
// 同じ関数を使い回す。依存配列が空なので、作り直されることがない
const handleSave = useCallback(() => setSaved((s) => s + 1), []);
return (
<div className="flex flex-col gap-4">
<p className="rounded-md border p-3 font-mono">
count: {count} / 保存した回数: {saved}
</p>
<Button size="sm" onClick={() => setCount((c) => c + 1)}>
count を増やす(子には関係ない)
</Button>
<Child onSave={handleSave} />
</div>
);
}この章のまとめ
- React Compiler は、メモ化を自動で入れてくれる仕組み
- 人間の勘より機械的なので、包み忘れも包みすぎも起きない
- ただし既存コードを読むときと効かないときの原因究明には、手で書く知識が要る
- 道具が変わっても、「React は同じものかどうかだけを見る」という仕組みは変わらない
with-callback.tsx
"use client";
import { useTrackDemoRender } from "@/components/lesson/demo-card";
import { RenderBox } from "@/components/lesson/render-box";
import { Button } from "@/components/ui/button";
import { memo, useCallback, useState } from "react";
// 子はさっきとまったく同じ
const Child = memo(function Child({ onSave }: { onSave: () => void }) {
return (
<RenderBox title="memo した子" tone="highlight">
<Button size="sm" variant="outline" onClick={onSave}>
保存
</Button>
</RenderBox>
);
});
export function WithCallback() {
useTrackDemoRender();
const [count, setCount] = useState(0);
const [saved, setSaved] = useState(0);
// 同じ関数を使い回す。依存配列が空なので、作り直されることがない
const handleSave = useCallback(() => setSaved((s) => s + 1), []);
return (
<div className="flex flex-col gap-4">
<p className="rounded-md border p-3 font-mono">
count: {count} / 保存した回数: {saved}
</p>
<Button size="sm" onClick={() => setCount((c) => c + 1)}>
count を増やす(子には関係ない)
</Button>
<Child onSave={handleSave} />
</div>
);
}