Next.jsより遥かに良い:TanStack Server Componentsが登場

BBetter Stack
컴퓨터/소프트웨어AI/미래기술

Transcript

00:00:00Reactサーバーコンポーネント。賛否両論ありますね。
00:00:04最近は批判的な声の方が多いようですが、
00:00:08TanStackが参戦したことで、状況が変わるかもしれません。
00:00:13そうなんです。TanStackにもサーバーコンポーネントが登場しました。
00:00:21Next.jsとは全く異なるアプローチを取っています。見ていきましょう。
00:00:26まずは、発表記事にある一節を紹介します。
00:00:31多くの人が安心できる内容だと思います。
00:00:35「現在、多くの人がReactサーバーコンポーネントを
00:00:40サーバーファーストなものと考えています。
00:00:45サーバーがツリーを所有し、useClientがインタラクティブな部分をマークし、
00:00:48フレームワークの規約が全体をどう構成するかを決める。
00:00:52これでは、Reactサーバーコンポーネントが便利なプリミティブから、
00:00:57アプリ全体が従うべき存在になってしまいます。
00:01:01Reactサーバーコンポーネントの価値を享受するために、
00:01:06最初からそのモデル全体を購入する必要はないはずです。」
00:01:10要するに彼らが言いたいのは、
00:01:15Next.jsのような道には進みたくないということです。
00:01:18Next.jsではデフォルトがサーバーコンポーネントで、
00:01:22インタラクティブ性が必要な場所にuseClientディレクティブを使いますよね。
00:01:27そうではなく、TanStackはReactサーバーコンポーネントを、
00:01:32クライアント側でJSONを取得するのと同じくらい
00:01:36粒度細かく使えたらどうだろう、と考えているのです。
00:01:40その目標を踏まえた上で、彼らがどうサーバーコンポーネントを
00:01:45実装したのかを見ていきましょう。ネタバレになりますが、
00:01:49私はこの実装方法が本当に気に入りました。
00:01:53ここにあるのは、通常のTanStack Startアプリです。
00:01:58現時点ではすべてがクライアントコンポーネントになっています。
00:02:01サーバーコンポーネントを動かすために必要なインストール手順を
00:02:06いくつか踏んだだけで、vconfigを修正した程度です。
00:02:10今のページはこんな感じです。
00:02:14ここにGreetingコンポーネントがあります。
00:02:18現時点ではクライアントコンポーネントです。
00:02:23コードを見ると、ただのReactコンポーネントですね。
00:02:27そして下の方には通常のTanStackルートがあり、
00:02:32そこでGreetingコンポーネントを使用しています。
00:02:36では、このGreetingコンポーネントでサーバー側のロジックを
00:02:41実行したいとしましょう。例えば、
00:02:46OSのホスト名を取得したり、サーバーでしか使えない
00:02:51環境変数を取得したりして、実際にサーバーで動いていることを
00:02:55示したいと思います。今の状態でos.hostnameを使おうとすると、
00:02:59これはNode.jsの関数でありブラウザでは使えないため、
00:03:03動きません。
00:03:07そこで、Greetingコンポーネントをサーバーでレンダリングする必要があります。
00:03:12TanStack Startでその最初のステップとなるのが、
00:03:16シンプルなサーバー関数です。ご覧の通り、
00:03:21getGreetingという関数を用意しました。
00:03:25その中で、新しいrenderServerComponent関数を使い、
00:03:30コンポーネントを渡して、レンダリング可能な
00:03:34サーバーコンポーネントを返しています。
00:03:38コンポーネントに対するGETリクエストを作成している、
00:03:43と考えると分かりやすいでしょう。
00:03:47次にやることは、そのサーバー関数から
00:03:52コンポーネントをフェッチすることです。
00:03:56ルート内のローダーで、await getGreetingして
00:04:01そのまま返せば、レンダリング可能なサーバーコンポーネントになります。
00:04:05そしてルート内でuseLoaderDataを使い、
00:04:10このようにコンポーネントを使用します。
00:04:14これで最初のTanStackサーバーコンポーネントが完成しました。
00:04:18os.hostnameが正しく動作し、サーバーでのみ利用可能な
00:04:22環境変数も取得できています。
00:04:28この実装で私が何より気に入っているのは、
00:04:32renderServerComponent関数という新しいもの以外は、
00:04:37すべて通常のTanStack Startだという点です。
00:04:42これを単純なJSONデータを返す形に置き換えても、
00:04:47全く同じ方法でフェッチできます。
00:04:51また、この実装はコードがどこで動くかを
00:04:56非常に明示的に示していると思います。
00:05:00すべてサーバー関数の中で行われるため、
00:05:05サーバーで動くことが明確です。
00:05:09実際、デモコードをさらに改善できます。
00:05:14サーバーで実行したい関数をサーバー関数の中に入れ、
00:05:18その値をpropsとしてGreetingコンポーネントに渡すだけです。
00:05:22するとGreetingコンポーネントは、
00:05:27クライアントでもサーバーでも使えるただのコンポーネントになります。
00:05:31実行したいサーバーロジックは、
00:05:36すべてサーバー関数内に収まっています。
00:05:41これもサーバーで動くことが非常に明確です。
00:05:45Next.jsのロジックとは真逆のような感じで、本当に最高です。
00:05:49Reactを普通に考えて、すべてクライアントファーストで、
00:05:53必要に応じてサーバーコンポーネントを追加していく形です。
00:05:57では、サーバーコンポーネントの中で
00:06:01クライアントコンポーネントを使いたい場合はどうでしょう?
00:06:05例えば、クリックするたびにカウントが増える
00:06:10カウンターボタンを追加したいとします。
00:06:14これをそのままサーバーコンポーネントに追加しようとすると、
00:06:18onClickやuseStateを呼ぶことになり、完全に壊れます。
00:06:23「useStateは関数ではない」といったエラーが出ます。
00:06:27Greetingがサーバーコンポーネントとして使われているからです。
00:06:32クライアント側の機能は使えません。
00:06:36修正するには2つの方法があり、2つ目の方が断然おすすめですが、
00:06:401つ目はNext.jsを使ったことがある人にはおなじみかもしれません。
00:06:44そのロジックを別のコンポーネントに分離して、
00:06:48use clientディレクティブを使うというものです。
00:06:53これはTanStack Startのサーバーコンポーネントでも動作します。
00:06:58サーバーコンポーネントの中でコンポーネントを使えば、
00:07:03問題なく動きます。ですが、このアプローチには欠点があります。
00:07:08サーバーコンポーネントがクライアントコンポーネントの
00:07:12レンダリングを制御することになり、少し散らかってしまいます。
00:07:17もしカウンターコンポーネントがツリーのどこにあるかを知りたければ、
00:07:22ルートを見てGreetingサーバーコンポーネントがあり、
00:07:27その中を見てクライアントコンポーネントがある、という確認が必要です。
00:07:32この境界が曖昧になり、複雑さが増していくのです。
00:07:37しかしTanStackはそれで妥協しませんでした。
00:07:42「サーバー側で、UIのクライアント側をすべて決める必要はないのでは?」
00:07:46と考え、全く新しいものを作りました。それがコンポジットコンポーネントです。
00:07:50使い方は、まずサーバーコンポーネントからクライアントコンポーネントを削除し、
00:07:56単にGreetingコンポーネントのchildrenに置き換えます。
00:07:59次に、サーバー関数から返すものを変える必要があります。
00:08:04レンダリング可能なサーバーコンポーネントを返すのではなく、
00:08:08「コンポジットソース」を返すのです。
00:08:12そのために、createCompositeComponentという
00:08:162つ目のTanStackサーバーコンポーネントヘルパー関数を使います。
00:08:20ここでは、propsが「スロット」となるような
00:08:24コンポーネントを構築しています。
00:08:28非常にシンプルなchildrenスロットを使っており、
00:08:32コンポジットコンポーネントの子要素として渡したものを
00:08:36props.childrenに渡し、先ほどのGreetingコンポーネントへと渡しています。
00:08:41ここでも、サーバー関数からコンポジットコンポーネントをフェッチします。
00:08:45loaderでの使い方は全く同じです。
00:08:49ソース名をgreetingに変えて、useLoaderDataで読み込みます。
00:08:53唯一の違いは、これをコンポーネントとして直接使えないことです。
00:08:57エラーが出ています。コンポジットコンポーネントをレンダリングするには、
00:09:01TanStackのCompositeコンポーネントヘルパーを使い、
00:09:06sourceプロパティに、先ほどフェッチしたコンポジットコンポーネントを渡します。
00:09:10これでサーバーコンポーネントが以前のようにレンダリングされます。
00:09:15カウンターをコンポジットコンポーネントの子要素として渡しており、
00:09:19それがGreetingコンポーネントへprops.childrenとして渡され、
00:09:23すべてうまく動いています。
00:09:28また、カウンターコンポーネントからはdirectiveを削除できます。
00:09:33コンポジットコンポーネントの一部としてスロットで使われるため、
00:09:38クライアントコンポーネントであると自動的に認識されるからです。
00:09:42これまでのuse clientディレクティブを使う方法より手間がかかるように
00:09:47見えるかもしれませんが、真のパワーはDX(開発者体験)にあります。
00:09:51サーバー側でクライアントコンポーネントがどこでレンダリングされるかを
00:09:56決定させるのではなく、
00:10:01ここにクライアントコンポーネントが入る「スロット」を用意し、
00:10:05サーバーコンポーネント自体は何が入るか全く知らない、という形です。
00:10:10クライアント側で後から追加するので、クライアントベースのコンポーネントは
00:10:14すべてクライアント側のコードで完結します。
00:10:18これだけではありません。例えばブログ投稿のような
00:10:22複雑なページを見てみましょう。投稿はサーバーレンダリングされます。
00:10:26解決したい問題が2つあります。1つ目は、
00:10:31「いいね」ボタンや「フォロー」ボタンを追加したいのですが、
00:10:36タイトル上部に配置したく、クライアントコンポーネントを使いたい点です。
00:10:40現時点ではchildrenスロットパターンを使っているので、
00:10:44投稿アクションをここに追加すると、
00:10:49コメントセクションと同じ場所に行ってしまいます。
00:10:53特定のクライアントコンポーネントをどこに置くか、
00:10:58サーバーコンポーネントに伝える方法が必要です。
00:11:032つ目の問題は、「フォロー」ボタンです。
00:11:07投稿ページ自体、誰が投稿者かを知りません。
00:11:12そのロジックはサーバーコンポーネントにオフロードしました。
00:11:16クライアントコンポーネントで投稿者を取得しようとすると、
00:11:21投稿のJSONをフェッチする必要がありますが、これは
00:11:25二重にデータをフェッチすることになり、良いパターンではありません。
00:11:31幸い、TanStackにはchildren以外の
00:11:36スロットタイプが2つあります。1つ目は「render props」です。
00:11:41これはReact要素を返す関数を渡すものです。
00:11:45何でも好きな名前にできますが、ここではrenderActionsとして、
00:11:49サーバーコンポーネントから何を渡したいかを指定します。
00:11:54投稿IDと著者IDですね。
00:11:59あとはコンポジットコンポーネント側で、

Key Takeaway

TanStack Startは、アプリケーション全体をサーバーファーストに縛り付けることなく、renderServerComponentとコンポジットコンポーネントを利用して、必要な場所にのみサーバーレンダリングを統合する柔軟な開発モデルを提供する。

Highlights

TanStack Startは、React Server Componentsをアプリケーション全体に強制せず、必要に応じて粒度細かく利用するアプローチを採用している。

renderServerComponent関数を用いることで、サーバー側のロジックを通常のTanStack Startのルート内で簡単にフェッチ・利用できる。

クライアント機能を含むコンポーネントをサーバーコンポーネントと混在させる際、createCompositeComponentを活用することで、コンポーネント間の境界を明確に保つ。

コンポジットコンポーネントは、サーバーが内部構造を知らない「スロット」を提供し、クライアント側のコンポーネントを後から注入する形式でDXを向上させる。

render props機能を利用することで、サーバーコンポーネントから投稿IDなどの動的なデータをクライアントコンポーネントへ直接受け渡すことが可能になる。

Timeline

React Server Componentsへの新たなアプローチ

  • React Server Componentsをアプリケーション全体の前提条件にしない設計思想を掲げる。
  • サーバー側でのレンダリングを、JSONデータ取得と同じくらい粒度細かく制御することを目指す。

Next.jsのようなデフォルトでサーバーファーストとするモデルではなく、Reactコンポーネントをクライアントファーストで考えつつ、必要に応じてサーバー機能を注入する手法を提唱する。

renderServerComponentによるサーバーロジックの実装

  • サーバー関数内でrenderServerComponent関数を呼び出し、コンポーネントをサーバーレンダリング可能な形式に変換する。
  • OSのホスト名や環境変数など、サーバーでしか利用できないロジックをコンポーネント内で直接実行できる。

TanStack Startのルート内ローダーでこの関数を利用し、useLoaderDataを通じてコンポーネントをフェッチする。従来のJSONデータを返すフェッチ手法とほぼ同じ手順で、コードがサーバーで動くことを明示的に表現できる。

コンポジットコンポーネントによる境界の最適化

  • use clientディレクティブを多用してサーバーとクライアントの境界が曖昧になる問題を回避する。
  • createCompositeComponentを用いて、クライアント側UIを後から注入するスロットベースの構造を構築する。

サーバーコンポーネントが子コンポーネントの構造を知らないままchildrenとして受け渡すことで、クライアントコンポーネントのコードを純粋に保つ。これにより、useState等のフックもエラーなしで利用可能になる。

高度なデータ受け渡しとrender props

  • render propsを用いて、サーバーコンポーネントから必要なデータのみをクライアント側のUIに受け渡す。
  • 二重のデータフェッチを発生させずに、サーバーレンダリングされたコンポーネントのID情報を効率的に利用する。

ブログ投稿のような複雑なページにおいて、アクションボタンなどの配置を制御しつつ、必要なコンテキスト(投稿IDや著者IDなど)を動的にクライアント側のUIへ注入する設計を可能にする。

Community Posts

No posts yet. Be the first to write about this video!

Write about this video