ナレッジワークのフォーム設計へのこだわり を見た

nagasawaaaa
·

動画

概要

再設計の背景

どんなアプリケーションをつくっているか

  • セールス領域に特化して業務生産性を向上するSaaS

    • 大小様々なフォームが約25ほど存在

    • toBながらもtoCのユーザビリティが求められる

フォームの実装が抱えていた課題

  • 実装方針が統一されていない

    • useStateであったり react-hook-form を使っていたり、そのハイブリッドだったり

    • バリデーションの実装方法も場所によってさままざ

  • 強い副作用を持つことによるテスタビリティの低下

    • ネットワークリソースの取得やグローバルステートへの依存など

    • StorybookやComponentテストを用意しづらい状態

  • 実装の重複部分が多いことによるメンテナンス性の低下

    • ほとんど同じUIなのに別々なComponentとして実装

    • 共通化すべき実装が別々に実装されていてメンテナンスが困難

再設計でやったこと

  • react-hook-form + zod に乗っかる形で統一

    • フォーム特有の煩雑な状態管理の独自実装を回避し、うまく隠蔽してくれる

    • 両方とも充実したドキュメントと採用事例の多さ

  • Container / Presentation パターンの導入

    • Containerコンポーネント: 副作用のハンドリングとビジネスロジックのみに責務を持つ

    • Presentationコンポーネント: 副作用を極力持たずUIに責務を持つ純粋な関数コンポーネント

  • ディレクトリ構造の見直し

    • 作成・編集コンポーネントを一つのディレクトリでまとめて管理する

    • 共通する部分の多いUIを共通化しメンテナンス性を向上

4つのこだわり

  1. 依存を閉じ込めるディレクトリ構造

    • 一つのディレクトリ配下で作成・編集コンポーネントを管理する

    • サブディレクトリ内で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

  2. react-hook-form の hooks を使ったフォーム簿挙動の安定化

    • フォームで利用する共通パーツを useController, useFormState でラップしたものを用意する

      • 親のフォームの状態や、特定の入力フィールドの情報を取得することが可能

    • アプリケーションのフォーム全体の挙動を統一する

  3. フォームオブジェクトのスキーマ定義とバリデーションを zod で一括管理

    • フォームの入力値を格納するオブジェクトのスキーマをzodで用意する

    • フォームオブジェクトの型を生成

    • react-hook-form にわたす validation resolver を生成

  4. Storybook を活用したコンポーネントテストの拡充

    • フォームはユーザー入力が頻繁に行われるので不具合が起こりやすい

    • Storybookのストーリーをコンポーネントテストで再利用

感想

動画自体はスライドを基本的読んでいく流れ。

問題or課題定義 => どうやって何をして解決したかを喋っている。

再設計の背景: フォームの実装が抱えていた課題

ナレッジワークさんクラスのエンジニアがいる会社でも実装方針が統一されていなかったり実装の重複部分が多いことによるメンテナンス性の低下っていうのが発生しているというのが意外。同じ人間だという事にちょっと安心。

再設計でやったこと

元がどんな実装だったかはわからないけど、フォームに利用するライブラリは統一するのがベター。

zod は使ったことがないので使ってみるのも有りだが今では他に良いライブラリもあるのかも。yup のが軍配が上がっている記事は見つけた。

Container / Presentation パターンは自分も導入することが多い。

ただ、Presentationにビジネスロジックと副作用のハンドリングを書かないよう気をつける。PresentationはContainerで取得や加工したデータを受け取って表示するのみに徹する。

ディレクトリ構造は作成・編集コンポーネントを一つのディレクトリ配下でまとめるとのことだが、詳しい設計の話は出てこなかった。

propsで isCreate みたいなのを受け取ったら作成用のフォームになるとか??

後述の「依存を閉じ込めるディレクトリ構造」の話で明らかになる。

4つのこだわり

  1. 依存を閉じ込めるディレクトリ構造

    • コンポーネント用のディレクトリ直下にContainerコンポーネントを置く。

    • 同列階層にPresentation用のコンポーネントを入れるサブディレクトリを置く。

    • Presentation用ディレクトリにPresentationコンポーネントのテスト、Storyを置く。

    • コンポーネント固有のコンポーネントがある場合は更にサブディレクトリを切ってそこに固有のコンポーネント(Presentation)を置く。

  2. react-hook-form の hooks を使ったフォーム簿挙動の安定化

    • 共通パーツ(inputやbutton)は useController や useFormState でラップしたものを用意しておく。これによって親のフォームの状態や、特定の入力フィールドの情報を取得することが可能。

    • これらをやっておくとフォーム全体の挙動を統一できる。

    • VueのVeeValidate でも同じことができそう?

  3. フォームオブジェクトのスキーマ定義とバリデーションを zod で一括管理

    • VeeValidate v4 と yup でも同じことができる

  4. Storybook を活用したコンポーネントテストの拡充

    • Storybookのストーリーファイルをテストで再利用できるのを知らんかった

    • sotrybook/testing-react っていうライブラリの composeStories をimportすれば使えそう

@nagasawaaaa
札幌でフロントエンドエンジニアをやっている二児の父です。 日報とかを雑に書いていきます。