はじめまして。プロダクトエンジニアのもっさんです。
この記事は READYFORアドベントカレンダー2021 、3日目の記事です。
はじめに
READYFORでは今年の9月ごろに、プロジェクトを実行する実行者が、支援してくれた方々とのコミュニケーション及びリターンの管理機能などを管理するための機能を一新し、リリースしました。(詳細はこちら)
今回は、そこで新しく作成した SPA での State 管理についての話をします。
ページ構成
今回リリースした SPA の各ページでは、大きく分けてサーバーから取得したデータを表示するテーブル部分と、ユーザーが検索したい内容を入力するフォーム部分の 2 つに分かれます。検索内容は URL Parameter から渡され、 API のパラメータ及び、フォームのデフォルト入力値として利用されます。
このページが状態として持っている箇所は、大きく分けて以下の 3 つになります。
- URL のパラメータ (Query)
- 検索フォーム (Form State)
- テーブル (Resource State)
この State を何も考えずに扱うと State 間での不整合が生まれるため(formで検索した値がテーブルにだけ反映されて、URLパラメータへの反映を忘れてしまうなど)
、ひと工夫が必要です。
前提として以下のライブラリ、ツールが利用されています。
Next.js
site: https://nextjs.org
vercel が提供している React.js 製のフロントエンドフレームワークです。
Next.js は、本番環境に必要なすべての機能(ハイブリッド静的およびサーバーレンダリング、TypeScript サポート、スマートバンドリング、ルートプリフェッチなど)をゼロコンフィグで利用できます。
SWR
site: https://swr.vercel.app
Next.js の開発元である vercel が提供しているデータ取得のための React Hook ライブラリです。
SWR は、まずキャッシュからデータを返してからフェッチリクエストを送り、最後に最新のデータを持ってくるという戦略を取っています。
React Hook Form
site: https://react-hook-form.com
高性能で柔軟かつ拡張可能な使いやすいフォームバリデーションライブラリです。
最小限のコードでフォームを作成でき、 TypeScript との親和性が非常に高いため、型安全なフォームを作成できます。
openapi-typescript
site: https://github.com/drwpow/openapi-typescript
openapi の定義から TypeScript の型を生成するための node ライブラリです。
pathpida
site: https://github.com/aspida/pathpida
Next.js の Page コンポーネントの定義から自動的に URL の型定義ファイルを生成するライブラリです。
型の依存関係
基本となる型定義は openapi-typescript を用いて、schema.yaml から自動的に型定義ファイルを生成しています。生成された型をもとに、API Query の型、Form で扱うための型及び、ページの Query Parameter の型を定義します。基本的には生成された型からエイリアスを貼り、必要な型を抽出、結合して、利用します。ただし、Form においては input の型の都合上、例外的に独自の型を宣言する場合がありますが、その場合でも型の宣言・利用は Form State と Form View 内で完結します。Form Hook が Props として受け取る型は加工せず、また、他のパートへの型の変更の影響を与えることはありません。
データフロー
データフローの考え方において、以下の図のようなレイヤに分けて考えます。各モジュール、親コンポーネントのそれぞれのレイヤで、どのような責務を持つかを定義します。
Query
このレイヤーでは、 Next.js の useRouter を利用して URL パラメータにアクセスします。
Query では以下の責務を持ちます。
- ページに必要なパラメータの型定義(pathpida を利用)
- パラメータの型の変換と型の保証
- 必須パラメータのチェック(存在しない場合はエラーへの遷移)
- Template レイヤへの値の受け渡し
Template
このレイヤでは Query レイヤからのデータを受け取り Hook へのデータの受け流しを行います。また Hook と View の繋ぎ込みを行います。
表示の出し分け等は行いますが、基本的にロジックを持ちません。
Form State
ユーザの操作により、変化する値を管理するレイヤーです。検索フォームの入力や、ページ情報、ソート情報の保持、ハンドラ定義などもこのレイヤでおこないます。
実装上は Hook として切り出していて、 State 管理には React Hook Form を利用しています。UI の制限により、State で保持する値や型を変更している場合には、この State 内に閉じた型を宣言します。データの変化がある場合は Next.js の push アクションを経由して URL の変更し、 Query レイヤーの State の変更します。
Resource State
受け取った Props を元に API からリソースの取得をおこないます。Form State と同じく実装上は Hook として切り出していて、State の管理及びリソースの取得には SWR を利用しています。 ユーザの操作によって検索条件が変更になった場合、Form の State から直接パラメータを受け取ることはせず、必ず Query と Template を経由してパラメータを受け取ります。
まとめ
今回のまとめた複数 State 管理の考え方は、 Flux のイメージから設計されています。複数モジュール間におけるデータの流れを一方向にし、ユーザの操作による検索パラメータの適用を push アクションに集約しています。これにより、複数の State の整合性が保ちやすくなりました。
明日はTakepepeさんによるフロントエンドのテストに関する話です!