これは何
Next.js App Routerでは、データfetchをとっとと下層に動かすと幸せという話。
幸せを噛みしめるために、今までfetchで困ってきた歴史?の説明。
クライアントサイドfetchの問題
ほとんどのデータは全ユーザーに共通のものだ。
これをクライアントサイドから
const Component = () => {
const [data, setData] = useState();
useEffect(() => {
fetch('https://api.com')
.then((res) => res.json())
.then((json) => {
setData(json);
});
}, []);
return <>{data}</>;
};
このようにfetchすれば、サーバーサイドレンダリングのメリットの多くを失うことになる。
- 表示が遅い。ファーストビューにあればチラつきが見える。
- APIリクエストが多い。10分間キャッシュして良い内容であっても、10分で100人が来れば100回リクエストが出る。
- ややこしい
Pages Routerでの問題
Pages Routerでは、サーバー上でのみfetchする独自の方法が用意された。
getServerSidePropsなどでfetchし、レンダリングまで済ませてユーザーに送信することができる。
export async function getServerSideProps() {
const data = await fetch('https://api.com');
return {
props: { data },
};
}
export default function Page({ data }) {
return <>{data}</>;
}
しかし、これにも問題があって...
最上位のpageコンポーネントにおいてしか動かない。
例えば
- 記事
- お天気ウィジェット
- 占いウィジェット
を表示するページがある時、各UI部品が必要とするデータをページレベルでまとめてfetchし、丁寧にUIまでバケツリレーすることになる。
他のページでもお天気と占いを表示したくなれば、fetcherを独立させることになる。
データはページと一致するものではない。それを必要とするUIと結合しているものだ。
この例で、お天気fetcherの役目はお天気コンポーネントへのデータ提供だが
- コンポーネントは呼び出されるページと密結合している。このテンプレに占いを入れよう!という作業の影響範囲はページから占いコンポーネントまでの全てにわたる
- バケツリレーは長い
- フォルダは遠く、一緒に作業し辛い
千本引きの紐に頑張ってラベルをつけてる気持ちになる。
App Routerの特徴
App Router最大の特徴は、React Server Componentsが使えることだ。
結論だけ言えば、扱い方は以下になる。
- デフォルトで全てのコンポーネントはRSCである。
- RSCとは「ハイドレーションされない=静的HTMLになる代わりにサーバーサイドfetchができる」コンポーネントである
- ハイドレーションが必要になった階層で
'use client;'
で「クライアント境界」を設定する。そこから下層は全て標準コンポーネント(クライアントコンポーネント)になる。
繰り返しになるが、RSCの現状ほぼ唯一かつ大きな強みは「fetchができること」だ。
ハイドレーションが必要な代表的機能
- useState
- useContext (Providerは子に寄せることで不要)https://future-architect.github.io/articles/20231214a/
- useRef
- useEffect
App Routerでの構造
晴れて好きなコンポーネントでfetchできるようになったので、コンポーネントは自身が必要なデータの取得まで担当するのが一番だ。
「お天気を取得し表示する」
「ユーザーIDだけ受け取ったら残高を取得し表示する」
などである。
部品の独立性は一気に高まり、コードの改修は容易になる。
作業としては、fetchをUIと一致する階層まで「下ろす」構造転換になる。
なお、fetchしたい階層とハイドレーションしたい階層が同じなら、新しく「fetch層」「ハイドレーション層」の2層に分けると良い。
//components/Otenki/index.tsx
import UIComponent from './UIComponent'
const DataComponent = () => {
const data = await fetch('https://api.com');
return <UIComponent data={data}/>;
};
//components/Otenki/UIComponent/index.tsx
'use client';
import { useState } from 'react';
const UiComponent = ({ data }) => {
const [state,setState]= useState();
//なんでもする
return <>{state}</>
};
階層が増えてしまうが、千本くじより良い。
雑記
- styled-componentsなどのCSS-in-JSライブラリのほとんどはRSCに対応する気配がない。スタイルを1行書けばそれ以下ではfetchできないので、大きなマイナスである。Next.js使いは離れる時かもしれない...
- RSCはReactの機能だが、現在まともに使えるのはNext.js App Routerだけなので、Next.jsの機能と考えて問題ない