SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

フロントエンド開発における定番ライブラリ「React」の最新バージョン解説

フロントエンド開発の定番「React」バージョン18の新機能を解説──並行レンダーで操作感が向上!

  • X ポスト
  • このエントリーをはてなブックマークに追加

優先度の低い更新を後回しにできるTransition

 Transitionは、優先度の低い画面更新を後回しにできる機能です。この機能を、図6のサンプルで説明します。このサンプルでは、テキストボックスに入力した文字を複数回、画面に表示します。また、特定のスマホ名を入力すると、会社名を前につけて表示します。

図6 Transitionのサンプル(p002-transition)
図6 Transitionのサンプル(p002-transition)

 テキストボックスの文字を連続的に更新すると「更新中...…」が表示されて画面への反映が遅れ、その後まもなく反映されることがわかります。

図7 テキストボックスの入力が遅れて画面に反映される様子(p002-transition)

図7 テキストボックスの入力が遅れて画面に反映される様子(p002-transition)

 このサンプルで行っているTransitionの処理は、リスト5の通りです。

[リスト5]Transitionの利用例(p002-transition/src/App.js)
// state ...(1)
const [inputText, setInputText] = useState('');
const [dispText, setDispText] = useState('');
// useTransitionでisPendingとstartTransactionを取得 ...(2)
const [isPending, startTransition] = useTransition();
// テキストボックス文字列変更時の処理 ...(3)
function onChangeInput(e) {
  // テキストボックスへの文字反映のため、setInputTextはすぐに行う ...(4)
  setInputText(e.target.value);
  // 優先度を低くする処理はstartTransitionで囲む ...(5)
  startTransition(() => {
    // 入力された文字列に対応した接頭辞を付与してdispTextに設定 ...(6)
    const prefix = getPrefix(e.target.value);
    setDispText(prefix + e.target.value);
  });
}

 (1)で、テキストボックスの文字列inputTextと、画面に表示する文字列dispTextの2つのstateを定義しています。(2)でuseTransitionフックを実行して、Transition中を表すisPending変数と、Transitionを開始するstartTransition関数を取得します。画面表示では、isPendingがtrueの時に「更新中...」を表示します(詳細はサンプルコード参照)。

 (3)はテキストボックスの文字列が変更されたときの処理です。テキストボックスの文字列に対応するinputTextは、入力結果をすぐに反映するため、そのままsetInputTextメソッドを実行します(4)。

 一方、入力された文字列に接頭辞をつけて表示するdispTextの処理(6)は、(5)の通りstartTransitionで囲んで、優先順位が低いものと指示します。この記述により、テキストボックスの文字列が短時間に多数変更されたときには、dispTextの表示更新が遅れる代わりに、inputTextの表示は遅れない(つまりテキストボックスへの入力が遅延しない)ようになります。

優先順位の低い状態変数を指定できるuseDeferredValue

 startTransitionは、囲まれた処理に対して低優先度を指定しますが、処理の記述や処理中の判定(isPending)が必要ない場合は、状態変数そのものに対して低優先度を指定するuseDefferedValueフックが利用できます(リスト6)。

[リスト6]useDeferredValueの利用例(p003-deferred/src/App.js)
const [inputText, setInputText] = useState('');
// inputTextから、優先度が低いdispTextを生成 ...(1)
const dispText = useDeferredValue(inputText);

 (1)の通りuseDeferredValueメソッドにinputTextを渡して生成したdispTextは、表示の優先順位が低いものと判定され、Transition同様、inputTextの表示(=テキストボックスへの文字入力)を邪魔しないよう、遅れて更新されます。実装の詳細はサンプルコード(p003-deferred)を参照してください。

サーバーサイドレンダリングの初期化処理の変更

 これまで説明してきた例では、Webページはクライアント側の処理で表示されます。このような処理をクライアントサイドレンダリング(CSR)と呼びます。一方、Reactにはサーバー側でWebページのコンテンツを生成するサーバーサイドレンダリング(SSR)の機能も備えており、React 18で変更が加えられました。

 図8のページをCSRとSSRで実装したサンプルで、React自身のCSRとSSRの機能を説明します。なお、現実的にはReact単体でSSRを実装することはほぼなく、Next.jsのような、Reactを利用するフレームワークを用いてSSRを実装することが多いですが、図8のサンプルではReact自身のSSR機能を説明するため、フレームワークに頼らずSSRを実装しています。

図8 Reactで構築したカウントアップのサンプル(p004-csr、p005-ssr)
図8 Reactで構築したカウントアップのサンプル(p004-csr、p005-ssr)

 画面に表示するAppコンポーネントは、リスト7の通りです。(1)で、useStateフックでcount変数とsetCount関数を取得し、(2)でボタン押下時にcountを1増やす処理を記述しています。コンポーネントの表示内容は(3)です。

[リスト7]カウントアップのコンポーネント(p004-csrおよびp005-ssr/src/App.js)
function App() {
  // カウント ...(1)
  const [count, setCount] = useState(0);
  // ボタン押下時の処理(カウントを1増やす) ...(2)
  function onClickButton() {
    setCount(c => c + 1);
  }
  // コンポーネントのHTMLをJSXで記述して返却 ...(3)
  return (
    <div>
      <h3>Reactカウントアップアプリ</h3>
      <div>現在のカウント:{count}</div>
      <button onClick={onClickButton}>カウントアップ</button>
    </div>
  );
}
export default App;

 このAppコンポーネントをSSRで表示するReactの初期化処理はリスト8となります。

[リスト8]SSRにおけるReactの初期化処理(p005-ssr/src/index.js)
// React 17までの古い記述 ...(1)
// import ReactDOM from 'react-dom';
// ReactDOM.hydrate(<App/>, document.getElementById('root'));
// React 18からの記述 ...(2)
import ReactDOMClient from 'react-dom/client';
ReactDOMClient.hydrateRoot(document.getElementById('root'), <App />);

 CSR(リスト1/2)同様、SSRでも、React 17までの記述(1)とReact 18からの記述(2)が存在します。hydrate(ハイドレート)とは、SSRで生成されたDOM構造(HTML記述)に、JavaScriptの処理を後から付与して、動的なWebページとして成立させる処理です。

 SSRでは、表示するHTMLをサーバー側で生成する必要があります。p005-ssrサンプルでは、この処理をserver/index.jsにリスト9の通り実装しています。Node.js用Webサーバーライブラリー「Express」を利用しています。

[リスト9]SSRにおけるサーバー側の処理(p005-ssr/server/index.js)
const PORT = process.env.PORT | 3000;
const app = express();
// 「http://localhost:3000/」へアクセス時の処理 ...(1)
app.get('/', (req, res) => {
  // HTML要素をJSXで記述 ...(2)
  const htmlElem =
(略)
        <div id='root'><App /></div>
(略)
  // HTML文字列を生成して返却 ...(3)
  const htmlString = ReactDOMServer.renderToString(htmlElem);
  res.send(htmlString);
});
// buildフォルダー内のファイルにアクセスできるようにする ...(4)
app.use(express.static('./build'));
// Webサーバー開始 ...(5)
app.listen(PORT, () => {
  console.log(`Server is listening on port ${PORT}`);
});

 Webブラウザーからアクセスされたときの処理は(1)です。ここでは(2)で記述したHTML要素を(3)でHTML文字列にしてWebブラウザーに返却します。(4)は静的ファイルの配置指定、(5)はWebサーバーを開始する記述です。(2)のHTML要素に含まれた<App />、つまりAppコンポーネントがサーバーサイドレンダリングされて、Webブラウザーに返却されるHTMLに含まれるようになります。

 WebブラウザーでWebページのソースを表示させると、CSRではWebブラウザー上で動的にAppコンポーネントを描画するため、Appコンポーネントがソースに含まれません。

図9 CSR時のHTMLのソース(p004-csr)
図9 CSR時のHTMLのソース(p004-csr)

 一方SSRでは、HTMLのソース自体にAppコンポーネントの記述が含まれています。Webページの表示時には、この記述に対してハイドレートで動的な処理が付与されます。

図10 SSR時のHTMLのソース(p005-ssr)
図10 SSR時のHTMLのソース(p005-ssr)

サーバーサイドレンダリングとサーバーコンポーネント

 SSRと似た用語として、Reactには「サーバーコンポーネント」があります。SSRではWebページのHTML自体をサーバー側で生成しますが、サーバーコンポーネントではコンポーネント単位の仮想DOMまでをサーバー側で生成してクライアント側に送り、それを元にクライアント側で描画します。対して、クライアント側で仮想DOMの生成から描画までをすべて行う(今までの)コンポーネントは「クライアントコンポーネント」です。

 サーバーコンポーネントの詳細については、Next.jsのドキュメントも参照してください。

次のページ
コンポーネント表示までの一時表示を指定できるSuspense

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
フロントエンド開発における定番ライブラリ「React」の最新バージョン解説連載記事一覧
この記事の著者

WINGSプロジェクト  吉川 英一(ヨシカワ エイイチ)

WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS Twitter: @yyamada(公式)、@yyamada/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/17953 2023/07/06 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング