Upgrade to Pro — share decks privately, control downloads, hide ads and more …

状態と共に暮らす:ステートフルへの挑戦

ypresto
April 23, 2025

 状態と共に暮らす:ステートフルへの挑戦

ypresto

April 23, 2025
Tweet

More Decks by ypresto

Other Decks in Programming

Transcript

  1. © LayerX Inc. 2 ⾃⼰紹介 • LayerX バクラク事業部 ◦ 債務管理チーム

    (請求書受取) ◦ ソフトウェアエンジニア ◦ 経理の⽅が利⽤するプロダクトの開発 • 趣味は主に写真とスプラ、⼦どもとおでかけ ypresto (プレスト)
  2. © LayerX Inc. 4 フロントエンド=ステートフル • ロジックと状態が容易に結合、分解が困難 • 順番‧タイミング問題:setTimeout() •

    単体テストが困難、結合テストの肥⼤化 • コンポーネント内がガチャガチャする...! useEffect()祭り!!! 複雑!!! 状態が中⼼に来ることで‧‧
  3. © LayerX Inc. 6 状態という複雑さと共に暮らす • すべてのアプリケーションには、 それ以上減らせない複雑性がある • 本質的な複雑さは減らせず、移動しかできない

    • 例:UIを簡単にすると、システムのロジックが複雑になる テスラーの複雑性保存則 https://www.nomodes.com/larry-tesler-consulting/complexity-law https://scrapbox.io/koushisa/複雑性保存の法則
  4. © LayerX Inc. 8 状態という複雑さと共に暮らす • ユーザー=システム要件を減らす? • システムの中で、 •

    本質的でない複雑さが少なくなる場所 • 品質担保が容易な場所 • に移動する! 本質的な複雑さを「どこに移動するか」が⼤事
  5. © LayerX Inc. 9 状態という複雑さと共に暮らす • 状態に対する本質的な操作は2種類! • 状態という複雑さから引き剥がす ステートフルの複雑性

    状態 計算された 状態 (値) 変換ロジック イベントハンドラ リアクティブな接続 ⼿続き的な呼出‧変更 外界 (DOMなど) 状態の 参照‧変更 1. 状態から別の状態を計算 2. イベントに応じて 状態を変更
  6. © LayerX Inc. 14 リアクティブとDerived State • イベントハンドラの網羅性やタイミング =本質的でない複雑さを意識しなくて済む •

    複雑な関係性は、純粋関数に移動してテストする • 外界の複雑さは、(引数扱いの) hooksに移動する • 状態の組みたてができるのがリアクティブ リアクティブ:状態間の関係を普通のコードで表現 参考: https://zenn.dev/layerx/articles/22dd45dc69a57c
  7. © LayerX Inc. 15 リアクティブとDerived State • イベントハンドラを使わずに、 複数の状態を組み⽴てられるのがリアクティブ •

    情報の加⼯ (例:⽂字列→⽂字数、⾦額→税額) • 外界の状態 (例:URL、LocalStorage、⾮同期処理、...) • 外界やロジックという複雑さを、hooksや純粋関数に移動 外界→加⼯→表⽰ を Derived State で
  8. © LayerX Inc. 16 リアクティブとDerived State • 「状態間の関係」の表現に、余計なイベントハンドラが挟まる ◦ 更新漏れやタイミングの⼼配、テスタビリティ...

    • よって、useState() を不必要に使⽤しない ◦ または (状態への参照が制限された) hooksに追い出す • 余談:フォームのリセットは、Dialogのライフサイクルに委ねたい useEffect() + 常にsetState() は、99%アンチパターン 状態1 状態2 状態1 useEffect(...) 別の状態変更 状態変更
  9. © LayerX Inc. 18 状態という複雑さと共に暮らす • 状態に対する本質的な操作は2種類! • 状態という複雑さから引き剥がす ステートフルの複雑性

    状態 計算された 状態 (値) 変換ロジック イベントハンドラ リアクティブな接続 ⼿続き的な呼出‧変更 外界 (DOMなど) 状態の 参照‧変更 1. 状態から別の状態を計算 2. イベントに応じて 状態を変更
  10. © LayerX Inc. 22 Event const onChangeUserId = (userId) =>

    { setUserId(userId) setGroupId(users.find(u => u.id = userId)?.groups[0]?.id) # グループを初期値に設定する // 他のフィールドたちの書き換えもここに } 「便利」を実現するほど、複雑化するイベントハンドラ テスタビリティを諦めない..!
  11. © LayerX Inc. 23 • みんな⼤好き (?) Reducer • nextState

    = f(state, action) const reducer = action => { switch (action.payload.type) { case "setUser": return { userId: action.payload.userId, groupId: null } ... } • 「状態の変更」に純粋関数を強制するパターン ◦ 複数の (計算されない) 状態を同時に変えたい‧複雑なときに使いましょう イベントハンドラを純粋関数で表現する
  12. 24 © LayerX Inc. わたしたちのアプリケーションにて。 「旅費交通費」に合わせて 税区分を⾃動設定したい! 「旅費交通費」の デフォルトの税区分設定 外貨のときの

    「不課税」税区分設定 適格の経過措置税区分の 対応関係設定 借⽅の勘定科⽬ 請求書の通貨 適格請求書? 借⽅の税区分 get get get set set get しかも税区分変わったら、 ほかも変えたい!!
  13. © LayerX Inc. 26 Event • フォームへの「変更内容」を計算して返す純粋関数 ◦ transform() •

    現在の状態や、1つまえのtransform()が返したdiffを引数で受ける ◦ apply(transform(transform(transform({ 借⽅勘定科⽬: 新しい値 }))) • 設定は、transformを作るときに引数として受ける • 分解でき、テストでき、いくつでも組み合わせられる • ASTの変換 (transformer) や、Goのhttp Middewareから着想 • reducerのような既存パターンに囚われず、アプリケーションの特性にあった設計を! ⽬的は純粋関数で表現することです! どう解決したか
  14. © LayerX Inc. 30 Event イベント:勘定科⽬を「旅費交通費」に変更 if (デフォルトの税区分 (状態の参照) →

    勘定科⽬のデフォルトの税区分 (状態の参照) → 税区分に対応する「経過措置」税区分 (setState()) →他にも わたしたちのアプリケーションにて。 通貨 旅費交通費の デフォルトの税区分設定 税区分を⾃動で設定したい 外貨のときの 「不課税」税区分設定 経過措置税区分の 対応関係設定 税区分 勘定科⽬
  15. © LayerX Inc. 31 Event 勘定科⽬の変更 (setState()) → 外貨時のデフォルトの税区分 →

    勘定科⽬のデフォルトの税区分 (状態の参照) → 税区分に対応する「経過措置」税区分 (setState()) わたしたちのアプリケーションにて。 「旅費交通費」の デフォルトの税区分設定 税区分を⾃動で設定したい 外貨のときの 「不課税」税区分設定 経過措置税区分の 対応関係設定 税区分 借⽅の勘定科⽬ 請求書の通貨
  16. © LayerX Inc. 36 TODO 値の初期化の際のみ コンボボックス1が切り替わったので、コンボボックス2をクリア‧初期値にしたい 単純には const setComboBox1Value

    = (value) => { setRawComboBox1Value(value); if (...) { setComboBox2Value(null) } 条件が増えるとhandler祭りになる → useEffectのほうがマシかもしれない いや、useReducer()のほうがマシかも ×:モーダルの開閉時のリセット 状態を持ったコンポーネントのライフサイクル (unmount) で状態を消去 useEffect(() => { if (...) { setState() } }) はuse sparingly
  17. © LayerX Inc. 38 「ステートフル」を分解 • 状態 • 計算された状態 (Derived

    State) • イベントハンドラによる状態変更 ステートフルの登場⼈物 状態 計算された 状態 ロジック イベントハンドラ リアクティブな接続 ⼿続き的な呼出‧変更 外界 (DOMなど) 状態変更 2. Reducer, and ...