XStateを使用したヤドカリくんフロントエンドの質問state管理検証
現状(v2.0.0)のフロントエンドの課題
開発で迷った際にコードを読み込む必要がある
test, lintを拡充したい
ページを動かさないとバグが分からない
ページ遷移で場合分けが多い
本来ページ遷移の状態管理はロジック単体でテストされるべき
フロントエンドのロジックとデザインを分割したい
「特技が活かせる体制でない」の原因
Reactのページ遷移の仕組み等も読み込まないとデザインに変更が入れられない
質問遷移の全貌が分からない
ヤドカリくんのページ遷移とその条件を一目、一か所で管理できる必要がある
正しい状態遷移になっているか判断できないと品質は担保できない
XStateとは?
XState: 状態遷移を管理するためのJavaScriptライブラリ
React用のフック(useMachine)やTypeScriptの型定義もあるため、Reactに導入しやすい
状態自体はReactコンポーネントとは関係ないため、ロジックとデザインを分離可能
1オブジェクト内で全状態遷移を定義できるため一覧性が高い
VSCode拡張で状態のグラフを表示することも可能
検証:XStateで一問一答形式実装
実際のヤドカリくんに導入するのはコストが大きいため、小さな一問一答サンプルアプリをXStateで作成
テーマはG系ラーメンの食券機(題材はフィーリングで決めたので重要ではありません)
https://scrapbox.io/files/69341050776af376b379d1ac.png
選んでいくと最後に注文名が出てくる
(ヤドカリくんでいうと世帯情報householdに相当)
https://scrapbox.io/files/693410d148ad5a3efb0a1005.png
XStateでヤドカリくんの要件を満たせるか検証
✅状態遷移ができる
そのためのライブラリなので当然可能
フック useMachine の戻り値 send にイベントを送信することで実現
イベントによって行うため、状態管理オブジェクト(machine)本体のメソッドを触る必要はない
send({type: "next"})
next というイベントを送れば、そのイベントによってどの状態に遷移すべきかは状態管理オブジェクト側で判断
質問を増やしてもイベントの改修不要
✅選択肢に応じて、内部のデータ(ヤドカリくんの場合は世帯情報、サンプルアプリの場合はラーメンのトッピング)を更新できる
データ更新もイベントによって行う
✅質問の分岐ができる
guard を使用することで、内部データに応じてどの状態に進むかを選択できる
麺量「ミニ」を選んだときは「ニンニク」の質問を飛ばして「ヤサイ」の質問に進む
✅戻るボタンが使える
ただしデフォルトでは対応していないので開発が必要
※ history は子状態間を移動した際に今選んでいる選択肢を記憶するものなのでここでは使えない
一問一答の文脈の履歴は未実装
内部データとして状態の履歴 histories を持たせる(現状のフロントエンドと同じ仕組み)
next イベントの際に自分の状態名を histories に追加
back イベントの際にはダミーの状態 histories へ遷移
状態 history では always でイベントを待たず即時別の状態へ遷移、遷移先は histories の末尾で判定
状態の数だけ定義が必要なため map を使って一括定義
ただし、histories の末尾要素の削除も必要なため、イベント終了時の共通処理 exit で削除
→ histories の末尾、つまり最後に訪れた状態へ戻る
(ややこしいのでQiitaでもう少し詳しくまとめる予定です)
✅戻るボタンを押した際、分岐がある質問であっても前の質問に戻れる
現状のフロントエンドと同じ仕組みのため可能
XStateで課題が解消するか検証
✅本来ページ遷移の状態管理はロジック単体でテストされるべき
(未実装だが)UIを介さずユニットテスト可能
✅フロントエンドのロジックとレイアウトを分割したい(デザインしやすくしたい)
状態同士の関係性や分岐の条件はMachineの定義内で完結
データの更新はイベントを送信するだけでよい
そのイベント送信自体コールバック関数にくるんで外から渡してしまえば、質問のコンポーネントにはロジック実装が不要になる
以下の例でも、テンプレート内にはコールバック関数やXStateの定義は入らずデザインのみを定義
✅ヤドカリくんのページ遷移とその条件を一目、一か所で管理できる必要がある
Machineの定義で一覧可能
(型合わせが上手くいかず actions にリテラルを書き煩雑になっているが、それを整理できればさらに短くなる予定)
グラフで図示することも可能(VSCode拡張)
戻るボタンがあるのであまり見やすくない...
新たな課題?
😢型合わせがややこしい
型の定義が数行に渡るほど長いので、エラーを見てもどこが間違っているか分かりづらい
かといってanyを使うと実装ミスに気づけない(次の質問にうつるとクラッシュするというv2開発中に起きたトラブルが再発する)
型合わせができず、冗長に同じ定義を繰り返している箇所もある
解決できれば対処予定
世帯員ごとに回答が入れ子になるとassignで更新できない?
assignの指定の仕方は直下の属性
questions[person][index][question]のような更新方法は取れなさそう
code:ts
actions: assign({
feedback: ({ event }) => event.feedback,
}),
→キーを統合してフラットにする必要がある?
code:ts
questions[${person}_${index}_${question}]
逆転の発想、世帯員を質問に持たせる?→採用
バックエンドへのリクエストとは入れ子の関係が逆になる(OpenFiscaは世帯員が質問(variable)を持つ)がやむなし
一問一答形式のcomponentの作り方を変える必要がある
ロジックを全く含まないテンプレート
イベントの戻り値に応じて適切な質問を流し込むラッパーコンポーネント
(逆に言うと質問ごとにコンポーネントを定義する必要はなくなる)
components/questions配下にテンプレートと共通ひな型question.tsxを移設する
ロジックを取り除く
バージョンを上げないとxstateが入らない
TypeScriptを5系にしないとtscでコンパイルエラー
nodeのバージョンを上げる必要がある
GitHub Actions側は指定可能だが、Netlify側はどうやって設定する?
課題解消(26/01/24)
型合わせ:型推論が効かないので assign を明示する必要があった
ボイラープレートを actionObj の中に隠蔽し定義を簡素化
テストの作成
1状態ごとに次の状態がどうなるかを確認
やりたいこと
例外のキャッチ:状態遷移でクラッシュした場合に例外を拾い例外用の状態へ遷移
UI上にどこでエラーが起きたかを表示できるようにする(スクショで原因特定できるよう)
xstateの機能でできる?
デッドコードの削除
xstate移行前のコード+v1のattributes系定義
importされてないものはすべて消してよい
xstateのcontextをhouseholdへ変換
変換処理をリクエスト時の1回にまとめることでロジック集約
既存のquestionのhousehold代入処理をすべて移植する必要あり
テストケース用意する
プログレスバーの実装
どうやって計算する?
分母:stateの長さ
分子:historyの長さ
で計算可能?