React Server Components【RSC】とは?
RSCとは?
※ RSCはまだReactチームにより開発中であり、本番環境にはまだ推奨されていないことに注意してください。
つまり、実験段階にあるためこの機能の実装の詳細は今後変更される可能性があります。
React Labs: 私たちが取り組んでいること – 2022 年 6 月
React-Server-Components(RSC)は、Reactアプリのパフォーマンスを向上させるために開発された方法であり、サーバー側でレンダリングされるReactコンポーネントを作成することができます。
ただし、RSCはサーバーサイドレンダリング(SSR)ではなく、SSRの代替手段でもありません。
これらの名前には両方に「サーバー」という言葉が含まれているため、混同されることがありますが、実際には異なる機能です。
RSCを使用する場合、SSRを使用する必要はありません。ただし、SSRとRSCを組み合わせて使用することで、サーバーコンポーネントでサーバー側のレンダリングを行い、ブラウザで適切にハイドレートすることができます。
RSCはReactのSuspense機能ともうまく連携し、アプリ内の読み込み状態を管理することができます。
もしReactのSuspenseについて詳しく知りたい場合は、こちらのReact 18 Suspense 遅延読み込みとパフォーマンスを理解する 基礎記事を参考にしてください。
RSCを使用すると、定期的にコンポーネントを再取得することができます。
これにより、新しいデータがある場合にのみ再レンダリングするコンポーネントを含むアプリケーションをサーバー上で実行し、クライアントに送信する必要なコードの量を制限することができます。
サーバーコンポーネントを使用するためには、詳細な理解は必要ありませんが、その機能の基本的な動作原理については理解しておくことが重要です。
利点
RSC(React Server Components)の必要性について考えてみましょう。
従来のReactアプリでは、すべてのコンポーネントがクライアント側で実行され、ブラウザ上で動作していました。これにより、状態の追跡やイベントに応じたReactツリーの変更、効率的なDOMの更新が可能となりました。
では、なぜわざわざサーバー上で何かをレンダリングする必要があるのでしょうか?その利点を見てみましょう。
RSCは、バンドルサイズを縮小することで、開発者がクライアントのコストを心配することなく助けてくれます。
任意のサイズのプラグインを使用でき、コンポーネントはサーバー側で処理されるため、クライアントのブラウザはそれらをダウンロードする必要がありません。
通常、useEffectフックを使用したAPI呼び出しは問題ありません。
しかし、データフェッチのアプローチでは、レンダリングに時間がかかることがあります。さらに、バンドルサイズの問題もあります。バンドルサイズが大きいと、ダウンロードに時間がかかります。
RSCはこの問題を解決するのに役立ちます。
JavaScriptバンドルのサイズが小さくなり、ユーザーエクスペリエンスが向上します。サーバーコンポーネントはバンドルに含まれず、ブラウザによってダウンロードされないため、バンドルサイズには影響しません。
さらに、サーバーはデータベース、GraphQLエンドポイント、ファイルシステムなどのデータソースに直接アクセスできます。
そのため、RSCはサーバー上で実行されるため、ネットワーク呼び出しを待つ必要がありません。これにより、レンダリングプロセスが劇的に高速化されます。これは、ブラウザよりも高速にデータを取得できます。
サーバーでのレンダリングには、ブラウザよりも優れた利点があります。
以上が、RSCの必要性とその利点についての考えです。
サーバーとクライアントのコンポーネント分割と仕組み
まず、package.jsonファイルにはいくつかの実験的なバージョンを持つReactのパッケージが含まれています。
これらは、RSC(React Server Components)を実現するためのものであり、また、入出力システムとの連携に使用されるラッパーのパッケージも含まれています。
このパッケージ群は通称「React IO Libraries」と呼ばれています。
"react": "0.0.0-experimental-3310209d0", "react-dom": "0.0.0-experimental-3310209d0", "react-fetch": "0.0.0-experimental-3310209d0", "react-fs": "0.0.0-experimental-3310209d0", "react-pg": "0.0.0-experimental-3310209d0", "react-server-dom-webpack": "0.0.0-experimental-3310209d0",
React IO Librariesをインストールするには、プロジェクトのルートディレクトリで以下のコマンドを実行します。
npm install react react-dom react-fetch react-fs react-pg react-server-dom-webpack
これにより、package.jsonファイルにReact IO Librariesの依存関係が追加され、プロジェクトに必要なライブラリがインストールされます。インストールが完了したら、React IO Librariesをプロジェクトで使用することができます。
※注意:インストールするパッケージのバージョンは、具体的な要件やプロジェクトの状況によって異なる場合がありますので、適切なバージョンを選択することが重要です。また、バージョンが更新されている可能性があるので、最新のバージョンを確認することも忘れずに行ってください。
RSCを使用する際には、以下の3つの異なるファイル拡張子を使用することができます
.client.jsx
: クライアントコンポーネント。ブラウザ上でのみレンダリングされ、サーバーコンポーネントを使用できません。これらは通常、アプリケーションのインタラクティブな部分を表示するために使われます。ファイル名の拡張子は.client.js
または.client.jsx
です。
.server.jsx
: サーバーコンポーネント。サーバー側で実行されるコンポーネントであり、サーバーコンポーネントはサーバーコンポーネントをインポート可能ですが、クライアントコンポーネントはサーバーコンポーネントをインポートできません。サーバーコンポーネントは、サーバー上のデータベースやファイルシステムなどのデータソースに直接アクセスできるため、データ取得プロセスが高速かつ効率的に行われます。ファイル名の拡張子は.server.js
または.server.jsx
です。
.jsx
: 共有コンポーネント。クライアントとサーバーの両方でレンダリングできるコンポーネントです。サーバーコンポーネントとクライアントコンポーネントが共通の機能を持つような場合に使用されます。共有コンポーネントは状態を持つことができず、通常の.js
または.ts
拡張子を使用します。
アプリケーションを起動すると、server/api.server.jsスクリプトを使用したNodeサーバーが実行され、scripts/build.jsスクリプトによるクライアントサイド側のReactバンドル用Webpackがビルドされます。
サーバーコンポーネントは特別な形式のストリーミングとしてクライアントに返され、HTMLではなく事前にレンダリングされたコンポーネントが返されます。
サーバーコンポーネントはデータを再レンダリングするために複数回再取得できます。
以下は、サーバーコンポーネントの例です。
// MyComponent.server.jsx export default function MyServerComponent(props) { // サーバー上のデータベースからデータを取得する例 const data = fetchDataFromServerDatabase(); return ( <div> <h1>Server Component</h1> <p>{data}</p> </div> ); }
そして、以下はクライアントコンポーネントの例です。
// MyComponent.client.jsx export default function MyClientComponent(props) { // ブラウザ上でのみ実行される処理の例 const handleButtonClick = () => { alert('Button clicked!'); }; return ( <div> <h1>Client Component</h1> <button onClick={handleButtonClick}>Click Me</button> </div> ); }
以下は共有コンポーネントの例です。
// MyComponent.jsx export default function MySharedComponent(props) { // サーバーとクライアントの両方で実行される処理の例 const sharedFunction = () => { // 共通の処理 }; return ( <div> <h1>Shared Component</h1> <p>Shared content here</p> </div> ); }
これらの例は、サーバーコンポーネント、クライアントコンポーネント、共有コンポーネントがどのように定義され、それぞれの特性が異なることを示しています。
これらのコンポーネントを適切に組み合わせることで、効果的なReactアプリケーションを構築することができます。
分かりやすい例で仕組みを見て見ましょう。
おさらいをしますと、React IO Librariesには、サーバーコンポーネントとクライアントコンポーネントの両方があります。
これらのコンポーネントは、サーバーサイドとクライアントサイドで異なる役割を果たします。
サーバーコンポーネントはプライベートデータをフェッチすることができ、ファイル名は.server.js
で終わります。一方、ユーザーと対話するためのコンポーネントは、.client.js
で終わるファイルで定義されます。
効率的なコーディングのために、サーバーコンポーネントとクライアントコンポーネントを適切に分割することが重要です。
データにアクセスする必要があるコンポーネントは.server.js
に、ユーザーと対話するコンポーネントは.client.js
に配置します。
以下は、サーバーとクライアントコンポーネントの簡単な例です
// ClientComponent.client.jsx export default function ClientComponent({ children }) { return ( <div> <h1> Hello from the client! </h1> {children} </div> ); } // ServerComponent.server.jsx export default function ServerComponent() { return <h2> Hello from the server! </h2>; }
これらのコンポーネントは、ClientComponentがServerComponentを子要素として含むように組み合わされています。
import ClientComponent from './ClientComponent.client'; import ServerComponent from './ServerComponent.server'; const OuterServerComponent = () => { return ( <ClientComponent> <ServerComponent /> </ClientComponent> ); };
このように、サーバーコンポーネントはReactのクライアントコンポーネントツリーと組み合わせて動作します。
サーバーコンポーネントはサーバー上で実行され、クライアントコンポーネントはクライアント上で実行されるため、それぞれが異なる役割を持ちます。
一つ注意するべき点は、クライアントコンポーネントはサーバーコンポーネントを直接インポートしてレンダリングすることはできないということです。
なぜなら、サーバーコンポーネントにはブラウザで動作しないコードが含まれている可能性があるためです。
サーバーコンポーネントがブラウザバンドルに取り込まれることになると、効率的なクライアントサイドのバンドルサイズを保つことが難しくなります。
React IO Librariesの仕組みを理解するためには、公式のReactドキュメントやデモを視聴して試してみることが大切です。
詳細な情報は、以下のReact公式ブログで確認することもおすすめします。
Introducing Zero-Bundle-Size React Server Components – React Blog
適用ルール
サーバーコンポーネントは、サーバー上で要求ごとに1回実行されるため、状態を持つことができません。
そのため、useStateやuseReducerなどのReactフックはサポートされていません。
また、同様に、RSC(React Server Components)ではuseEffectやuseLayoutEffectなどのレンダリングライフサイクルメソッドも使用できません。
さらに、イベントのハンドリング(例:onClickやonChange)も扱うことができませんので、これらの機能を利用することはできません。
また、サーバーコンポーネントはブラウザ専用のAPIを使用することができません。
基本的には、ポリフィルをサーバー上に実装しない限り、DOMなどのブラウザ専用のAPIは利用できません。
同様に、ブラウザのみのAPIに依存するユーティリティ関数やカスタムフックも使用できません。これらのAPIを使用するためには、クライアントコンポーネントを使用する必要があります。
従来のReactコンポーネントは、ブラウザ側でレンダリングされるため、これらをクライアントコンポーネントと呼びます。
一方、サーバーコンポーネントの特徴は、ブラウザに送信される前にサーバーサイドでレンダリングされることです。
これにより、サーバーコンポーネントではサーバー上の機能を利用してデータを取得したり、事前レンダリングを行うことが可能になります。
サーバーコンポーネントは、サーバーサイドとクライアントサイドの機能を効果的に組み合わせることで、高度なアプリケーションの構築が可能となります。
これらの制限が、サーバーコンポーネントを使用する際に注意すべきポイントとなります。
特に、クライアントサイドの動的な振る舞いやブラウザのDOM操作が必要な場合には、サーバーコンポーネントでは制約が大きいと言えます。
ただし、欠点として捉える一方で、サーバーコンポーネントは重要な利点も持っています。
サーバーコンポーネントの特長的な利点は、サーバーサイドでのレンダリングによる高速な初期読み込みや、データの事前取得によるパフォーマンス向上が挙げられます。これにより、ユーザーエクスペリエンスの向上やSEO(検索エンジン最適化)の強化など、重要なメリットもあります。
つまり、サーバーコンポーネントは特定のユースケースにおいて非常に価値のあるツールであり、プロジェクトの要件や目標に応じて、サーバーコンポーネントとクライアントコンポーネントを適切に組み合わせて利用することが重要です。
SSRとの違い
サーバーサイドのレンダリングは、初回のページ読み込みを高速化することを主眼においた技術です。
SSR(サーバーサイドレンダリング)は、サーバー上でページ全体をレンダリングし、HTMLデータをユーザーに送信する技術です。これにより、初回ページロード時にクライアントがHTMLを受け取り、画面が早く表示される利点があります。
一方、RSC(React Server Components)は全く異なるアプローチを持っています。RSCはクライアントの状態を失うことなく、いつでも再レンダリングできる技術です。SSRアプリでも再レンダリングは可能ですが、その際には完全な新しいHTMLページの再レンダリングが行われ、アプリの状態は初期化されてしまいます。
RSCは、ツリー内のどのコンポーネントからでも関数やデータベースなどのサーバーサイドのデータソースにアクセスできます。一方で、SSRでは特にNext.jsではページのトップレベルでのみ機能する組み込み関数であるgetServerProps()を使用する必要があります。
最も重要な違いは、RSCではコンポーネントが依然としてクライアント側のコンポーネントであるという点です。それにもかかわらず、必要な依存関係はすべてダウンロードされるため、RSCを使用する場合でもクライアント側のパフォーマンスは向上します。
要約すると、SSRは初回ページロードの高速化を重視する一方で、RSCはクライアントの状態を保ちつつ再レンダリングを可能にし、サーバーサイドのデータソースに柔軟にアクセスできる特長的な技術です。
最後に
RSC(React Server Components)は現在実験段階であり、本番環境での使用にはまだ完全に準備が整っていません。
ただし、将来的にはReactの重要な要素として成長する可能性があります。
現時点では、RSCはNode.jsサーバー環境でのみ利用できますが、今後の展望は非常に魅力的です。
RSCを学習しておくことは賢明であり、将来的にこの技術が注目されることは間違いありません。
ただし、RSCを活用したアプリケーションの開発には慣れが必要であることを覚えておくべきです。
一部の開発者が述べているように、RSCはWebの革命をもたらしたとは言い難いかもしれません。データの取得がサーバー側で行われるため、転送するデータ量が増える可能性があります。
SSRとRSCは両者ともサーバー上でのReactレンダリングを伴いますが、本質的に異なるアプローチを取っています。RSCは新たな開発であり非常に興味深いものであり、私もRSCについてさらなる学習を楽しみにしています。
この記事がRSCの理解をより明確にするのに役立てば幸いです。
RSCは新しいテクノロジーであり、Next.jsやRemixなどのフレームワークと統合する可能性もあります。
以上が、本日の内容となります。引き続き素晴らしい技術の進化に目を向けていきましょう。
この記事が役に立ったら、ブックマークと共有をしていただけると幸いです。