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

カートのデータレイヤーに秩序をもたらすステートマシン「XState」について/about-xstate-a-state-machine-that-controls-the-carts-data-layer

rry
December 17, 2021

 カートのデータレイヤーに秩序をもたらすステートマシン「XState」について/about-xstate-a-state-machine-that-controls-the-carts-data-layer

https://base.connpass.com/event/231346/

こちらのイベントの LT 資料です。

Next.js を使ったカート大規模リプレイスでは、カートの状態管理に XState というライブラリを利用しています。

複雑な状態遷移に対応するためのフロントエンドステートマシン XState について、実際に使ってみてどうだったかなどをご紹介しています。

簡易カートのコード: https://github.com/ryamakuchi/xstate-cart-demo
簡易カートのデモサイト: https://xstate-cart-demo.vercel.app/

rry

December 17, 2021
Tweet

More Decks by rry

Other Decks in Programming

Transcript

  1. © 2012-2021 BASE, Inc. 2 自己紹介 rry(りりぃ) • 2021年4月に BASE

    株式会社へ入社 ◦ 2021年4月〜9月くらいまで今回の PJ を担当 • アクセシビリティやっていきをしています ◦ https://devblog.thebase.in/entry/2021/12/ 06/111517 • メンターは先ほど発表してくれた Ayako さん • アイコンはスイカじゃなくてコザクラインコ :@ryamakuchi
  2. © 2012-2021 BASE, Inc. 4 https://xstate.js.org/docs/ https://github.com/statelyai/xstate XState とは? JS

    / TS で使えるステートマシン (State Machine・State Chart)のライブラリ
  3. © 2012-2021 BASE, Inc. 9 ← すごく簡略化したカートの状態遷移図 ・initializing(初期状態) ・empty(カートが空) ・checkout(カートの編集・確認・更新)

     ・editing(編集中)  ・finalizing(注文完了後の後始末中)  ・completed(注文および後始末完了) ・orderCompleted(注文完了を表示) 簡略化してもこれだけある 状態の遷移
  4. © 2012-2021 BASE, Inc. 14 状態の遷移のコード const cartMachine = createMachine<CartMachineContext,

    CartMachineEvent>( { ... states: { initializing: { on: { LOAD_CHECKOUT: { target: 'checkout', actions: ['loadCheckout'], cond: 'checkoutValid', }, }, }, checkout: { initial: 'editing', states: { editing: { on: { MUTATE_CHECKOUT: [ { actions: ['mutateCheckout'], }, ], ... ・initializing という状態から、LOAD_CHECKOUT という  Action が発火して checkout に遷移 ・checkout の初期状態は checkout.editing なので  checkout.editing に遷移 … といったように、宣言的に状態遷移を書くことができる。 https://xstate.js.org/docs/guides/states.html
  5. © 2012-2021 BASE, Inc. 15 状態と密接に結びつくデータのコード export const loadCheckout =

    assign<CartMachineContext, CartMachineEvent>( (context, event) => { if (event.type !== 'LOAD_CHECKOUT') return context; const { customer, cart, checkout } = event; return { ...context, customer, cart, checkout, }; } ); ・データ(Context)を更新したい場合は、Assign Action   を利用する ・例えば loadCheckout 関数では、event の payload で  渡ってきたデータを return して Context を更新している https://xstate.js.org/docs/guides/context.html#assi gn-action
  6. © 2012-2021 BASE, Inc. 16 状態の遷移に伴う副作用のコード finalizing: { invoke: {

    id: 'finalizeCheckout', src: 'finalizeCheckout', onDone: { target: 'xxx', }, onError: 'yyy' }, }, … services: { finalizeCheckout: async () => { // 後始末的なコード }, }, ・Action を使うと同期的な副作用を及ぼすことが  できるが、非同期的な副作用を及ぼしたいときは  Invokeing Services を利用することができる ・例えば Invoking Promises を利用すると、services で  定義したコードが resolve したら onDone が発火、  reject したら onError が発火する https://xstate.js.org/docs/guides/communication.html
  7. © 2012-2021 BASE, Inc. 19 ・複雑な状態遷移に対応するために、  State Nodes を利用して細かく状態遷移を  定義することができる

    ・簡易カートの例では checkout State が  editing, finalizing, completed という  State Nodes を持っている https://xstate.js.org/docs/guides/state nodes.html State Nodes 状態(State)はさらに下位の状態(State Nodes)を持つことができる checkout: { initial: 'editing', states: { editing: { on: { FINALIZE_CHECKOUT: 'finalizing', }, }, finalizing: { invoke: { id: 'finalizeCheckout', onDone: 'completed' onError: 'completed', }, }, }, },
  8. © 2012-2021 BASE, Inc. 20 Invoking Services ・「状態の遷移に伴う副作用のコード」でも  紹介したが、Invoking Services

    が便利 ・簡易カートの例では後始末的なコードとして  適当なコードを書いているが、実際のカート  では主に作成・更新系の API を叩く処理  などでたくさん使っている https://xstate.js.org/docs/guides/comm unication.html 状態遷移時に非同期処理などを実行できる finalizing: { invoke: { id: 'finalizeCheckout', src: 'finalizeCheckout', onDone: { target: 'completed', }, onError: 'completed' }, }, … services: { finalizeCheckout: async () => { // 後始末的なコード }, },
  9. © 2012-2021 BASE, Inc. 21 ・A の場合は A Action を実行したいが、

     B の場合は B Action を実行したい、  みたいなときに便利 ・他にも簡易カートの例のように、  バリデーションが通ったら次の状態に  遷移したい、というような使い方もできる https://xstate.js.org/docs/guides/guard s.html#guards-condition-functions Guards(Condition Functions) initializing: { meta: { label: 'cart に関する処理を開始', }, on: { LOAD_CHECKOUT: { target: 'checkout', actions: ['loadCheckout'], cond: 'checkoutValid', }, }, }, … export const checkoutValid = (_: CartMachineContext, event?: CartMachineEvent) => { if (event?.type === 'LOAD_CHECKOUT') { return ( isCheckout(event.checkout) && isCart(event.cart) && isCustomer(event.customer) ); } return false; }; cond プロパティを使うことで条件による状態遷移ができる