サーバーコンポーネントとクライアントコンポーネント

サーバーコンポーネントとクライアントコンポーネントの仕組みを理解するには、以下の2つの基本的なWeb概念に慣れておくと役立ちます:

  • アプリケーションコードが実行される環境: サーバーとクライアント
  • サーバーコードとクライアントコードを分離するネットワーク境界

サーバーとクライアント環境

Webアプリケーションの文脈において:

左側にブラウザ、右側にサーバーを示し、ネットワーク境界で分離された図
  • クライアントとは、ユーザーのデバイス上のブラウザを指し、アプリケーションコードを要求するためにサーバーにリクエストを送信します。その後、サーバーから受け取ったレスポンスをユーザーが操作できるインターフェースに変換します。
  • サーバーとは、データセンター内のコンピュータを指し、アプリケーションコードを保存し、クライアントからのリクエストを受け取り、何らかの計算を行い、適切なレスポンスを返します。

各環境には独自の機能と制約があります。例えば、レンダリングとデータ取得をサーバーに移行することで、クライアントに送信されるコード量を減らすことができ、アプリケーションのパフォーマンスを向上させることができます。しかし、先に学んだように、UIをインタラクティブにするためには、クライアント側でDOMを更新する必要があります。

したがって、サーバーとクライアント用に書くコードは常に同じではありません。データ取得やユーザー状態の管理など、特定の操作は、一方の環境にもう一方よりも適している場合があります。

ネットワーク境界

ネットワーク境界とは、異なる環境を分離する概念的な線です。

Reactでは、コンポーネントツリー内のどこにネットワーク境界を配置するかを選択できます。例えば、サーバー上でデータを取得してユーザーの投稿をレンダリングし(サーバーコンポーネントを使用)、各投稿のインタラクティブなLikeButtonをクライアント上でレンダリング(クライアントコンポーネントを使用)できます。

同様に、サーバー上でレンダリングされページ間で共有されるNavコンポーネントを作成できますが、リンクのアクティブ状態を表示したい場合は、Linksのリストをクライアント上でレンダリングできます。

レイアウトコンポーネントが3つの子コンポーネント(Nav、Page、Footer)を持つコンポーネントツリー。PageコンポーネントにはPostsとLikeButtonの2つの子があります。Postsコンポーネントはサーバーでレンダリングされ、LikeButtonコンポーネントはクライアントでレンダリングされます。

内部的には、コンポーネントは2つのモジュールグラフに分割されます。**サーバーモジュールグラフ(またはツリー)**にはサーバー上でレンダリングされるすべてのサーバーコンポーネントが含まれ、**クライアントモジュールグラフ(またはツリー)**にはすべてのクライアントコンポーネントが含まれます。

サーバーコンポーネントがレンダリングされた後、**Reactサーバーコンポーネントペイロード(RSC)**と呼ばれる特殊なデータ形式がクライアントに送信されます。RSCペイロードには以下が含まれます:

  1. サーバーコンポーネントのレンダリング結果
  2. クライアントコンポーネントをレンダリングする場所のプレースホルダー(または穴)と、それらのJavaScriptファイルへの参照

Reactはこの情報を使用して、サーバーコンポーネントとクライアントコンポーネントを統合し、クライアント上のDOMを更新します。

この仕組みを見てみましょう。

クライアントコンポーネントの使用

前章で学んだように、Next.jsはデフォルトでサーバーコンポーネントを使用します - これはアプリケーションのパフォーマンスを向上させるためであり、追加の手順を踏むことなく採用できることを意味します。

ブラウザのエラーを振り返ると、Next.jsはサーバーコンポーネント内でuseStateを使用しようとしていることを警告しています。インタラクティブな「いいね」ボタンをクライアントコンポーネントに移動することで、この問題を修正できます。

appフォルダ内にlike-button.jsという新しいファイルを作成し、LikeButtonコンポーネントをエクスポートします:

/app/like-button.js
export default function LikeButton() {}

<button>要素とhandleClick()関数をpage.jsから新しいLikeButtonコンポーネントに移動します:

/app/like-button.js
export default function LikeButton() {
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return <button onClick={handleClick}>Like ({likes})</button>;
}

次に、likesステートとインポートを移動します:

/app/like-button.js
import { useState } from 'react';
 
export default function LikeButton() {
  const [likes, setLikes] = useState(0);
 
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return <button onClick={handleClick}>Like ({likes})</button>;
}

ここで、LikeButtonをクライアントコンポーネントにするために、ファイルの先頭にReactの'use client'ディレクティブを追加します。これにより、Reactはこのコンポーネントをクライアント上でレンダリングします。

/app/like-button.js
'use client';
 
import { useState } from 'react';
 
export default function LikeButton() {
  const [likes, setLikes] = useState(0);
 
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return <button onClick={handleClick}>Like ({likes})</button>;
}

page.jsファイルに戻り、LikeButtonコンポーネントをページにインポートします:

/app/page.js
import LikeButton from './like-button';
 
function Header({ title }) {
  return <h1>{title ? title : 'Default title'}</h1>;
}
 
export default function HomePage() {
  const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];
 
  return (
    <div>
      <Header title="Develop. Preview. Ship." />
      <ul>
        {names.map((name) => (
          <li key={name}>{name}</li>
        ))}
      </ul>
      <LikeButton />
    </div>
  );
}

両方のファイルを保存し、ブラウザでアプリを表示します。エラーがなくなったので、変更を加えて保存すると、ブラウザが自動的に更新されて変更が反映されるはずです。

この機能はFast Refreshと呼ばれます。編集内容について即座にフィードバックが得られ、Next.jsに事前設定されています。

まとめ

要約すると、サーバーとクライアント環境について学び、それぞれの使用タイミングを理解しました。また、Next.jsがパフォーマンス向上のためにデフォルトでReactサーバーコンポーネントを使用すること、およびUIの小さな部分をインタラクティブにするためにクライアントコンポーネントを選択的に使用する方法について学びました。

追加資料

サーバーコンポーネントとクライアントコンポーネントについては、さらに学ぶことがたくさんあります。以下に追加のリソースを示します:

On this page