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
状態と共に暮らす:ステートフルへの挑戦
Search
ypresto
April 23, 2025
Programming
1
290
状態と共に暮らす:ステートフルへの挑戦
ypresto
April 23, 2025
Tweet
Share
More Decks by ypresto
See All by ypresto
Next.jsとNuxtが混在? iframeでなんとかする!
ypresto
3
3.3k
Cancel Next.js Page Navigation: Full Throttle
ypresto
1
2.2k
Next.js のページ遷移を全力で止める
ypresto
15
7.5k
TypeScriptの型とパフォーマンス (TSKaigi 2024)
ypresto
21
7.6k
アクセシビリティとE2Eテスト
ypresto
0
97
VS Codeのプロセスモデルとデバッグ方法 - パフォーマンスと安定性を支えるアーキテクチャ
ypresto
1
460
TypeScriptの型定義をPRする技術
ypresto
1
740
Other Decks in Programming
See All in Programming
PHPバージョンアップから始めるOSSコントリビュート / how2oss-contribute
dmnlk
1
1k
リアクティブシステムの変遷から理解するalien-signals / Learning alien-signals from the evolution of reactive systems
yamanoku
3
1.2k
リアルタイムレイトレーシング + ニューラルレンダリング簡単紹介 / Real-Time Ray Tracing & Neural Rendering: A Quick Introduction (2025)
shocker_0x15
1
300
地域ITコミュニティの活性化とAWSに移行してみた話
yuukis
0
240
List とは何か? / PHPerKaigi 2025
meihei3
0
860
DomainException と Result 型で作る型安全なエラーハンドリング
karszawa
0
910
ウォンテッドリーの「ココロオドル」モバイル開発 / Wantedly's "kokoro odoru" mobile development
kubode
1
110
On-the-fly Suggestions of Rewriting Method Deprecations
ohbarye
1
1.7k
AWS で実現する安全な AI エージェントの作り方 〜 Bedrock Engineer の実装例を添えて 〜 / how-to-build-secure-ai-agents
gawa
8
800
Chrome Extension Techniques from Hell
moznion
1
160
Qiita Bash
mercury_dev0517
2
200
Agentic Applications with Symfony
el_stoffel
2
290
Featured
See All Featured
StorybookのUI Testing Handbookを読んだ
zakiyama
29
5.6k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
Java REST API Framework Comparison - PWX 2021
mraible
30
8.5k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
45
7.2k
Testing 201, or: Great Expectations
jmmastey
42
7.5k
Into the Great Unknown - MozCon
thekraken
37
1.7k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
233
17k
BBQ
matthewcrist
88
9.6k
The Pragmatic Product Professional
lauravandoore
33
6.5k
What's in a price? How to price your products and services
michaelherold
245
12k
RailsConf 2023
tenderlove
30
1.1k
Building a Scalable Design System with Sketch
lauravandoore
462
33k
Transcript
© LayerX Inc. 状態と共に暮らす:ステートフルへの挑戦 ypresto @ LayerX Frontend Night -
Exploring State (2025/04/20)
© LayerX Inc. 2 ⾃⼰紹介 • LayerX バクラク事業部 ◦ 債務管理チーム
(請求書受取) ◦ ソフトウェアエンジニア ◦ 経理の⽅が利⽤するプロダクトの開発 • 趣味は主に写真とスプラ、⼦どもとおでかけ ypresto (プレスト)
© LayerX Inc. 3 フロントエンド=ステートフル フロントエンドとはステートフル、だからこそ難しい バックエンド ステートレスに設計し、状態は外部に任せる フロントエンド DOM、フォーム、非同期処理など、ステートフルが本体
© LayerX Inc. 4 フロントエンド=ステートフル • ロジックと状態が容易に結合、分解が困難 • 順番‧タイミング問題:setTimeout() •
単体テストが困難、結合テストの肥⼤化 • コンポーネント内がガチャガチャする...! useEffect()祭り!!! 複雑!!! 状態が中⼼に来ることで‧‧
None
© LayerX Inc. 6 状態という複雑さと共に暮らす • すべてのアプリケーションには、 それ以上減らせない複雑性がある • 本質的な複雑さは減らせず、移動しかできない
• 例:UIを簡単にすると、システムのロジックが複雑になる テスラーの複雑性保存則 https://www.nomodes.com/larry-tesler-consulting/complexity-law https://scrapbox.io/koushisa/複雑性保存の法則
Apollo 11 (1969) Crew Dragon Endeavour (2020) 参考: https://blog.btrax.com/jp/space-x-touchscreen https://airandspace.si.edu/collection-media/NASM-NASM2013-02663
https://www.youtube.com/live/H-l6f4wcv2I?feature=shared&t=21153
© LayerX Inc. 8 状態という複雑さと共に暮らす • ユーザー=システム要件を減らす? • システムの中で、 •
本質的でない複雑さが少なくなる場所 • 品質担保が容易な場所 • に移動する! 本質的な複雑さを「どこに移動するか」が⼤事
© LayerX Inc. 9 状態という複雑さと共に暮らす • 状態に対する本質的な操作は2種類! • 状態という複雑さから引き剥がす ステートフルの複雑性
状態 計算された 状態 (値) 変換ロジック イベントハンドラ リアクティブな接続 ⼿続き的な呼出‧変更 外界 (DOMなど) 状態の 参照‧変更 1. 状態から別の状態を計算 2. イベントに応じて 状態を変更
1. 状態から別の状態を計算する
© LayerX Inc. 11 リアクティブとDerived State 状態から状態を計算:Derived State
12 © LayerX Inc. リアクティブとDerived State:本質的でない複雑さの例 状態が変わるイベントハンドラで、 漏れずに呼ばないといけない
© LayerX Inc. 13 リアクティブとDerived State 実際の業務システムで同じこと⾔われたら‧‧
© LayerX Inc. 14 リアクティブとDerived State • イベントハンドラの網羅性やタイミング =本質的でない複雑さを意識しなくて済む •
複雑な関係性は、純粋関数に移動してテストする • 外界の複雑さは、(引数扱いの) hooksに移動する • 状態の組みたてができるのがリアクティブ リアクティブ:状態間の関係を普通のコードで表現 参考: https://zenn.dev/layerx/articles/22dd45dc69a57c
© LayerX Inc. 15 リアクティブとDerived State • イベントハンドラを使わずに、 複数の状態を組み⽴てられるのがリアクティブ •
情報の加⼯ (例:⽂字列→⽂字数、⾦額→税額) • 外界の状態 (例:URL、LocalStorage、⾮同期処理、...) • 外界やロジックという複雑さを、hooksや純粋関数に移動 外界→加⼯→表⽰ を Derived State で
© LayerX Inc. 16 リアクティブとDerived State • 「状態間の関係」の表現に、余計なイベントハンドラが挟まる ◦ 更新漏れやタイミングの⼼配、テスタビリティ...
• よって、useState() を不必要に使⽤しない ◦ または (状態への参照が制限された) hooksに追い出す • 余談:フォームのリセットは、Dialogのライフサイクルに委ねたい useEffect() + 常にsetState() は、99%アンチパターン 状態1 状態2 状態1 useEffect(...) 別の状態変更 状態変更
Reactivity = Derived State with Confidence. リアクティブで 状態間の関係を簡潔‧安全に定義する
© LayerX Inc. 18 状態という複雑さと共に暮らす • 状態に対する本質的な操作は2種類! • 状態という複雑さから引き剥がす ステートフルの複雑性
状態 計算された 状態 (値) 変換ロジック イベントハンドラ リアクティブな接続 ⼿続き的な呼出‧変更 外界 (DOMなど) 状態の 参照‧変更 1. 状態から別の状態を計算 2. イベントに応じて 状態を変更
2. イベントに応じて状態を変更する
onChange={e => setState(e.target.value)}
制作‧著作:LayerX 終
© LayerX Inc. 22 Event const onChangeUserId = (userId) =>
{ setUserId(userId) setGroupId(users.find(u => u.id = userId)?.groups[0]?.id) # グループを初期値に設定する // 他のフィールドたちの書き換えもここに } 「便利」を実現するほど、複雑化するイベントハンドラ テスタビリティを諦めない..!
© LayerX Inc. 23 • みんな⼤好き (?) Reducer • nextState
= f(state, action) const reducer = action => { switch (action.payload.type) { case "setUser": return { userId: action.payload.userId, groupId: null } ... } • 「状態の変更」に純粋関数を強制するパターン ◦ 複数の (計算されない) 状態を同時に変えたい‧複雑なときに使いましょう イベントハンドラを純粋関数で表現する
24 © LayerX Inc. わたしたちのアプリケーションにて。 「旅費交通費」に合わせて 税区分を⾃動設定したい! 「旅費交通費」の デフォルトの税区分設定 外貨のときの
「不課税」税区分設定 適格の経過措置税区分の 対応関係設定 借⽅の勘定科⽬ 請求書の通貨 適格請求書? 借⽅の税区分 get get get set set get しかも税区分変わったら、 ほかも変えたい!!
Death. ⼿続き的な 状態操作祭り
© LayerX Inc. 26 Event • フォームへの「変更内容」を計算して返す純粋関数 ◦ transform() •
現在の状態や、1つまえのtransform()が返したdiffを引数で受ける ◦ apply(transform(transform(transform({ 借⽅勘定科⽬: 新しい値 }))) • 設定は、transformを作るときに引数として受ける • 分解でき、テストでき、いくつでも組み合わせられる • ASTの変換 (transformer) や、Goのhttp Middewareから着想 • reducerのような既存パターンに囚われず、アプリケーションの特性にあった設計を! ⽬的は純粋関数で表現することです! どう解決したか
Design Functional parts for your application. アプリケーションにあわせて、 ⼩さな純粋関数を組み合わせられるような パターンを発明する
© LayerX Inc. 28 • リアクティブを活⽤し、状態間の関係を意識して、分解して組み⽴てる • アプリケーションにあった状態変更ロジックの設計をし、⼿続き的な変更操作を減らす • 純粋関数に切り出せば、「複雑な部分」がテスタブルになる
• 余分な複雑さを減らし、複雑さを移動して、状態と上⼿に付き合っていきましょう..! まとめ
© LayerX Inc. 29 フロントエンド=ステートフル • 純粋関数で書かれた部分はテストしやすい https://www.destroyallsoftware.com/talks/boundaries • 外界に結合しない部分をぶ厚く中⼼に。そうでない殻の部分を薄く外に。
Functional Core, Imperative Shell
© LayerX Inc. 30 Event イベント:勘定科⽬を「旅費交通費」に変更 if (デフォルトの税区分 (状態の参照) →
勘定科⽬のデフォルトの税区分 (状態の参照) → 税区分に対応する「経過措置」税区分 (setState()) →他にも わたしたちのアプリケーションにて。 通貨 旅費交通費の デフォルトの税区分設定 税区分を⾃動で設定したい 外貨のときの 「不課税」税区分設定 経過措置税区分の 対応関係設定 税区分 勘定科⽬
© LayerX Inc. 31 Event 勘定科⽬の変更 (setState()) → 外貨時のデフォルトの税区分 →
勘定科⽬のデフォルトの税区分 (状態の参照) → 税区分に対応する「経過措置」税区分 (setState()) わたしたちのアプリケーションにて。 「旅費交通費」の デフォルトの税区分設定 税区分を⾃動で設定したい 外貨のときの 「不課税」税区分設定 経過措置税区分の 対応関係設定 税区分 借⽅の勘定科⽬ 請求書の通貨
Integration Test is not a silver bullet. テスタビリティを諦めない
© LayerX Inc. 33 • 表⽰はリアクティブの組み合わせで ◦ ⼦コンポーネントの表⽰に必要なロジックは⼦側で呼び出す ◦ コンボボックスなど、テンポラリな状態は⼦側で制御
• 更新は純粋関数として ◦ 状態と更新は親で⼀括管理する • TODO: 表にする 指針:表⽰と変更を分けて考える
© LayerX Inc. 34 Testing Pyramid モジュール (コンポーネント) 間の接続 の正当性
ロジックの正当性
© LayerX Inc. 35 Event useQuery()のenabledを使わない、なぜならそれはイベントハンドラ内でのみで必要な情報を露出させて いるため NOTE: イベントハンドラを無理にReactiveにしない
© LayerX Inc. 36 TODO 値の初期化の際のみ コンボボックス1が切り替わったので、コンボボックス2をクリア‧初期値にしたい 単純には const setComboBox1Value
= (value) => { setRawComboBox1Value(value); if (...) { setComboBox2Value(null) } 条件が増えるとhandler祭りになる → useEffectのほうがマシかもしれない いや、useReducer()のほうがマシかも ×:モーダルの開閉時のリセット 状態を持ったコンポーネントのライフサイクル (unmount) で状態を消去 useEffect(() => { if (...) { setState() } }) はuse sparingly
© LayerX Inc. 37 • Derived State‧リアクティブを活⽤して、状態を減らす ← useEffect()を使わない話に集中するか ‧‧?
• 状態の変更操作を純粋関数で表現する ステートフルをうまく扱う
© LayerX Inc. 38 「ステートフル」を分解 • 状態 • 計算された状態 (Derived
State) • イベントハンドラによる状態変更 ステートフルの登場⼈物 状態 計算された 状態 ロジック イベントハンドラ リアクティブな接続 ⼿続き的な呼出‧変更 外界 (DOMなど) 状態変更 2. Reducer, and ...