空雲 Blog

Eye catchNext.jsのAppRouterでReact.cacheを使う時の基本操作

publication: 2023/11/25
update:2024/02/21

AppRouterとReact.cache

AppRouterでStateを持たないServerコンポーネント同士のデータ共有は、React.cacheが基本となります。AppRouterの話題だとfetchのキャッシュやServerActionsの話に目が持っていかれますが、それよりも根本の話になります。

React.cacheの使い方を知らずにfetchやServerActionsを使いだすのは、useStateを知らないのに、いきなり外部通信系のプログラムを書こうとしているような状態です。

簡単なReact.cacheの使い方

簡単なサンプルでReact.cacheの使い方を紹介します。

app/context.ts

cacheは関数をキャッシュします。その関数が戻すのは、データである必要はありません。ContextAPIでもデータ操作用のdispatchを返すような構造を取ることがありますが、同じように書くことが出来ます。

1import { cache } from "react";
2
3export const createContext = <T>(v: T) =>
4 cache(() => {
5 let value = v;
6 return {
7 set: (v: T) => (value = v),
8 get: () => value,
9 };
10 });
11
12export const context = createContext(0);

app/Test.tsx

contextにはcacheの戻り値が入っています。キャッシュされたインスタンスを取得するには、context()で取り出す必要があります。一般的なサンプルでは引数を取る書き方ばかりですが、引数をキーとしてインスタンスを切り替える必要がなければ不要です。

1import { context } from "./context";
2
3export const Test = () => {
4 context().set(context().get() + 1);
5 return <div>{context().get()}</div>;
6};

app/page.tsx

1import { Test } from "./Test";
2
3const Page = () => {
4 return (
5 <>
6 <Test />
7 <Test />
8 <Test />
9 <Test />
10 </>
11 );
12};
13
14export default Page;

実行結果

{"width":"35px","height":"123px"}

普通に値が加算されるだけのように見えますが実はそうではありません。ページをリロードしても内容は1~4です。それすらも当たり前のように思えますが、これがモジュール変数をそのまま使った場合などは値が初期化されず、リロード後も加算され続けます。

解説

React.cacheは、コンポーネントツリーごとにインスタンスを生成します。つまりリロード後、新しいコンポーネントツリーが生成されると、別のメモリ空間が生成されるのです。サーバに対して複数のアクセスが同時に行われた場合も、値が混線することはありません。この構造を利用すればContextAPIのように、コンポーネントを飛び越えて値の共有が可能となります。

こちらは基本操作なので、ContextAPIのように本格的にデータを配る記事は別に出します。