Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Suspenseのユースケースを探る

 Suspenseのユースケースを探る

KeitaroOkamura

February 08, 2022
Tweet

More Decks by KeitaroOkamura

Other Decks in Technology

Transcript

  1. Suspenseのユースケースを探る
    FukuokaJS #15 2021/02/08

    View full-size slide

  2. 自己紹介
    ● Mercari, Inc
    ● Software Enginner
    @karaagekeitaroo
    Keitaro Okamura @peter

    View full-size slide

  3. はじめに

    View full-size slide

  4. 知らない方のために
    ● 「Suspense」 は次期メジャーバージョンの React 18 で大きく拡張された機能
    ○ 以前から存在していたが、それまではコンポーネントの Dynamic Import のために使われていた
    ● React 18 では Server-Side Rendering (SSR) のストリーミングサポートが追加される
    ○ この SSR ストリーミングは Suspense を前提にしている
    ○ また、「React Server Components」 という技術的革新においても Suspense(正確に言えば、React
    Concurrent Rendering という機能)が重要な役割を果たします
    ● つまり
    ○ 技術的な転換期に入ってきており(SSG → SSRという流れが強くなっている)様々なステート管理や
    データ取得ライブラリが Suspense を含む React 18 への対応を始めている
    ○ Suspense のおさらいをしないと僕みたいに取り残されますw

    View full-size slide

  5. Suspense とは何か
    簡単に言うと
    ● コンポーネントを「ローディング中なのでまだレンダリングできない」
    という状態にすることができるのが Suspense です
    ● Suspense というコンポーネントが、内部でレンダリングが失敗したコン
    ポーネントのハンドリングを担当してくれます
    ○ まるで JavaScript の try-catch 文のような振る舞い
    ○ React の既存機能である ErrorBoundary と Suspense の仕組みは
    似ていて、キャッチする対象が違うだけ
    ローディング中


    キャッチ

    エラー

    キャッチ

    View full-size slide

  6. Suspence のユースケース
    ● サスペンスは、データも含むその他あらゆるものを宣言的に「待機」するための機能
    ○ メインのユースケースはデータ取得だが、画像やスクリプト、あるいはその他の非同期的な作業の待機
    にも使える
    ○ つまり、コンポーネント以外のリソースの取得を待機することができるようになった
    Suspense for Data Fetching is a new feature that lets you also use to declaratively “wait” for
    anything else, including data. This page focuses on the data fetching use case, but it can also wait for images,
    scripts, or other asynchronous work.
    https://reactjs.org/docs/concurrent-mode-suspense.html
    公式には以下のように書いてある

    View full-size slide

  7. Suspense の典型的な例
    before after
    Suspense を使ったAPIからのデータ取得の例を挙げてみます(SWRを使用する例)

    View full-size slide

  8. コンポーネントリソースやスクリプト取得などの待機にも使える(next/dynamicを使用する例)
    データ取得以外の例

    View full-size slide

  9. 1. コンポーネントが「ローディング中」を宣言する部
    分。それは、コンポーネントがレンダリング中に
    Promise を throw することで行う
    2. Suspenseコンポーネント自体。このコンポーネント
    で囲った部分でサスペンド※が発生した場合、指定
    しているフォールバックコンテンツが代わりにレン
    ダリングされる
    ※Promise が解決するまではコンポーネントをレンダリングできない状態

    loading…}>
    loading…
    Suspense のAPI は主に2つの要素で成り立つ
    <子コンポーネント />
    ローディング中
    Promiseをthrowする
    フォールバックコンテンツを指定
    Suspense の仕組み
    キャッチ

    View full-size slide

  10. Suspence の仕組み
    ● Promise を投げるのは、それをキャッチした Suspense に「このコンポーネントはいつレンダリングができ
    るのか」を伝えるため
    ○ 投げられた Promise が解決されたら、それを投げた子コンポーネントのレンダリングをリトライする
    という仕組みになっている
    ● render やライフサイクルメソッドでエラーが起きたときに、それを親コンポーネントの
    componentDidCatch※というライフサイクルメソッドでキャッチするという仕組みで Suspense は動きます
    ※React で子コンポーネントのランタイムエラーをキャッチするためのハンドラ
    詳しく説明すると

    View full-size slide

  11. ここから本題

    View full-size slide

  12. ● アンマウント
    ○ コンポーネントライフサイクルメソッドの一つ => componentWillUnmount()
    ○ コンポーネントが DOM から削除されるときに呼ばれる
    ● つまり、Suspense によって置き換えられた子コンポーネントは UI が破棄されない
    ○ UI が破棄されないので、画面の状態※を残したままにできる
    ○ 何度も再描画されてほしくなく、状態を保持したままでいてほしいようなケース
    ○ ※どういったケースで使えるのか、実際に実験してみる
    Suspense によって置き換えられた子コンポーネントは、アンマウントされない
    Suspense コンポーネントの特徴

    View full-size slide

  13. ● 例えば、ページ全体を囲うモーダルのようなケース
    ○ State で表示状態を管理し、その状態によって描画
    (表示/非表示)を切り替えている
    ○ 状態の変化によって、アンマウントが発生する
    State によって描画を制御するケース
    Suspense のユースケースを探る

    View full-size slide

  14. ● アンマウントされると UI が破棄されるので、入力中の input
    の値や、スクロール位置などの状態が破棄されてしまう
    ● 同様なケースとしては、タブ切り替えなど
    ● 破棄されるのは UI の状態だけ、入力中の値やスクロール位置
    を State(コンポーネントによって管理されているオブジェ
    クト) で管理していれば破棄されない
    何が困るか?
    Suspense のユースケースを探る

    View full-size slide

  15. Suspenseを使ってみる

    View full-size slide

  16. ● Suspense を利用したコンポーネントを作ってみる
    ● このコンポーネントの中身は、props で受け取った値に応じ
    て空の Promise を throw するというシンプルな中身
    ○ Loadable が ローディング中であることを Promise を
    throw することで Suspense に伝える
    ○ Suspense は その Promise を監視する
    ○ fallback は指定しなかったら何も表示されない
    Promise を throw すればいいってことはわかった
    Suspense を使ってみる

    View full-size slide

  17. ● 描画の制御を Suspense を囲うことでおこなう
    アンマウントされないので、UI が保持される
    Suspense を使ってみる

    View full-size slide

  18. ● アンマウントされないことで、ある瞬間にユーザーから見えない部分について、不要な再レンダリングを回避で
    きる
    ○ つまり、結果として React が不要な計算(Reconciliation のような差分検出処理)をするのを省くことが
    できる
    ○ 今回の実験したケースでは、DOM自体が大きくないので、そこまでパフォーマンスに影響はないかもしれ
    ない(一つのコンポーネントで管理する派の人たちもいるはず)
    ● しかし、忘れてはいけない制限として
    ○ Suspense で囲った子コンポーネントは 一時的に画面から削除され、プレースホルダーに置き換えられる
    ○ つまりは、通常はユーザーに表示されないような部分があるケースのみ、Suspense の性質を利用するこ
    とができる
    UI を保持すること以外にもうまみがある
    Suspense のうまみ

    View full-size slide

  19. ● 最もふさわしいユースケースはネイティブアプリの Stack 型のナビ
    ゲーション
    ○ このSuspenseの性質を利用することで Stack の一番上にある
    コンポーネント以外を停止することで、効率良く画面の状態
    をロックすることができる
    ● React Native の contributor の人が React Native 向けにコンポー
    ネント作ってました
    ○ https://github.com/software-mansion/react-freeze
    それネイティブアプリじゃん
    通常はユーザーに表示されないような部分があるケース

    View full-size slide

  20. ● Suspenseはデータ取得以外のユースケースにも活用できる
    ● Promise を throw すること自体は難しくないが、非同期のデータ取得(データフェッチ系)が絡むと
    一から書くのは難しそう
    ○ そこは素直にライブラリ使ったほうがいいと思う
    ● Suspense を使うことで、今までは ローディング の状態を判定して処理をしていたような、手続き的
    なコードがより宣言的に書けるようになる
    ● Suspense によって今までのコンポーネントの責務が大きく変わっていきそう(コンポーネントがロー
    ディング中の表示の責務を持たないとか)
    まとめ

    View full-size slide

  21. 参考
    ● React 18に備えるにはどうすればいいの? 5分で理解する
    ○ https://qiita.com/uhyo/items/bbc22022fe846fd2b763
    ● ReactのSuspense対応非同期処理を手書きするハンズオン
    ○ https://zenn.dev/uhyo/books/react-concurrent-handson
    ● Experimenting with React Freeze
    ○ https://blog.swmansion.com/experimenting-with-react-freeze-71da578e2fa6
    ● ソースコード
    ○ https://github.com/KeitaroOkamura/suspense-usecases

    View full-size slide

  22. おしまい
    Thank you for listening.

    View full-size slide