メインコンテンツへ移動

React Server Components

無料2021-05-05#Front-End#JS#React服务端组件#RSC#React Server Components vs SSR#SSR with React Server Components#React Server Components under the hood

React Server Components とは何か?どのような用途があるか?どのような外見か?

前言

2020 年のクリスマスの前に、React チームは Server Components に関するメッセージを公開しました。それ以前、私はまさに SSR(Server-Side Rendering、サーバーサイドレンダリング)を研究しており、[Next.js のハイブリッドレンダリング](/articles/next-js ハイブリッドレンダリング/) に感嘆していました

実際、Server Components も確かに Server-Side Rendering とは千絲万縷のつながりがあります。結局両方とも Server という言葉が含まれていますから(真面目な顔で、これは非常に重要です)。これまで SSR に関する一連の研究のおかげで、ある意味では、Server Components の背後にある思考と設計考量をより深く理解できると言えます

一.Server Components とは何か?

Server Components are a new type of?component that we're introducing to?React and this tells React that we only want?this component to ever render on the server.

Data Fetching with React Server Components 講演ビデオから引用)

公式定義によると、Server Components は本質的に新しい React コンポーネント(Portal、Fragment などのコンポーネントタイプと同じ)であり、その特別な点はこの種のコンポーネントはサーバー上でのみレンダリングされることです

しかし、React コンポーネントはクライアントでうまく動作しているのに、どうして突然サーバーで実行する必要があると言っているのでしょうか?Hooks もまだ理解しきれていないのに、一大波のサーバーサイドコンポーネントの更新がまたやって来ようとしています。React チームのこれらの人々はいったい何をしているのでしょうか?

二.何を解決するためのものか?

RFC に書かれている通り、React が Server Components の概念を導入することで一大把の問題を解決できます:

  • Zero-Bundle-Size Components:バンドル成果物のサイズの問題

  • Full Access to the Backend:データアクセス、伝達はまずコンポーネントツリー構造を考慮する必要がある問題

  • Automatic Code Splitting:コード分割のパフォーマンス最適化は手動改造が必要という問題

  • No Client-Server Waterfalls:リクエスト順序とコンポーネントツリー構造が強関連という問題

  • Avoiding the Abstraction Tax:多層抽象がもたらすパフォーマンス問題

これらなどですが、これらは解決策から逆算した収益のように見え、私がより注目しているのはその初衷です。最初にもっとも解決したかった問題は何でしょうか?

First, we wanted to make it easier for developers to fall into the "pit of success" and achieve good performance by default. Second, we wanted to make it easier to fetch data in React apps.

Motivation から引用)

初衷は 2 大类の問題を解決したいことです:

  • 第一類:パフォーマンス最適化は比較的複雑で、デフォルトで高性能を実現できるか?

  • 第二類:React アプリケーションでデータを取得するには実は多くの懸念があるが、よりシンプルで、よりエレガントな方法はないか?

パフォーマンス最適化とは、必要に応じてライブラリを引用する、ルートごとにコードを分割する、データリクエストを事前に行う、過度な抽象化を減らすなどです。これらの最適化措置はすべて手動改造が必要で、アプリケーション開発に一定の複雑さをもたらします。一方、高性能のために、通常データリクエストをトップ層に引き上げるため��データ表示コンポーネントがデータソースに明確に対応できないという問題が生じます

例えば私たちがよく目にする:

// 数据展示组件的数据源依赖不清晰
function ArtistPage({ artistID }) {
  const artistData = fetchAllTheStuffJustInCase();

  return (
    <ArtistDetails
      details={artistData.details}
      artistId={artistId}>
      <TopTracks
        topTracks={artistData.topTracks}
        artistId={artistId} />
      <Discography
        discography={artistData.discography}
        artistId={artistId} />
    </ArtistDetails>
  );
}

(コード保守性を考慮して)より望ましいのは:

// 类似这样的清晰依赖,每个组件明确知道其数据从哪来
function ArtistDetails({ artistId, children }) {
  const artistData = fetchDetails(artistId);
  // ...
}
function TopTracks({ artistId }) {
  const topTracks = fetchTopTracks(artistId);
  // ...
}
function Discography({ artistId }) {
  const discography = fetchDiscography(artistId);
  // ...
}

絶えず極限のユーザー体験と開発体験を追求する React チームにとって、よりシンプルで、よりエレガントなデータ取得方式は常に探求方向でした(Suspense for Data Fetching (Experimental) など)。したがって、実際の状況は React チームがデータ取得問題を解決する際に、Server Components の思路を提案し、ひらめいてこの大胆な考えが多くのパフォーマンス問題もついでに解決できることに気づき、そこでクライアントからサーバーへの躍遷的な新特性予告が生まれたのかもしれません……

三.Server Components はどのように解決するのか?

コンポーネントのデータ関連問題を解決するには、コンポーネントに各自明確なデータソースを持たせる必要がありますが、ウォーターフォール式リクエストはパフォーマンス問題をもたらします……良いユーザー体験、低メンテナンスコスト、高性能は 3 つを兼ね備えるのが難しいように思えますが、不可能ではありません。少なくとも React チームはすでに 2 つの解法を探索しています:

  • Relay + GraphQL:Relay フレームワーク と GraphQL 特性を配合し、散在するデータリクエストをマージして、パフォーマンス問題を解決

  • Move our components to the server:コンポーネントをサーバーに持って行って実行し、データリクエストのコストを下げ、これにより(很大程度上で)パフォーマンス問題を解決

GraphQL はリクエストで指定されたデータモデル(schema)に基づいてデータフラグメントを簡単に組み立てることができ、Relay フレームワークと配合して複数回のリクエストを 1 回にマージします。これによりコンポーネントソースコードの保守性(明確なデータソース依存)を保持しつつ、由此产生的パフォーマンス問題も回避できます。しかし残念ながら GraphQL に強く依存しており、真の意味での通用解決策とは言えません

一方、Server Components の道は比較的野心的で、複数回のクライアントリクエストの時間オーバーヘッドを下げるために、いっそコンポーネントをサーバー上で実行することにしました。そして(同一ユニット)サーバー間のデータ通信は非常に速く、このとき複数回のデータリクエストのパフォーマンスオーバーヘッドは恐れるに足りず、さらに最終的に(フレームワーク層で)データキャッシュメカニズムを導入した後に完全に解決されます

ちょっと待って、コンポーネントをサーバーに持って行って実行するというのは、SSR そのものではありませんか?これは莫非朝花夕拾?

四.Server Components と SSR の関係?

連絡

一般に、伝統的な SSR は 2 つのプロセスを免れません。サーバーサイドレンダリング + クライアント hydrate 二次レンダリング

  • サーバーサイドレンダリング:サーバーサイドでファーストスクリーンのコンテンツ(HTML 文字列)をレンダリング

  • クライアント hydrate 二次レンダリング:クライアントでファーストスクリーンのコンテンツ(このときページはインタラクション不可)およびフロントエンドアプリケーションの完全なコードをロードした後、render に類似した hydrate 二次レンダリングプロセスを行い、インタラクションイベントをバインドする(このときページはインタラクション可能)

Server Components のレンダリングプロセスはこれに類似しています:

  • サーバーサイドレンダリング:サーバーサイドでファーストスクリーンのコンテンツ(一種の中間形式、同様に UI を記述するために使用)をレンダリング

  • クライアントレンダリング:サーバーサイド出力の中間形式を受け取り、最初から render し、ストリーミングレンダリングを開始

したがって、Server Components と SSR の連絡には少なくとも以下の点があります:

  • 両方ともサーバーサイドでコンポーネントレンダリングロジックを実行(したがって両方ともインタラクションをサポートしない)
  • 両方とも同一コンポーネントがクライアント、サーバーサイドを跨いで実行可能(Shared Components)
  • 両方ともクライアントからサーバーサイドに延伸し、より多くのパフォーマンス突破を追求

両者の関係について、React 公式は非常に的確な表現をしています。**Server Components と SSR は補完的(complementary)**であり、双剣合璧で、SSR はファーストスクリーンを HTML にレンダリングしてコンテンツ表示を加速でき、Server Components は hydrate 二次レンダリングに必要なロード実行コード量を削減できます(Server Components はサーバーサイドでのみレンダリングされ、関連コードはクライアントでロード実行する必要がない)。これによりページのインタラクティブ時間を加速できます:

You can combine Server Components and SSR, where Server Components render first, with Client Components rendering into HTML for fast non-interactive display while they are hydrated. When combined in this way you still get fast startup, but you also dramatically reduce the amount of JS that needs to be downloaded on the client.

区別

重要な区別は 3 点あります:

  • Server Components 関連コードは根本的にクライアントに提供されず、伝統的 SSR はすべてのコンポーネントコードをクライアント bundle に打ち込む必要がある

  • Server Components はコンポーネントツリーの任意の位置で直接バックエンドにアクセス可能で、伝統的 SSR はトップ層(ページレベル)でのみデータ取得可能

  • Server Components は更新時にクライアントインタラクション状態(入力された検索語、スクロール位置、フォーカス、選択内容などを含む)を保持可能。Server Components のレンダリング結果は HTML よりも情報が豊富な中間形式(HTML は HTML しか表現できないが��カスタム形式にはこの制限がない。例えば props を持たせることができる)

Server Components はサーバーサイドでのみ実行され、クライアントはこれらのコードをロードしません。サーバーサイドがクライアントに提供する始终是 Server Components のレンダリング結果で、二次更新も含みます。中間形式でクライアントに提供された後、クライアントはサービスからのレンダリング結果を現在のすでにレンダリング済みのクライアントコンポーネントに merge するだけなので、インタラクション状態を保持できます:

Specifically, React merges new props passed from the server into existing Client Components, maintaining the state (and DOM) of these components to preserve focus, state, and any ongoing animations.

P.S.両者の区別に関する詳細情報は、danabramov on Zero-Bundle-Size React Server Components を参照

五.Server Components の優勢

1.bundle size を削減するのに有利

Server Components はサーバーサイドでのみ実行されるため、コンポーネント自体およびその依存ライブラリはクライアント bundle に打ち込まれません。したがって很大程度上でパッケージ体积を削減できます(Facebook のパイロットケースは 30% 程度削減

一方、中間の多層抽象カプセル化はすべてサーバーサイドで消化され、クライアントの負担を軽減します

2.コンポーネントツリーの任意の位置でバックエンドリソースにアクセス可能

コンポーネントツリーの任意の位置でバックエンドリソースにアクセスできるのは、伝統的 SSR でもできません。伝統的 SSR にはクライアントフレームワークの配合が不足しており、データを一括で取得し、同期のコンポーネントレンダリングを行い、最後に結果をクライアントに提供するしかないためです

実際、初衷はコンポーネントとそのデータソースの関係をより明確にし、コード保守性をより良くすることです:

// 类似这样的清晰依赖,每个组件明确知道其数据从哪来
function ArtistDetails({ artistId, children }) {
  const artistData = fetchDetails(artistId);
  // ...
}
function TopTracks({ artistId }) {
  const topTracks = fetchTopTracks(artistId);
  // ...
}
function Discography({ artistId }) {
  const discography = fetchDiscography(artistId);
  // ...
}

コンポーネントを修正する際に対応するデータも一緒に修正し、コンポーネントを下线 する際にデータリクエストも一緒に下ろす

3.必要に応じてコードをダウンロード可能

サーバーサイドにはデータがあるため、どのコンポーネントを下す必要があるかを正確に知っています:

Server Components let?you only download the code that you actually need, they enable?automatic code splitting for client code?with support of a bundler plugin, you can use as?much Server Components or as little as you like.

同時に自動コード分割を可能にし、すべての Client Components import は自動で必要に応じてロードされ、dynamic imports を逐一採用する必要がなくなりました。開発者は明示的に気にする必要がなく、Server Components がデフォルトでサポートします

4.SSR と補完

SSR の角度からのみ見ても、Server Components はコンポーネント化フレームワークがコンポーネントシステムレベルで SSR アプリケーションフレームワークが解決できない問題を解決しました。例えば:

  • hydrate 二次レンダリングを加速(クライアントで必要なロード実行コード量を削減し、迂回して救国)

  • ストリーミングレンダリングサポート(SSR ストリーミング出力とは異なり、ストリーミングレンダリングは必ずコンポーネント化フレームワーク自体がサーバーサイドと配合する必要がある)

  • 部分更新を許可し、インタラクション状態を保持(伝統的 SSR はファーストスクリーンでのみ使用可能)

これらのパフォーマンスポイントは SSR フレームワークだけでは極限まで行うことができませんが、Server Components はこのプロセスを大幅に加速しました

一方、冒頭で Next.js がハイブリッドレンダリング方面で深く探求しており、SSG、SSR、CSR を多种方式で混用可能で、あらゆる機会を掴んでプリレンダリングを行い、その目的はファーストスクリーンパフォーマンスを向上させること(SPA ルートジャンプなどのインタラクションシナリオでのファーストスクリーンパフォーマンスを含む)であると述べました。したがって、ある意味では、Server Components とこれらのプリレンダリング探求は異曲同工であり、そのため衝突せず配合使用できます

六.発展現状

昨年から Facebook 内部でパイロットを行いましたが、初步的な検証に過ぎず、生産化までには一定の距離があります

さらにコンポーネントをサーバーサイドに持って行って実行するため、構築、サーバーサイドレンダリング、ルート制御など多くの環節に関わり、コンポーネント化フレームワークの範疇を超えているため、React チームは Next.js チームと協力して共建する計画を立て、まず Next.js との統合を試みます(もちろん、Server Components は特定の SSR フレームワークのみに限定されず、まず一つを統合して試すだけです)

現在 公式 Demo を通じて试玩可能です。注意が必要なのは、Demo は postgre データベースに依存しているため、docker を通じて起動することを推奨します:

docker-compose up -d
docker-compose exec notes-app npm run seed

试玩を通じていくつかの詳細を理解できます。例えば Server Components がレンダリングして来た中間形式はおよそこのような外見です:

[caption id="attachment_2344" align="alignnone" width="625"]server-components server-components[/caption]

その中で、Client Components は bundle インデックスの形式で返され、ネイティブコンポーネント(div、span など)は JSON 形式で返されます。例えば:

[
  "$",
  "header",
  null,
  {
    "className": "sidebar-note-header",
    "children": [
      ["$", "strong", null, { "children": "todo" }],
      ["$", "small", null, { "children": "5/4/21" }]
    ]
  }
]

P.S.React Server Components のより多くの技術詳細については、RFC: React Server Components を参照

参考資料

コメント

コメントはまだありません

コメントを書く