t0mmy

2020年12月30日に参加

学習履歴詳細

実践 Next JS 9章 データ更新とUI 読了

やったこと

  • 実践 Next JS 9章 データ更新とUI 読了

学んだこと

Server Action とは

Form からサーバーの非同期関数を直接呼び出すことが出来る機能。

従来の「API Client を介して Route Handler (API Route) を呼び出す」という手法と比較して、以下のメリットがある。

  • API Client (中間コード)が不要
  • Browser 向けにバンドルされていた API Client が少なくなる
  • ハイドレーションが完了する前に実行できる

Typescript を採用していると、呼び出したい関数の型情報を参照できるため、型安全性が高まる。

Server Action を活用する

基本的な使い方

use server ディレクティブを宣言する。
関数内部で宣言すると、該当関数のみ Server でのみ実行される非同期関数とみなされる。

export default function ServerComponent() {
  const myAction = async () => {
    "use server";
    ...
  }
}

または、use client の時のように、ファイルの先頭に記述することもできる。
この場合、同ファイルに記述された関数全てが Server でのみ実行される非同期関数とみなされる。

Server Action は、 <form> 要素の action 属性に渡して使用するのが一般的。

import {myAction} from "ServerActionを定義したファイルのパス"
...
  <form action={myAction}>
    // any code
  </form>
...

form の action 属性に設定した関数は、第一引数に、 Web 標準の FormDate オブジェクトが渡される。

受け取り側(上記コードだと myAction 関数)は、 formData.get("id) のように、<input> 要素のデータを参照する。

参考: https://developer.mozilla.org/ja/docs/Web/API/FormData

引数のバインド

関数オブジェクトの bind メソッドを使用して、 Server Action に引数をバインドできる。
これにより、引数バインド済みの新しい Server Action を作成できる。
この時、FormdData は、バインドされた引数の後ろに渡される。

Progressive Enhancement

従来、<form> の aciton にはイベントハンドラをアタッチしていた。
これには、ハイドレーションが完了するまで Form イベントを送信できないという欠点がある。

Server Action のように、action に、 Server 側で動作する非同期関数を直接アタッチする場合、ハイドレーションは不要となる。
これは、ハイドレーションが機能しない(≒Javascript が動かない)状態でも動作することを意味している。

この、「Javascript が機能しない状態でも <form> を機能させる」という実装方針を、Progressive Enhancement と呼ぶ。

action イベントに加え、 onSubmit イベントハンドラも同時に設定可能。

この場合、以下のように動作する。

  • ハイドレーション未完了: action
  • ハイドレーション完了: onSubmit

onSubmit だと、バリデーションをはじめとしたクライアント側ロジックを実装できる。
このため、 onSubmit は優れた UX を提供できる(代わりに、ハイドレーションが完了するまで動かない)。

actiononSubmit を同時に実装すると、
action により最低限の動作を保証しつつ、 Javascript が有効な環境では、onSubmit に実装したロジックにより、優れた UX を提供できる。

On-demand Revalidation

キャッシュを無効化するプロセスを On-demand Revalidation と呼ぶ。

(fetch の引数ではなく)以下専用の API を使用することで、任意のタイミングでキャッシュを無効化できる。

  • revalidatePath... 特定の Route のパスでキャッシュを無効化する
  • revalidateTag 特定のタグ文字列でキャッシュを無効化する

キャッシュを無効化するだけで、キャッシュを再作成するわけではない。
(キャッシュを作成するのは、次のリクエスト時)

On-demand Revalidation との対比で、 Time-based Revalidation を「Stale-whilre-revalidate」とも呼ぶ。

Server Action で On-demand Revalidation を使用すると、以下の利点がある。

  • router.refresh() を実行せずとも「Router キャッシュ」がクリアされる
    • redirect 関数を使用すると、更新と同時に画面遷移可能

この利点が大きいため、「データの作成、更新、削除」において、Route Hander よりも Server Action を優先した方が良い。

例外的に、「Next.js アプリケーションの外側から On-demand Revalidation を呼び出す」という処理を実現する場合は、Route Handler の On-demand Revalidation を採用できる。

ユースケース: ブログ記事
- 外部 CMS でブログ記事を書き、Next.js が、CMS からデータを取得するような構成
- ブログ記事更新後、 On-demand Revalidation を実行して、効率よくキャッシュを無効化できる

useFormState

Server Action と組み合わせて使用する、React 標準の Hook。
Form の状態を保持できる。Hook なので、 Client Component でのみ使用可能(そもそも Form 用だし、Client Compoennt でしか使わない)。

使い勝手は、React の useReducer 。

サーバー側で発生した事象を、「状態」という形でフロント側と共有できる。

const [state,dispatch] = useFormState(
  ServerAction に相当する関数,
  {state情報}
);

useOptimistic

楽観的更新を実装できる、React 標準の Hook。

const [optimisticState,addOptimistic] = useOptimistic(
  state,
  (currentState,optimisticValue) => {} //楽観的に更新した状態を返す
)

Revalidate の設計

On-demand Revalidation の考察

データの変更を反映させたい場合は、更新直後に On-demand Revalidation を使用するとよい。
以下二種類が存在する(再掲)。

  • revalidatePath... 特定の Route のパスでキャッシュを無効化する
  • revalidateTag 特定のタグ文字列でキャッシュを無効化する

複数のキャッシュをまとめて無効化したい場合、 revalidateTag を使用することになる。

  • fetch 関数に定義した tags["具体的なタグ名"] を参照し、該当するタグをもつキャッシュをまとめて無効化できる

広範囲のキャッシュを頻繁に無効化すると、キャッシュが活きず、データアクセス速度が低下する。

一方で、局所的なキャッシュ無効化だけだと、キャッシュが悪さ(≒古い情報を提供)し、それを解消するための複雑なロジックを要する。

データ更新のユースケースに併せて、適切なタグの付与が必要。

  • ページをまたいだデータの更新が必要 => 抽象的なタグを用いて一括キャッシュ無効化
  • ID で区別された、一ページ分のデータの更新が必要 => 具体的なタグを用い、スコープを絞ってキャッシュ無効化

参考文献

https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations

Next.js

2024年06月02日(日)

2.0時間