Next.jsの最近の大きな目玉機能はReact Server Component(以下サーバーコンポーネント)です。パフォーマンスアップに有効だったり、gRPCだRESTだGraphQLだ論争を終わりにするServer Actionsなど盛りだくさんです。
一方で、サーバーコンポーネントはコーディング上の制約がいろいろあります。
- サーバーコンポーネントではhooksが使えない
- サーバーコンポーネントのソースからクライアントコンポーネントは
import
できるが逆はできない。レンダーツリーを工夫すればクライアントコンポーネントの下にサーバーコンポーネントを配置することは可能
サーバーコンポーネントでは非同期コンポーネントを作成でき、fetch
でサーバーから情報をとってきたり、DBアクセスした結果を利用できます。しかし、最近のモダンReactの場合、状態管理などはすべてhooksに寄せるので大きくコードの変更が必要になってしまいます。せっかくとってきたデータを全部propsでバケツリレーしなければならないとなると不便です。利用が必要な個所で個別にフェッチするという実装もありです。Next.jsはキャッシュして呼び出しを減らしてくれますが以下のようなケースではカバーしきれません
fetch()
以外の、たとえばDB接続での取得では重複リクエストになる- 利用したい箇所がクライアントコンポーネントの場合、最寄りのサーバーコンポーネントからバケツリレーが必要
コード量も増え、速度も遅くなったらうれしくないですよね。
しかし、後者の制約の脱出ハッチを使えばContextを利用してサーバーから取得した値を子供のコンポーネントに流してやれるのではないか、と思ったので試してみました。これが利用できればサーバーから取得する値はコンテキストに入れておいて、バケツリレーを回避できます。サーバーコンポーネントは根っこの方に近いコンポーネントで利用されますが、そこでコンテキストが使えれば既存のコードから大幅な書き換えを減らせるはずです。
Next.jsアプリケーションの作成
次のコマンドでさっと作成します。いろいろオプションを聞かれますが、サーバーコンポーネントを使うためにappルーターを選びます。このサンプルはTypeScriptにしているのでTypeScriptも選んでいますが、型を外せばJSでも動くでしょう。
npx create-next-app@latest |
さっそくトップページを書き換えていきます。まずダメだった例を紹介します。
// ダメだった例 |
// ダメだった例 |
// コンテキストから値を取得して表示するクライアントコンポーネント |
どこがダメでしょうか? 実行してみると、createContext()
の呼出はダメよ、とエラーになっています。このファイルに”use client”を足してもダメです。
このcreateContext()
を含むコードを全部クライアントコンポーネントに追い出せばOKです。次のステップでこれを直していきます。
OKなコード
まず、コンテキストを作成するだけではなく、それをラップした<Provider>
クライアントコンポーネントを作成します。
"use client" |
import { Child } from "./child" |
これでうまく表示されます。
レンダリングツリーとしては次のような形になります。Homeコンポーネントで、現在は即値ですがサーバーから取得した情報をProviderコンポーネントに渡し、このコンポーネントがコンテキストに格納します。Childコンポーネントはバケツリレーではなく、コンテキスト経由でユーザー情報を取得します。
ソースコードのインポートの依存関係は次の通りで、サーバー→クライアントの参照はあるが、クライアント→サーバーの参照はないため、React Server Componentの規約には反していません。
これでサーバーから取得した値もコンテキスト経由で子供のコンポーネントに参照させてあげられますね。もちろん、間に挟まるサーバーコンポーネントではuseContext
は使えないため、サーバーコンポーネントが利用したい値はフェッチで取るか、親からPropsで渡す必要があります。
まとめ
クライアントコンポーネントを経由することでコンテキストが利用できました。Reduxも、Recoilも、Jotaiも、すべて内部ではコンテキストを使って実現しています。コンテキストをクライアントコンポーネントとしてラップすることで使えるということは、これらの状態管理ライブラリもサーバーコンポーネントであろうと今まで通り使えるということです。
この手法を使えば既存のコードからの変更を小さくできるので、appルーターに移植するときに「とりあえず全部に”use client”をつけて回る、ということをしないでもよくなりますね。