うぇぶかいはつ

2023-12-23 16:06
2024-02-23 05:30

React に関連するウェブ開発技術

React の基礎知識

React におけるイベントの取り扱い

see also: Responding to Events – React

ネストされたコンポーネントの親に設定されているイベントハンドラは、子コンポーネントに対するイベントも拾う動作となります。これを Event Propagation と呼びます。子コンポーネントのイベントハンドラで e.stopPropagation() を呼ぶと親コンポーネントへのイベントの伝播を止めることができます。

いくつかのコンポーネントにはブラウザデフォルトの振る舞いが設定されているものがあります。例えば form タグについては子コンポーネントで submit イベントが発生するとページのリロードが発生してしまいます。これを防ぐには submit イベントが発生するコンポーネントのイベントハンドラで e.preventDeafult() を呼び出す必要があります。

React における画面更新の流れ

see also: Render and Commit – React

React におけるページ表示内容の更新は次のような流れで行われます。

  • レンダリングのトリガー
    • 初期画面のロード時
    • コンポーネントのステートが更新されたとき
  • コンポーネントのレンダリング
    • 最初のレンダリング: React がルートコンポーネントを呼び出す
    • 以降のレンダリング: 状態が更新された関数コンポーネントを呼び出す
  • DOM への変更の適用
    • 最初のレンダリング: appendChild DOM API を用いてすべてての DOM ノードを追加する
    • 以降のレンダリング: 差分が生じた必要最低限のコンポーネントの DOM のみ変更を適用する
  • (以降ブラウザの仕事) ブラウザが DOM の変更を再描画(ブラウザレンダリング)する
    • React ドキュメントでは React のレンダリング動作とブラウザレンダリングを区別するため、後者をペインティングと記述している

単純に初期画面表示以降のみに絞ると React のレンダリングは、コンポーネントのステートが更新されたことをきっかけとして、該当関数コンポーネント自体が再実行され、その計算結果が現在の DOM のコンポーネントと違う場合のみ変更を適用するという動作となります。

use client と use server ディレクティブ

  • 'use client' は、どのコードがクライアントで実行されるかをマークするディレクティブです。
  • 'use server' は、クライアントサイドのコードから呼び出せる、サーバサイドの関数をマークするディレクティブです。

Next.js の基礎知識

App Router

クライアントコンポーネントとサーバーコンポーネント

see also: 'use client' ディレクティブ – React
see also: 'use server' ディレクティブ – React. see also: React Labs: 私達のこれまでの取り組み - 2023年3月版 – React

React では基本的にクライアントサイドでレンダリングされますが、React チームにより設計された新しいアプリケーションアーキテクチャである React Server Components を使用するアプリではサーバーサイドでレンダリングされます。Next.js の App Router では React Server Components をプリミティブとして採用しており、従来の Pages Router を使ったアプリケーションの構成とはやや考え方が異なります。

Pages Router では基本的にページを単位として SSG, SSR, ISR というレンダリング方法がありましたが、App Router ではコンポーネント単位でサーバーサイドでレンダリングするか、クライアントサイドでレンダリングするかを決定し実装していきます。

クライアントコンポーネントとサーバーコンポーネントの違いは単純にクライアントサイドとサーバーサイドのどちらでレンダリングされるかという点となります。

React Server Components をプリミティブとする App Router ではデフォルトはサーバーコンポーネントとなり、クライアントコンポーネントを作るためにはファイルの冒頭に 'use client' ディレクティブを記述する必要があります。

実際に下記のように非常に単純なクライアントコンポーネントとサーバーコンポーネントを定義し、それらを組み込んだページを表示してみることにより、それぞれのコンポーネントの基本的な動作を確かめることができます。

// ClientComponent.tsx
'use client'

import { Typography } from '@mui/material'
import { useEffect, useState } from 'react'

const ClientComponent = () => {
  const [time, setTime] = useState<string>()
  useEffect(() => {
    setTime(new Date().toLocaleString('ja-JP'))
  }, [])
  console.log('this is a client component')
  return <Typography variant='body1'>Client Component(Updated at {time})</Typography>
}

export default ClientComponent


// ServerComponent.tsx
import { Typography } from '@mui/material'

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

const ServerComponent = async () => {
  await sleep(1500)
  console.log('this is a server component')
  const time = new Date().toLocaleString('ja-JP')
  return <Typography variant='body1'>Server Component(Updated at {time})</Typography>
}

export default ServerComponent


// page.tsx
import ClientComponent from './ClientComponent'
import ServerComponent from './ServerComponent'

const AppDir = async () => {
  return (
    <>
      <h1>Hello</h1>
      <ServerComponent />
      <ClientComponent />
    </>
  )
}

export default AppDir

上記を実行すると Next.js の実行ログに this is a server component がログ出力され、ブラウザに this is a client component がログ出力されます。

ページ、レイアウト、ルートハンドラなどは基本的にサーバー側でビルド時にレンダリングされ、デフォルトではキャッシュされますが、毎回サーバー側でレンダリングを発生させたり、ISR 的な動作で一定間隔おきに再レンダリングしたい場合は revalidate オプションを指定する形となります。

export const revalidate = 10

サーバーコンポーネントと Suspense

一般的にはサーバーコンポーネントがサーバー上でレンダリングしている間、UI にはなんらかのローディング表示が必要となることが多々あります。CSR ではそのようなときローディング状態管理を行いその状態に応じてスケルトンを表示するなどの対応が必要でした。

App Router のサーバーコンポーネントと Suspense という機能を利用するとこのようなローディングの状態の処理が非常に簡単になります。例えば、下記のようにレンダリングに 3 秒かかるコンポーネントがあるとすれば、それを利用する側にて Suspense を用いると fallback 時のコンポーネントを簡単に指定できます。

// ServerComponent.tsx
import { Typography } from '@mui/material'

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

const ServerComponent = async () => {
  await sleep(1500)
  console.log('this is a server component')
  const time = new Date().toLocaleString('ja-JP')
  return <Typography variant='body1'>Server Component(Updated at {time})</Typography>
}

export default ServerComponent


// page.tsx
import { Suspense } from 'react'
import ClientComponent from './ClientComponent'
import ServerComponent from './ServerComponent'

const AppDir = async () => {
  return (
    <>
      <h1>Hello</h1>
      <Suspense fallback={<p>Loading...</p>}>
        <ServerComponent />
      </Suspense>
      <ClientComponent />
    </>
  )
}

export default AppDir

Router Segment Config

see also: File Conventions: Route Segment Config | Next.js

Page, Layout, Router Handler の振る舞いを左右するオプション

  • dynamic: 静的レンダリングを行うか否かを指定するオプションでデフォルトは auto
    • auto: 動的なコンポーネントが存在しなければ可能な限り静的レンダリングを行うオプションでデフォルトの動作となる
    • force-dynamic: リクエスト時に毎回動的レンダリングを行うことを強制するオプション
    • error: よくわかってない(TODO)
    • force-static: cookies(), headers(), useSearchParams() が使われていてもからの値を返す形にて静的レンダリングを強制する
  • dynamicParams: 未生成の dynamic segment にアクセスがあった際の振る舞いをコントロールするオプションでデフォルトは true
    • true: generateStaticParams に含まれていないページも動的に生成するオプションでデフォルトの振る舞い
    • false: generateStaticParams に含まれていないページは 404 を返す
  • revalidate: layout や page の revalidation time を設定するオプションでデフォルトは false
    • false: fetch リクエスト force-cache として取り扱いキャッシュするオプション
    • number: layout や page を revalidate する間隔を指定するオプション
    • 0: 動的な関数やキャッシュしないデータフェッチが見つからない場合でも常に動的に layout や page を動的にレンダリングするオプション。明示的に force-cache していたり、revalidate を指定しているものはこの限りでない。
  • fetchCache: advanced なオプションにつき省略
  • maxDuration: サーバーサイドロジックの最大実行時間で、Vercel を利用する場合は 10 秒が上限

課題

関連リンク集

Vercel

Vercel Serverless Functions

Serverless Functions をセキュアにするためにはどうすれば良いのか

authorization ヘッダーを利用して API キーベースの認証を導入する方法がある

Authorization flow To securely trigger API routes and serverless functions with Github Actions, you need to provide an authorization key in the header of your API call, which, when executed, gets compared to a corresponding key in your Next.js application. You can achieve this by adding Encrypted Secrets to your Github repository and passing them with the header of your HTTP request, like shown in the previous code snippet. Along with adding the key to your Github repository, you also need to access it within your Next.js application, preferably through Environment Variables. How to secure serverless functions?? · vercel · Discussion #1833

Vercel Cron Jobs と組み合わせるときには CRON_SECRET という環境変数を利用するスペシャルな手法 が取れるが、Hobby プランは 1 invocation/day という厳しい制約があり、まあ API Key + EventBridge 使うことになるかなと思う

Copyright © 53ningen.com