動画
概要
再設計の背景
どんなアプリケーションをつくっているか
セールス領域に特化して業務生産性を向上するSaaS
大小様々なフォームが約25ほど存在
toBながらもtoCのユーザビリティが求められる
フォームの実装が抱えていた課題
実装方針が統一されていない
useStateであったり react-hook-form を使っていたり、そのハイブリッドだったり
バリデーションの実装方法も場所によってさままざ
強い副作用を持つことによるテスタビリティの低下
ネットワークリソースの取得やグローバルステートへの依存など
StorybookやComponentテストを用意しづらい状態
実装の重複部分が多いことによるメンテナンス性の低下
ほとんど同じUIなのに別々なComponentとして実装
共通化すべき実装が別々に実装されていてメンテナンスが困難
再設計でやったこと
react-hook-form + zod に乗っかる形で統一
フォーム特有の煩雑な状態管理の独自実装を回避し、うまく隠蔽してくれる
両方とも充実したドキュメントと採用事例の多さ
Container / Presentation パターンの導入
Containerコンポーネント: 副作用のハンドリングとビジネスロジックのみに責務を持つ
Presentationコンポーネント: 副作用を極力持たずUIに責務を持つ純粋な関数コンポーネント
ディレクトリ構造の見直し
作成・編集コンポーネントを一つのディレクトリでまとめて管理する
共通する部分の多いUIを共通化しメンテナンス性を向上
4つのこだわり
依存を閉じ込めるディレクトリ構造
一つのディレクトリ配下で作成・編集コンポーネントを管理する
サブディレクトリ内でPresentation コンポーネントとUIの共通実装を管理
↑の Presentation コンポーネントを作成・編集するコンポーネントが利用する形に
下記が参考のディレクトリ構造
FooForms
FooCreateForm.tsx // 作成フォームのContainer
FooUpdateForm.ts // 更新フォームのContainerx
index.ts // 上記2つのContainerのみをexportする
presentations
FooForm.tsx // 作成・編集が使うPresentation
FooFotm.stories.tsx
FooFotm.test.tsx
fields
ComprexField
ComprexField.tsx // FooFrom 固有の入力コンポーネント
index.ts
index.ts
index.ts
react-hook-form の hooks を使ったフォーム簿挙動の安定化
フォームで利用する共通パーツを useController, useFormState でラップしたものを用意する
親のフォームの状態や、特定の入力フィールドの情報を取得することが可能
アプリケーションのフォーム全体の挙動を統一する
フォームオブジェクトのスキーマ定義とバリデーションを zod で一括管理
フォームの入力値を格納するオブジェクトのスキーマをzodで用意する
フォームオブジェクトの型を生成
react-hook-form にわたす validation resolver を生成
Storybook を活用したコンポーネントテストの拡充
フォームはユーザー入力が頻繁に行われるので不具合が起こりやすい
Storybookのストーリーをコンポーネントテストで再利用
感想
動画自体はスライドを基本的読んでいく流れ。
問題or課題定義 => どうやって何をして解決したかを喋っている。
再設計の背景: フォームの実装が抱えていた課題
ナレッジワークさんクラスのエンジニアがいる会社でも実装方針が統一されていなかったり実装の重複部分が多いことによるメンテナンス性の低下っていうのが発生しているというのが意外。同じ人間だという事にちょっと安心。
再設計でやったこと
元がどんな実装だったかはわからないけど、フォームに利用するライブラリは統一するのがベター。
zod は使ったことがないので使ってみるのも有りだが今では他に良いライブラリもあるのかも。yup のが軍配が上がっている記事は見つけた。
Container / Presentation パターンは自分も導入することが多い。
ただ、Presentationにビジネスロジックと副作用のハンドリングを書かないよう気をつける。PresentationはContainerで取得や加工したデータを受け取って表示するのみに徹する。
ディレクトリ構造は作成・編集コンポーネントを一つのディレクトリ配下でまとめるとのことだが、詳しい設計の話は出てこなかった。
propsで isCreate みたいなのを受け取ったら作成用のフォームになるとか??
後述の「依存を閉じ込めるディレクトリ構造」の話で明らかになる。
4つのこだわり
依存を閉じ込めるディレクトリ構造
コンポーネント用のディレクトリ直下にContainerコンポーネントを置く。
同列階層にPresentation用のコンポーネントを入れるサブディレクトリを置く。
Presentation用ディレクトリにPresentationコンポーネントのテスト、Storyを置く。
コンポーネント固有のコンポーネントがある場合は更にサブディレクトリを切ってそこに固有のコンポーネント(Presentation)を置く。
react-hook-form の hooks を使ったフォーム簿挙動の安定化
共通パーツ(inputやbutton)は useController や useFormState でラップしたものを用意しておく。これによって親のフォームの状態や、特定の入力フィールドの情報を取得することが可能。
これらをやっておくとフォーム全体の挙動を統一できる。
VueのVeeValidate でも同じことができそう?
フォームオブジェクトのスキーマ定義とバリデーションを zod で一括管理
VeeValidate v4 と yup でも同じことができる
Storybook を活用したコンポーネントテストの拡充
Storybookのストーリーファイルをテストで再利用できるのを知らんかった
sotrybook/testing-react っていうライブラリの composeStories をimportすれば使えそう