Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
複雑なフォームを継続的に開発していくための技術選定・設計・実装 #tskaigi / #tsk...
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
izumin5210
May 23, 2025
Programming
10k
15
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
複雑なフォームを継続的に開発していくための技術選定・設計・実装 #tskaigi / #tskaigi2025
izumin5210
May 23, 2025
More Decks by izumin5210
See All by izumin5210
開発体験を左右するライブラリの API 設計 - GraphQL スキーマ構築ライブラリから考える #tskaigi
izumin5210
2
1.7k
izumin5210のプロポーザルのネタ探し #tskaigi_msup
izumin5210
2
860
AI Agent の開発と運用を支える Durable Execution #AgentsInProd
izumin5210
8
2.8k
AI Agent Tool のためのバックエンドアーキテクチャを考える #encraft
izumin5210
6
2.2k
Building AI Agents with TypeScript #TSKaigiHokuriku
izumin5210
6
1.8k
Web エンジニアが JavaScript で AI Agent を作る / JSConf JP 2025 sponsor session
izumin5210
4
3.4k
AI Coding Meetup #3 - 導入セッション / ai-coding-meetup-3
izumin5210
0
3.7k
Web フロントエンドエンジニアに開かれる AI Agent プロダクト開発 - Vercel AI SDK を観察して AI Agent と仲良くなろう! #FEC余熱NIGHT
izumin5210
3
1.2k
TypeScript を活かしてデザインシステム MCP を作る / #tskaigi_after_night
izumin5210
5
930
Other Decks in Programming
See All in Programming
肥大化するレガシーコードに立ち向かうためのインターフェース分離と依存の逆転 / JJUG CCC 2026 Spring
hirokunimaeta
0
520
New "Type" system on PicoRuby
pocke
1
780
AI 時代のソフトウェア設計の学び方
masuda220
PRO
29
12k
作って学ぶ、 JSX (TSX) ランタイムの基本
syumai
7
1.6k
ADKを使って簡単にAIエージェントを作ってみよう
k1mu21
0
250
Observability in Practice:Grafana 與 Edge Device SRE 的那些事
blueswen
0
150
Hunting Vulnerabilities in Symfony with LLMs
vinceamstoutz
0
530
Oxlintのカスタムルールの現況
syumai
6
1k
A2UI という光を覗いてみる
satohjohn
1
120
JJUG CCC 2026 Spring: JSpecify で実現する Kotlin フレンドリーな Java API 設計
ternbusty
1
160
Signal Forms: Beyond the Basics @ngBaguette 2026 in Paris
manfredsteyer
PRO
0
230
CLIであることを活かしたGitHub Copilot CLI活用術 / GitHub Copilot CLI Pro Tips & Tricks
nao_mk2
1
1.2k
Featured
See All Featured
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
140
Designing for Performance
lara
611
70k
Visualization
eitanlees
152
17k
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
430
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
250
Believing is Seeing
oripsolob
1
140
4 Signs Your Business is Dying
shpigford
187
22k
A Tale of Four Properties
chriscoyier
163
24k
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
140
[RailsConf 2023] Rails as a piece of cake
palkan
59
6.7k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.5k
Heart Work Chapter 1 - Part 1
lfama
PRO
7
36k
Transcript
複雑なフォームを継続的に開発していくための 技術選定・設計・実装 2025-05-24 TSKaigi 2025 @izumin5210
@izumin5210 © LayerX Inc. whoami LayerX バクラク事業部 (2022-09 -) Platform
Engineering 部 Enabling チーム Staff Software Engineer ISUCON14 4位 好きな関数は cva
「フォーム」って難しくないですか? 単純だったらいいけど、フィールド数が多かったり、画面が多かったり、 補完やフィールドのバリエーションが多かったり… © LayerX Inc. 3
たとえば経費精算フォーム。 裏側のビジネスルールの複雑さ・ペインを利用者からなるべく隠蔽する代わりに、実装でやることは多い。 さらに、事業会社の自社プロダクトは進化し続ける必要があるが、その一方でバグ・デグレを最小限に抑える必要もある © LayerX Inc. 4
難しい(複雑な, 大規模な, 成長する, etc.)フォームに、 設計と技術選定で立ち向かう話をします © LayerX Inc. 5
© LayerX Inc. フォームが難しく・大変になる例を見る なぜフォームが難しいのか・それに技術選定や型でどう立ち向かうか 6
最も単純な実装 © LayerX Inc. useState だけ この時点で将来的に困る気配がする フィールドが増えると useState も増える
1フィールド更新しただけで全体再描画 バリデーションどうする? これ以上複雑にならないなら全然これでもいい 7
フォームライブラリで便利に コード例は React Hook Form © LayerX Inc. 基礎的な面倒事は吸収してくれる useForm
して <input/> を register する いいかんじに再描画を減らす バリデーションの実行と制御もやってくれる フィールドが多少増えた程度なら、 これだけでも戦える ...本当に? 8
フォームライブラリで便利に? 見た目の話と振る舞いの話が混ざるのは(特に規模が大きいと)厳しい © LayerX Inc. フィールドが多少増えた程度なら、 これだけでも戦える ...本当に? UI に埋もれるバリデーション記述
見通しづらい, 対応漏れやすい, テストしづらい, ... バリデーションも重要なロジック 9
フォームライブラリ + バリデーションスキーマ コード例は React Hook Form + Zod ©
LayerX Inc. フォームが扱う値の構造と制約を 宣言的に記述できる(させる) 重要なロジックや構造を UI から 独立して扱える バリデーションのついでにフォームのモデリング を開発者に促す 10
© LayerX Inc. フォームはそもそもプログラミングとして難しくなりやすい たぶん 入力があり、それをもとに状態が構築され、同期非同期での操作が行われ、最後に出力が得られる… プログラムを複雑にする要素が多い 11
© LayerX Inc. 表面的な難しさ(UIが絡むアレコレなど)と、プロダクトがもつ本質的な複雑さ React Hook Form などのフォームライブラリは前者は回収してくれる が、後者は残る。 これをどうやって低減するか
12
© LayerX Inc. プロダクトが持つ複雑さを低減するには… まず、この中心の「フォーム(の状態) 」をどうモデリングしていくかが重要 何に対して・どういう操作が行われるか が曖昧だと、あとからどんどん大変になる Zod などのバリデーションスキーマは、この「フォーム」の「形状」に自然と目を向けさせてくれる
13
ここからフォームの例を複雑にしていき どうモデルを進化させるかみていきます © LayerX Inc. 14
もうちょっと大変にしてみる © LayerX Inc. EC サイトのカートを考える 商品と数量から合計金額が決まる 合計金額も画面に表示する 15
もうちょっと大変にしてみる © LayerX Inc. EC サイトのカートを考える 商品と数量から合計金額が決まる 合計金額も画面に表示する 合計金額に制限がある ←
New! 16
もうちょっと大変にしてみる © LayerX Inc. EC サイトのカートを考える 商品と数量から合計金額が決まる 合計金額も画面に表示する 合計金額に制限がある 小計も
← New! 17
フォームライブラリ + バリデーションスキーマの限界 © LayerX Inc. React Hook Form +
Zod でいうと、 watch や setValue が増えだしたらアラート 多少ならまだいいが、 useEffect とかと組み合わさり始めるとかなり危険信号 危険なサインの例 useEffect or watch で setValue を手続き的に呼び出すことによるバグ混入リスク イベントハンドラ内であっても、フォーム状態を無秩序に setValue することによる、 重要な書き込みロジックが無視されるリスク モデルの振る舞いをコードに反映できてないサインかも 18
フォームライブラリ + バリデーションスキーマの限界 © LayerX Inc. 参考: useEffect + setState
は危険信号 そのエフェクトは不要かも – React https://ja.react.dev/learn/you-might-not-need-an-effect 詳解:フロントエンドの状態とリアクティブ (なぜuseEffect()でsetState()がアンチパターンか) https://zenn.dev/layerx/articles/22dd45dc69a57c 19
フォームライブラリ + バリデーションスキーマの限界 © LayerX Inc. Zod でモデリングできるのはあくまで 「形状」と「制約」 だけ
それ以上の、たとえば今回のような計算・ロジックは組み込みづらい (頑張ればできるが、Zod の宣言的に書ける良さは活きづらい) 20
適切なモデルを考える © LayerX Inc. 今回の例(小計・合計)は、 「ある値から別の値を計算し、表示やバリデー ションに使いたい」というもの RHF + Zod
の都合に合わせるために、計算結果を状態に書く 単純なプログラムなら「一度状態に持つ」とい うことはしないのでは? 状態にせず値としたい 21
適切なモデルを支える技術選定例 © LayerX Inc. オブジェクト指向のようなモデルがフィットし ているなら、MobX が選択肢に上がるかも データの依存を素直に記述でき、 それを効率よく React
に伝えてくれる 実行時にデータの依存(今回だと getter が何を読んだか)を追跡 し、更新検知・再描画をしてくれる とはいえ MobX は癖があるし、OOP が React を触るエンジニア にとって扱いやすいかなど、別角度での検討事項は多い 22
コードはぱっと見でわかりやすくなった?自分が OOP に慣れてるからかもしれない React Hook Form とは独立したコードになっているのも、関心の分離・テスト観点で嬉しい © LayerX Inc.
23
ポイント① モデルは「形状」だけでなく、ロジック・振る舞いも含める © LayerX Inc. Zod だと「形状」 「制約」にとどまることが多い が、実際のプロダクトの複雑なフォームでは、 そこ以外にも重要な概念がある
「合計金額は数量と金額の掛け算」とかはシンプルだが、 現実はもっと難しいはず そういうドメイン上重要なものに名前をつけて、 重要だとわかるようにしたい 24
ポイント② フォームにおけるモデルを UI から分離する © LayerX Inc. React Hook Form
+ Zod だと厳しくなってくるようなフォームでは、 価値の高い・重要なロジックが埋まっていることも多いはず それらを UI から分離し「 (重要な)モデルである」というのを わかりやすく・見通しやすく・テストしやすくしておきたい そこをより重点的にテストする, そこはより注意してレビューする, ... 開発者の認知や行動を切り替える (とはいえ規模や複雑さがそうでもないなら UI に co-locate させるほうがメンテしやすい。要はバランス。 ) AI 向けにもモデルと UI が分離されたほうが動きやすいし、我々ももレビューしやすい…んじゃないかな? 25
ポイント③ なるべく状態ではなく、値として扱う © LayerX Inc. 状態は難しい 内部の状態の存在を知っていないと、その外から見た振る舞いを正しく知ることは難しい 状態への書き込みを「適切に(もれなく・ミスなく) 」徹底する難易度は高い 今回の例のように
readonly な「別の値から計算される値(Derived Value) 」として 扱うことができれば、扱いは非常に単純 26
MobX が常に絶対最強? もちろんそんなことはない © LayerX Inc. 27
もうちょっと大変にしてみる © LayerX Inc. EC サイトのカートを考える 商品と数量から合計金額が決まる 合計金額も画面に表示する 合計金額に制限がある ドル決済のときはレートを
API から取得 する ← New! 28
値に非同期処理が絡むなど、依存がさらに複雑なケース © LayerX Inc. MobX でも解くことはできるが、MobX は依存追跡をランタイムで実現しているため 複雑すぎる依存ではデバッグがつらかったりする この発表では詳しくは触れません。 技術にはいろんな特性があるんだなあくらいで捉えておいてもらえると。
ほかの技術だと、たとえば jotai などは複雑な依存や非同期の依存でもうまく扱える 29
jotai の簡単な例 © LayerX Inc. jotai は atom という単位で状態を管理する atom
関数は読み書き可能な状態のほか、 Read only な atom をつくることで、 他の値に依存した値(Derived Value)を 表現可能 この get(anAtom) の形でデータの依存グラフを描く 30
jotai で非同期の Derived Value を扱う例 © LayerX Inc. Read only
atom は非同期の値を返す こともできる 非同期 atom もだいたい通常の atom と同じように扱える 解説サボるために実装を簡略化しています。実際の利用方法は以下の記事を参照してください 「Jotai v2を使いこなすために実は必須級な“async sometimes”パターンの解説」 by uhyo https://zenn.dev/uhyo/articles/jotai-v2-async-sometimes 31
jotai は atom でデータの依存グラフを描く 青が状態, オレンジが Derived Value get(anAtom) がグラフのエッジ
© LayerX Inc. 32
jotai によるモデリング © LayerX Inc. MobX のときに挙げたポイントは jotai もクリアしている ポイント①
モデルは「形状」だけでなく、ロジック・振る舞いも含める ポイント② フォームにおけるモデルを UI から分離する ポイント③ なるべく状態ではなく、値として扱う やはり「ロジックを」 「UI と分離して」 「値でいいものは値として扱う」ことで 余計な複雑性を抑えたモデリングができる 33
jotai によるモデリング - 非同期処理 © LayerX Inc. 非同期処理を含む atom への依存をそこまで特別扱いせず、
おなじく atom の形で書けるのが強力 前述のレートの例のような「ユーザの入力値を API に渡し、 そのレスポンスを別の値やバリデーションに利用」は稀によくあるケース 非同期処理のために処理の流れを捻じ曲げたり、余計な中間状態を生やしたりは不要。 いつもの async function で OK。 React にマウントするときにまだ未解決なら勝手に Suspend してくれる 便利! …だけど、同じ useAtomValue が atom によって suspend したりしなかったりするのは 逆に認知負荷たかいかも…? ともちょっと思う。むずかしい。 34
jotai によるモデリング - テスト © LayerX Inc. コアは React にすら依存してないので、テストも書きやすい
API に依存してる場合はモックに差しか可能にするか、 MSW で外側からモックするか 35
「フォームのモデリング」という観点から見ると解いてる課題は近い データの依存を Derived Value として表現することで無理のないコードとなり、 余計な複雑性を下げることにつながっている ここでいう 無理のあるコード とは、 状態の更新にフックして別の状態の更新を頑張っていたコード
MobX (一見して)普通のクラスで書ける / 暗黙で依存追跡する / ... jotai 独自記法・概念がある / `get(anAtom)` の形で依存を明示する / ... © LayerX Inc. 36
MobX と jotai - どっちにするか © LayerX Inc. 依存追跡の方法が大きく違う MobX
はランタイムで暗黙的に呼び出しを記憶 jotai は get(anAtom) の呼び出しをリアクティブな(更新されうる)値の呼び出しとする get(anAtom) の記述など、独自 DSL 感のある jotai か 普通の OOP / クラスっぽくかける代わりに、良くも悪くも暗黙的な挙動が多い MobX か 37
MobX or jotai から常に選べばいい? もちろんそんなことはない © LayerX Inc. 38
問題によって適するモデル・設計は変わる モデル・設計が変われば適する技術も変わる © LayerX Inc. 今回は偶然 Derived Value / データの依存グラフ
/ データの流れ などに着目した設計が ハマる問題(プロダクト特性)だった 問題(プロダクトや機能)によって適する回答は変わりうる ウィザードっぽい複数画面からなるフォームであれば、 XState のような状態遷移に着目した技術が適するかも Undo 機能みたいなものが必要なら Redux / Zustand などが適するかも 単にフィールドが多いだけのフォームなら、 Zod で「形状」をモデリングするだけで事足りるかも プロダクトの複雑さはどこから来ているかを見極める 39
「フォーム」にこだわりすぎない © LayerX Inc. 今回の例も、フォームの問題にフォーム専用でない 状態管理ライブラリを持ち込んでいる 表面的な要素に囚われすぎず、 「いまどんな問題に向き合ってるんだろう」を一段上から考えてみる e.g. 「相関バリデーションが難しいことに困ってたんだけど、実はデータの依存関係をうまくモデリングできて
いないだけだな」 40
フォームライブラリを使わないことで失うものもある © LayerX Inc. これもトレードオフのひとつ たとえば React Hook Form であれば、
各フィールドの dirty check, バリデーション発火タイミング制御, etc. なくなったら面倒ではあるが、 「本質的な複雑さ」というよりは「シンプルに面倒」な類のものも多い なので最小限のユーティリティを実装してなんとかするという手もある 41
複数の技術・設計を組み合わせるパターンもありうる © LayerX Inc. たとえば以下の2つは両立しうる 「バリデーション対象フィールドが多いので Zod で宣言的 に記述したい」 「依存関係を持つフィールドが多いので
jotai を使いたい」 Zod のバリデーション結果も Derived Value にしてしまえ ばいい jotai と Zod を組み合わせると、 superRefine などは不要になるはず 42
最初から100点の答えを見つけに行く必要はない © LayerX Inc. 特に PMF 前のプロダクトや機能のプロトタイプフェーズなどでは、 あるべきの形が見えてないことも多い そんな状態で未来を先読みするのは難しいし打率も低い なので、そこそこいい感じになるところから始めよう
やはりフォームライブラリ(e.g. React Hook Form)とバリデーションスキーマ(e.g. Zod)の 組み合わせから入るのはコスパがいい まあこの2つで事足りるケースのほうが多いだろうし… 43
設計・選定の先 - 継続的に開発していくために © LayerX Inc. 44
「変更影響を受けた(壊れた)こと」に早く気付けるコードを書く © LayerX Inc. 気づくスピード: 型 > Lint > Unit
Test > ... > エンドユーザ 特に規模が大きい/複雑/パターン数が多い部品では なるべく型やテストが最初に壊れるようにしておく 現状の仕様をモデル+テストで表現できているのであれば 型で将来的な拡張への耐性を上げることが有効 45
早く壊れる型の例. などなど… © LayerX Inc. enum, union で取りうる値の幅が増えてハンドル漏れが起きるケース foo satisfies
never のような Exhaustiveness checking を常に書く 渡せるパラメタが増えたが、呼び出し側でその対応が漏れるケース アプリケーションコードでは optional は使わない(不要なら null などを明示的に渡させる) 変更が伝播し、意図せず Atom の型(関数の返り値)が変わって壊れる 関数の返り値型は推論に頼らず明示する 46
限界を迎えるサインには気をつける © LayerX Inc. 設計が限界を迎える(プロダクトの複雑さを受け入れきれなくなる)と、 機能開発に時間かかったりバグが増えたりする どれだけ丁寧に進めても、プロダクトの進化を読みきれず設計が耐えきれなくなること はありうる さっきの話と同じで、完璧な先読みはできない 限界を迎える兆候にはやめに気づいて手を打ち始められると
Good React Hook Form でいうと、 watch や setValue が増えだしたらアラート 47
おわり © LayerX Inc. フォームは難しい 「ユーザに変更される状態を持つ UI」の難しさと、「プロダクト由来の難しさ」の両方に直面する 問題領域に適したモデリング・適した技術選定をすることで、 後者の複雑さメンテナンス性は改善できるはず たとえばデータの依存が複雑なのであれば、Derived
Value をうまく扱える MobX や jotai は有効 最初から最適な選択は困難なので、限界を迎えるサインに早めに気付けるようにしたい 48