SHOEISHA iD

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

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

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

フロントエンドの定番ライブラリ「React」バージョン18の新機能を紹介──Strictモードと新フックに注目!

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

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

React 18で導入された新たなフック

 フックとは、クラス形式のコンポーネントで利用していたstateなどの機能を、関数形式のコンポーネントでも利用できるようにする機能です。前回記事では、並行レンダーに関連してReact 18で導入されたuseTransition、useDefferedValueフックを説明しました。今回はそれ以外にReact 18で導入されたフックを紹介します。

一意のIDを生成できるuseIdフック

 useIdフックは、一意のIDを生成するフックです。図6のサンプルでは、2つのコンポーネントでそれぞれIDを生成し、それを利用して入力フォームを表示します。

図6 useIdフックのサンプル(p002-useid)
図6 useIdフックのサンプル(p002-useid)

 図6のサンプルでは、Appコンポーネント内でCheckboxSample1、CheckboxSample2の2つのコンポーネントを表示します(リスト3)。

[リスト3]図6のサンプルのAppコンポーネント(p002-useid/src/App.js)
function App() {
  return (
    <div>
      <CheckboxSample1/>
      <CheckboxSample2/>
    </div>
  );
}
export default App;

 CheckboxSample1とCheckboxSample2の実装内容はほとんど同一です。CheckboxSample1コンポーネントの実装をリスト4に示します。

[リスト4]CheckboxSample1コンポーネント(p002-useid/src/CheckboxSample1.js)
function CheckboxSample1() {
  // 一意となるIDを生成 ...(1)
  const id = useId();
  return (
    <div>
      <h3>React 18 useId Checkbox1</h3>
      <div>このコンポーネント用のIDは「{id}」</div>
      <div>
        <input id={id + '-check1'} type="checkbox"></input>
        <label htmlFor={id + '-check1'}>Samsung</label>
      </div>
      <div>
        <input id={id + '-check2'} type="checkbox"></input>
        <label htmlFor={id + '-check2'}>Sony</label>
      </div>
      <div>
        <input id={id + '-check3'} type="checkbox"></input>
        <label htmlFor={id + '-check3'}>SHARP</label>
      </div>
    </div>
  );
}
export default CheckboxSample1;

 (1)のuseIdで一意のIDを生成して変数idに格納し、コンポーネントの表示内容を指定するreturnメソッド内で利用しています。CheckboxSample1とCheckboxSample2の各コンポーネントでuseIdを実行すると、異なるIDが取得できることが保証されるため、<input>や<label>タグに指定するIDの衝突を避けることができます。

DOM更新前に実行されるuseInsertionEffectフック

 useInsertionEffectフックは、コンポーネントのDOM構造が更新される前に実行される副作用を記述できるフックです。従来から利用できた副作用フックであるuseEffect、useLayoutEffectと併せて、図7のサンプルで説明します。このサンプルでは、useInsertionEffectでCSS(画面の背景色)を追加し、useLayoutEffectとuseEffectでそれぞれコンポーネント内の表示を更新します。

図7 useInsertionEffect、useLayoutEffect、useEffectフックのサンプル(p003-useinsertioneffect)
図7 useInsertionEffect、useLayoutEffect、useEffectフックのサンプル(p003-useinsertioneffect)

 このコンポーネントには、表示される多数の文字列が含まれるため、表示に時間がかかります。この場合、useEffectで更新する表示は、更新前の表示が一瞬見えますが、useLayoutEffectで更新する表示は更新前の表示が見えません(図8)。

図8 図7のサンプルで画面を更新したときの様子(p003-useinsertioneffect)

図8 図7のサンプルで画面を更新したときの様子(p003-useinsertioneffect)

 このサンプルのAppコンポーネントを、リスト5に示します。

[リスト5]図7、8のサンプルのAppコンポーネント(p003-useinsertioneffect/src/App.js)
function App() {
  // 画面に表示するメッセージのstate ...(1)
  const [msg1, setMsg1] = useState('This is default msg1.');
  const [msg2, setMsg2] = useState('This is default msg2.');
  const msg3 = 'This is default msg3.';
  // DOM更新前に(useLayoutEffect、useEffectより先に)動く ...(2)
  useInsertionEffect(() => {
    // CSSを設定できる ...(2a)
    const css = document.createElement('style');
    css.textContent = 'body {background-color:lightblue;}';
    document.head.appendChild(css);
  }, []);
  // DOM更新後に、同期的に動く(処理完了まで画面が更新されない) ...(3)
  useLayoutEffect(() => {
    // もとのmsgは見えずに、このメッセージが見える ...(3a)
    setMsg1('Hello from useLayoutEffect!');
  }, []);
  // DOM更新後に、非同期に動く(処理完了前に画面が更新される) ...(4)
  useEffect(() => {
    // もとのmsgが一瞬見えた後、このメッセージが見える ...(4a)
    setMsg2('Hello from useEffect!');
  }, []);
  return (
(略)
  );
}
export default App;

 (1)で画面に表示するメッセージのstateを定義しています。msg3は変更されることがないので固定値とします。(2)のuseInsertionEffectフックは、DOM更新前に動作します。ここでは(2a)の処理で、Webページの背景色を設定するCSSを設定しています。

 useLayoutEffect(3)とuseEffectフック(4)は、それぞれDOM更新後に動作しますが、前者は同期的に、後者は非同期で動くという違いがあります。ここではそれぞれ(3a)と(4a)で、メッセージのstate(msg1、msg2)を更新していますが、useLayoutEffectではメッセージ更新前の状態が画面に見えず、useEffectでは一瞬見えるという違いがあります。

 まとめると、useInsertionEffectはDOM更新前に実行できる副作用(CSSの設定など)、useLayoutEffectは画面の表示内容を更新する副作用、useEffectはそれ以外の副作用で利用することが望ましいといえます。

 なお、サンプルコードには、表示を遅くするために多数の文字列を画面に表示する処理が含まれています。詳細はサンプルコードを参照してください。

[補足]useInsertionEffectはライブラリー作者が便利に使える

 Reactの公式ドキュメントにおいて、useInsertionEffectは、JavaScriptでCSSを設定する(CSS-in-JS)ライブラリーの作者のためのものであり、一般にはuseEffectかuseLayoutEffectを利用するよう案内されています。

[補足]useLayoutEffectではレンダリングのブロックに注意

 上述の通り、useLayoutEffectはDOM更新後に「同期的に」動作するため、画面の表示内容を更新する処理を実装すれば、更新前の画面を見せずに画面を更新できます。

 一方で、useLayoutEffectで重い処理を行うと、同期的な動作のためレンダリングがブロックされ、パフォーマンスに影響を与えます。その意味で、通常の副作用はuseEffectで実行し、useLayoutEffectは画面の表示内容に関連した重くない処理に限定して利用するべきといえます。

外部ストアからデータを画面に反映できるuseSyncExternalStoreフック

 useSyncExternalStoreフックは、Reactの外部で管理されているストア(データの供給源)からデータを画面に反映できるフックです。本記事ではJavaScriptで取得できる日時を外部ストアとみなして、useSyncExternalStoreフックで画面に現在日時を表示する図9のサンプルで利用方法を説明します。

図9 useSyncExternalStoreフックのサンプル(p004-usesyncexternalstore)
図9 useSyncExternalStoreフックのサンプル(p004-usesyncexternalstore)

 このサンプルのApp.jsをリスト6に示します。

[リスト6]図9のサンプルのAppコンポーネント(p004-usesyncexternalstore/src/App.js)
function App() {
  // 外部ストアの現在の値を取得する処理 ...(1)
  const getSnapshot = () => {
    return new Date().toString(); // 現在の日時を返却 ...(1a)
  };
  // 外部ストアが変更されるたびに呼び出されるcallbackを登録・登録解除する処理 ...(2)
  const subscribe = (callback) => {
    // callback登録:setIntervalで1秒おきにcallbackが呼ばれるようにする ...(2a)
    const timer = setInterval(callback, 1000);
    // callback登録解除:clearIntervalでcallbackの実行を停止 ...(2b)
    return () => clearInterval(timer);
  };
  // 外部ストアの値(現在の日時)を参照できる変数snapshotを取得 ...(3)
  const snapshot = useSyncExternalStore(subscribe, getSnapshot);
  return (
    <div>
      <h3>React 18 useSyncExternalStore</h3>
      { snapshot } {/* (4) */}
    </div>
  );
}
export default App;

 useSyncExternalStoreフック(3)の引数には、外部ストアの現在の値を取得する処理(1)と、外部ストアが変更されるたびに呼び出されるcallbackの登録・登録解除処理(2)を指定します。ここでは(1a)で現在の日時を取得し、(2a)のsetIntervalと(2b)のclearIntervalで、定期的に実行するcallbackを登録・登録解除します。これらを利用して(3)で取得したsnapshotを、(4)の通り画面に表示するよう記述すると、外部ストアの現在値(=ここでは現在日時)が表示され、callbackの処理により1秒おきに更新されるようになります。

[補足]useSyncExternalStoreはReactでない既存のコードとの接続用

 Reactの公式ドキュメントにおいて、useSyncExternalStoreは、Reactではない環境で記述された既存のコードからデータを取得する際に有効であり、React内のデータを取得するにはuseStateやuseReducerを利用するように案内されています。

[補足]useEffectとuseSyncExternalStoreの使い分け

 外部データをReactのコンポーネントに反映させるには、useEffectフックで外部からデータを取得して反映する方法もあります。この場合、useEffectフックはレンダリング後にしか実行されないため、外部データの更新を反映するには、まずレンダリングが行われる必要があります。

 一方でuseSyncExternalStoreを利用すると、データが更新されたことをcallbackでReactコンポーネントに通知して再レンダリングさせることができます。そのためReactコンポーネントと独立して更新される外部データを参照する場合、useSyncExternalStoreが有用です。

まとめ

 本記事では、前回に引き続きReact 18の新機能について説明しました。今回は潜在的な問題点を洗い出すStrictモードへの機能追加と、React 18で導入された新たなフックとしてuseId、useInsertionEffect、useSyncExternalStoreについて紹介しました。

 前回説明したTranstionやSuspenseなどを含め、React 18は、開発者にとってはよりシンプルで本質的な実装、利用者にとってはWebページの使い勝手向上が期待できるアップデートといえます。

参考資料

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

  • 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/18019 2023/08/01 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

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

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

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

メールバックナンバー

アクセスランキング

アクセスランキング