Closed38

半年前の自分に教えたいApp Router周りのこと

hajimismhajimism

Concurrent周りを理解するところから始まるよねー
「Reactはとにかくユーザーにいち早く画面をお届けしたい」っていうのを押さえたい

hajimismhajimism
  1. SSR: 先にHTMLを渡しちゃうことでいち早く画面を表示
  2. Streaming SSR: ↑ページ単位でやってたら時間がかかるので、Suspense境界ごと(コンポーネントごと)に分割してSSR

っていう布石がまずあった

hajimismhajimism

App RouterでgetServerSidePropsがなくなったのは、この考え方とマッチする

↑ページ単位でやってたら時間がかかるので、Suspense境界ごと(コンポーネントごと)に分割してSSR

hajimismhajimism

Suspenseを上手く使えばいろんな指標が改善するよねー、っていう理解が必要

hajimismhajimism


どこまでLayoutにするかは開発者が決められるが、やりたいことはこの「ページ単位ではなくてコンポーネント単位の読み込み」と理解しておけばOK(さっきの動画のスクショ)

hajimismhajimism

じゃあSuspenseてどう使うねん?っていう話

実は公式ドキュメントには「整備にもうちょい時間かかります」とある

Suspense-enabled data fetching without the use of an opinionated framework is not yet supported. The requirements for implementing a Suspense-enabled data source are unstable and undocumented. An official API for integrating data sources with Suspense will be released in a future version of React.

https://react.dev/reference/react/Suspense

hajimismhajimism

Tanstack QueryやSWRとかも(不完全ながらも)Suspenseに対応している。でもApp Routerでおそらく一番使うことになるのがAsync Server Componentsとの組み合わせ

hajimismhajimism
const AsyncServerComponent = async () => {
  const data = await fetch(...)

  return (...)
}

みたいな感じでコンポーネント内部でデータ取得ができるし、AsyncServerComponentはデータ取得時にSuspendするのでSuspenseで包んであげる。

<Suspense fallback={<Skeleton />}>
  <AsyncServerComponent />
<Suspense />
hajimismhajimism

つまりApp Routerでは密かにページ全体がSuspenseで囲まれていることになる

hajimismhajimism

「でもあんまりページ全体でSuspendさせたくないよねー、表示できるところはいち早くお届けしたいよねー」っていう前提があり、データ取得責務は自然と子に降りていくことになる。

hajimismhajimism

getServerSidePropsではいろんなAPIをまとめて叩いてページコンポーネントのpropsに詰めて渡していた。

今は、Suspenseを使ってデータフェッチを分割し、Concurrent(並列)に処理する。そうすることではやくなる。この感覚が大事。

hajimismhajimism

となると、コンポーネントの責務も取得するデータごとに決まってくる。/api/todosを叩くのは<TodoList />であってほしい、みたいな感覚。

hajimismhajimism

APIエンドポイントは基本的に「オブジェクト(モデル)」ごとに作られている。

  • /api/todos:Todoオブジェクトのリスト
  • /api/todos?id=xxx:Todoオブジェクトの単体
hajimismhajimism

「データ取得がモデルベースで行われる」という前提のもと、データ取得の責務を切り分けたいのでContainer/Presenterパターンをよく使う。

ひとつのモデルコンポーネントに対して

  • データ取得のためのContainerコンポーネント
  • モデルに対しての見た目を表現するViewコンポーネント
  • Containerでデータ取得してSuspendするときのfallbackに渡すLoadingコンポーネント

がセットになるイメージ。

hajimismhajimism

↑はデータ取得にTanstackを使っているのでContainerからClient Componentだけれど、

  • データ取得はServerConponentで
  • 複雑なインタラクションがあるViewはClientComponentで

ってなるのが頻出パターン

hajimismhajimism

このコンポーネントはSuspenseで囲うべき!っていうのが名前でわかるように、SuspendableなコンポーネントはXXXContainerで統一する、ってう副次的な効果もある(賛否両論?)

hajimismhajimism

コンポーネント設計がモデル単位に収束していくんだから、ディレクトリ構成もモデルごとに凝集していくといいよねー、っていうような話をここに書いた
https://zenn.dev/link/comments/e38cbd58a8c2dc

hajimismhajimism

App RouterではRoute Segment(ページ単位のまとまり?をそう呼んでいる)ごとのファイル凝集(このページでしか使わない関数はそのページとCo-locationしておく、みたいなこと)が可能になっていてとても便利なんだけど、

https://nextjs.org/docs/app/building-your-application/routing/colocation

Route Segmentが肥大化するのもそれはそれでつらいので、

  1. modelに関心があるやつはとりあえずmodel/にいれとく
  2. modelに関心がなくて、複数箇所で使うようなやつはcommon/にいれとく
  3. modelに関心がなくて、ページ特有のものはRoute Segmentにいれとく

みたいな判断基準がいいかなと思っている。

hajimismhajimism

↑これはapp/内に入れるならってことですよね?補足ありがとうございます!

hajimismhajimism

ここまでで重要なメンタルモデルは拾えている気がするけど、さらにServer Conponentの理解を深めたければここらへんがおすすめ

https://github.com/reactwg/server-components/discussions/5
https://postd.cc/how-react-server-components-work/

hajimismhajimism

なんでキャッシュに注目が当たり始めたのかはここらへん読むと感覚がつかめるかも?さすがに嘘?

Note that there is still a network request per each keystroke. What’s being deferred here is displaying results (until they’re ready), not the network requests themselves. Even if the user continues typing, responses for each keystroke get cached, so pressing Backspace is instant and doesn’t fetch again.

https://react.dev/reference/react/useDeferredValue

hajimismhajimism

なんかもうちょっとある気がするけど一旦満足した

このスクラップは2023/10/05にクローズされました