Open25

次世代 React CSR SPA 開発 学習メモ

ピン留めされたアイテム

React 18 で Concurrent Rendering が実現される。これを使う実装パターンやライブラリが整った状態を当面の次世代と位置づけ、そこに向けて現在の React SPA 開発からの差分をキャッチアップしていく学習のメモをここに残す。

(SSR や ISR はメモ作成者の興味対象外なので特に触れる予定はない)

従来の React CSR SPA 開発でも React.lazy によって Suspense の機能は使えた。遅延読み込みさせることでコード分割が可能になり、読み込み速度[1]と記述のしやすさが向上した。

https://ja.reactjs.org/docs/code-splitting.html#reactlazy
脚注
  1. 正しくは必要部分の読み込み時間もしくは見かけ上の時間が短くなるということで、往復回数が増える分すべてを読み込むまでの時間は伸びる。 ↩︎

Suspense を、ボイラープレート的に使ったりそれをコードレビューで確認するような、普段の実装の 8 割程度を占める難度の内容を理解するので十分であれば、サマリ的なこの文書を読めばよい。

https://qiita.com/uhyo/items/bbc22022fe846fd2b763

差分

  • 読み込み処理は isLoading のような状態でなく Suspense で宣言的に表現できる
  • 複数コンポーネントをまとめてサスペンドできるため、複雑な表示制御は手続き的処理より簡単に書ける

Suspense の処理を学ぶにはこのハンズオンが良い。実装パターンやライブラリの使い方を軽く読むのに十分なレベルの知識が得られるはずだ。

https://zenn.dev/uhyo/books/react-concurrent-handson

差分

  • ローレベルでは、コンポーネント内処理で Promise を throw して Suspense で受け取るという流れになる
  • Suspense という概念の導入によりコンポーネントのステートに注意事項が増える
  • Suspense は(CSR では)主にサスペンドの境界を定める
  • render-as-you-fetch パターンが(ある程度実現しやすい形になって)登場する

render-as-you-fetch パターンは、手前味噌な概念だが「犠牲的モジュラフロントエンド」 https://zenn.dev/occar421/scraps/a02ac7b40c0df1 の実現に大きな役割を持つと考えている(fetch の物理処理と論理処理の間をいい感じにやるライブラリはさらに必要)。

Suspense を使いこなすところに到達するにはこのハンズオンが良い。Suspense によりより良い UX を提供するための知識が得られるはずだ。

https://zenn.dev/uhyo/books/react-concurrent-handson-2

差分

  • トランジションという優先度の低いステート更新という概念ができる
  • トランジションは「表の世界」でなくまず「裏の世界」(平行世界)に反映され、終わり次第「表の世界」に現れる
  • 実際にはトランジションは記録され、「表の世界」が変わるたびに毎回「裏の世界」(平行世界)が生成される
  • useTransition から得られる startTransitionisPending により、トランジションさせるための関数とその処理中のフラグのペアが得られる

どこかに載っていたのか、それを見て自分で思いついたのかは不明なこと。Suspense の概念の捉え方について。

  • 「Suspense=読み込み処理」や「Suspense=(変数としての)読み込み中フラグが不要」という捉え方ではかなり不十分
    • Suspense 自体は処理を待つ(サスペンド)という結構抽象的な概念
      • 今まではフラグなどの変数と手続き的な処理で実現していたがこれが変わる
      • 何の理由で待っているかは Suspense 部分は知る由もない
      • その一番わかりやすい具体例として、読み込み処理が終わるまで待つ、という例が頻繁に紹介される
    • すべてのコンポーネントはサスペンドする可能性がある
      • 今までは読み込み処理が終わるまで待つことをフラグで表現すると局所的だったがこれが変わる
      • どこが読み込むか(局所性を)考えるのは不要になるが、逆に、常にどこでも考えるべき観点になった
  • ErrorBoundary と似たようなもの
    • こちらも(最終ゲートとしてでなく)Suspense のような使い方で積極的に使っていく可能性がある
  • データ取得コードと取得後の表示コードの近接の効果は無視できない
    • 親で data を渡してくるのも動くには動くが一度に読むべきコード量が増えてしまう

render-as-you-fetch を実現しやすくなる(と筆者が予測している)GraphQL の利点などはこの記事でおさらいできる。

https://zenn.dev/yoshii0110/articles/2233e32d276551

https://speakerdeck.com/quramy/graphqltofalsexiang-kihe-ifang-2022nian-ban

RESTful API だったり Redux だったりを使い、よく考えていればそれらでも render-as-you-fetch は可能ではあるだろう。

React Location のようなルーターを使う手もあるかもしれない。

GraphQL の Fragment Colocation とその自動巻き上げ(希望的には Location に応じた root での必要最小限集合の fetch)が利用できるのは大きいと思う。

render-as-you-fetch はここが整理されていると思う。

https://scrapbox.io/tosuke/Render-as-You-Fetch
  • fetch-on-render, fetch-then-render, render-as-you-fetch の違い

「コンポーネント A はロケーション B の時にデータ C を要求する」ことが事前に解析できれば、コンポーネントが render される前に読み込みが走り render が走りサスペンドし、随時表示されていくのでうれしいはず。

Fragment Colocation で推奨されるような GraphQL と React とでそれぞれ import する鎖を活用すれば良さそうではある。ただし、useFragment のようなものは Suspense 実現のために必要になる。


export const PetPageGql = gql`${PetQuery}`;

export const PetPage() {
  const [initialQueryRef] = useSomething();
  const [petReference] = useQueryLoader(PetPageGql, initialQueryRef);
  return <Pet reference={petReference} />
}
export const PetQuery = gql`query Pet {
  pet() {
    name
    ...PetDetail
  }
  ${PetDetailFragment}
}`;

export function Pet({ reference }) {
  const { data: { pet: { name } } } = usePreloadedQuery(PetQuery, reference);
  return <div><span>{data.name}</span><PetDetail reference={reference} /></div>
}
export const PetDetailFragment = gql`fragment PetDetail on Pet { description }`;

export function PetDetail({ reference }) {
  const { data: { description } } = useFragment(PetDetailFragment, reference);
  return <p>{description}</p>
}

fetch-on-render

イメージ

fetch-then-render

イメージ

render-as-you-fetch

イメージ

<Suspense fallback={<div>foo-bar</div>}>
  <Component data={data} />
</Suspense>

これをやると GraphQL をクライアントで使う感じがつかめそう。Fragment Colocation も。

https://github.com/Quramy/gql-study-workshop

Fragment Colocation はメモの筆者のイメージと違い、機能やライブラリではなくファイル構造設計に近いような話だった。大きく構えすぎていた。

サブコンポーネントでの useFragment 相当の取得方法ができて初めて Fragment Colocation が本領を発揮しそうだし、render-as-you-fetch も理想に近づきそう。

GraphQL で出てくる概念諸々

"GraphQL Federation"

現状は Apollo での実装(Apollo Federation)しかない?

https://moneyforward.com/engineers_blog/2021/12/20/graphql-federation-2/

メモ作成者の現在の状況からすると必要ではないので、自分では深追いはしないでおく。

Persisted Query

https://qiita.com/Quramy/items/b3943a0c27f3ade2c57d

https://speakerdeck.com/indigolain/persisted-querywoyatutemita

感じたメリット

  • 意図しないクエリを封じる
  • リクエストのペイロード量の削減

感じたデメリット

  • 登録クエリ管理が難しそう
  • API リクエスト時のオンデマンドなクエリ変化という利点が失われてしまうのではないか?

Apollo の Automatic Persisted Queries は面白そうだった。一応、意図しないクエリで負荷がかかる課題は Query depth, Query complexity, Query rate limit の制限である程度緩和できるはず(リンクは後述)。

とりあえずはライブラリやサーバーが良しなにやってくれると信じて、フロントエンドではこれらは検討しなくていいんじゃないかな…最適化段階か何かでちょっと弄れば大丈夫そうだし。

(TODO)
production ready react 18 + graphql

https://engineering.mercari.com/blog/entry/20220303-concerns-with-using-graphql/

https://docs.github.com/ja/graphql/overview/resource-limitations#rate-liresource-limitationsmit

他にも GraphQL の資料はあるはず

海外の有料の資料(Production Ready GraphQL)

エラーのリソース定義のブログ記事(社の Slack にリンクがあるので探せばリンクは見つかる)

React 18 の Production Readiness はどう考えればよいのだろうか。

(TODO)
一応はバックエンドの GraphQL サーバー事情も少々…

何故かわからないが、GraphQL のクライアントライブラリとして、触る前から Apollo はなんか違うという気がしたので後回しにする。Meta 謹製の Relay か urql を触ってみる。

React Suspense や Fragment Colocation を使ったときに便利なものを見つけ出したい。

https://nulab.com/ja/blog/nulab/graphql-apollo-relay-urql/

(TODO)

  • relay
  • gqless 🤔
  • urql
ログインするとコメントできます