Background
Next.jsで利用できるServer ComponentsとServer ActionsはReactの機能ということを最近知ったものの、Next.js側のドキュメントばかり読んでReact側のドキュメントは読んだことがなかったのでこれを機会に読んでみた。
Disclaimer
自分が初めて知った部分・重要だと思った部分のメモなので全体の要約ではありません。
Server Components
import FancyText from './FancyText';
import InspirationGenerator from './InspirationGenerator';
import Copyright from './Copyright';
export default function App() {
return (
<>
<FancyText title text="Get Inspired App" />
<InspirationGenerator>
<Copyright year={2004} />
</InspirationGenerator>
</>
);
}
-
この例でCopyrightコンポーネントはInspirationGeneratorにJSXのpropsとして渡されている
- つまり文字列になっている
-
利用帯域の観点で言うと、「Server ComponentでバンドルされたHTML文字列 < Client Componentとしてクライアント側で評価・レンダリングされるソースコード」場合にのみServer Componentの優位性がある
- 実際にはサーバー側のほうがクライアント側よりスペックが良いためコンポーネントの評価にかかる負荷・実行時間が有利などの観点もあるので一概には決められない
- 例として挙げられているのはSVGパス文字列だが、リストでカードを表示するようなUIなども当てはまるはず
-
Server componentが常にweb serverで実行されるとは限らず、
next build
などページビルド時にレンダリングされることもある(参考) -
Client ComponentはServer Componentを受け取ることはなく、レンダリングされた結果の文字列を受け取る
Server Actions
-
Server Actions呼び出し時には、Server ComponentとClient Component間での情報のやりとりはシリアライズされた状態の引数および返り値によって実装されている
- シリアライズできない型:
- JSX
- 関数(関数コンポーネント含む。Server Actions以外)
- クラス
- クラスのインスタンス
- シンボル
- シリアライズできない型:
-
Server Actionsはデータ更新目的を想定しているので、データの取得は推奨されない
- Server Componentでのデータ取得→fetch、DBのクエリなど
- Server Componentでのデータ更新→基本ない
- Client Componentでのデータ取得→なるべくServer Componentに寄せる
- URLに紐づいたデータ取得をできるよう設計し、ソフトナビゲーションでデータ取得する
- 画面イベントに応じてURLの変化なしにコンポーネントをリフレッシュする必要がある場合、router.refresh()をしてデータを再取得する
- それでも要件を満たせないならSWRとか使う
- Client Componentでのデータ更新→Server Actions
-
Server Actions使用時には、Server ActionsをClient Componentに渡すが、その渡し方は2つ
- Client Component内でServer Actionsをimportする
- Server Actionsがファイルの行頭で'use server'されている必要がある
- bundlerがClient ComponentをビルドするときにServer Actionsへの参照を作成する
- この場合にDynamic importって効くのだろうか?
- Server ComponentからClient ComponentへServer Actionsを引数として渡す
- Server ComponentをレンダリングするときにServer Actionsへの参照を作成する
- アプリケーションの実行時にClient側が利用するServer Actionsを動的に変えたいということがある場合(あるのか?)にはServer Componentから引数で渡す方が良い
- 基本的にはServer側で受け取った情報に応じてServer Actionsの動作を分ける方がいいと思う。そうでないとClient側がServer側の詳細を知りすぎる
- Client Component内でServer Actionsをimportする
-
Server Actionsを
<form>
外で利用することもできる。その場合にはuseTransition
startTransition
を使う-
const currentCount = await incrementLike();
のようにServer Actionsの返り値をClient Component側で参照して、それをstateに反映できる - 内では自動的にTransitionにラップされるとのこと
-
import incrementLike from './actions';
import { useState, useTransition } from 'react';
function LikeButton() {
const [isPending, startTransition] = useTransition();
const [likeCount, setLikeCount] = useState(0);
const onClick = () => {
startTransition(async () => {
const currentCount = await incrementLike();
setLikeCount(currentCount);
});
};
return (
<>
<p>Total Likes: {likeCount}</p>
<button onClick={onClick} disabled={isPending}>Like</button>;
</>
);
}
-
useActionState
を使ってサーバーサイドでの更新時に処理の状態を確認できる