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. 1
    カートのデータレイヤーに
    秩序をもたらすステートマシン
    「XState」について
    BASE Tech Talk #1 〜Next.jsを使ったカート大規模リプレイスPJの裏側〜
    @rry

    View full-size slide

  2. © 2012-2021 BASE, Inc. 2
    自己紹介
    rry(りりぃ)
    ● 2021年4月に BASE 株式会社へ入社
    ○ 2021年4月〜9月くらいまで今回の PJ を担当
    ● アクセシビリティやっていきをしています
    ○ https://devblog.thebase.in/entry/2021/12/
    06/111517
    ● メンターは先ほど発表してくれた Ayako さん
    ● アイコンはスイカじゃなくてコザクラインコ
    :@ryamakuchi

    View full-size slide

  3. © 2012-2021 BASE, Inc. 3
    XState とは?

    View full-size slide

  4. © 2012-2021 BASE, Inc. 4
    https://xstate.js.org/docs/
    https://github.com/statelyai/xstate
    XState とは?
    JS / TS で使えるステートマシン
    (State Machine・State Chart)のライブラリ

    View full-size slide

  5. © 2012-2021 BASE, Inc. 5
    XState とは?
    状態遷移図を見ることができる

    View full-size slide

  6. © 2012-2021 BASE, Inc. 6
    XState とは?
    React 専用というわけではなく
    Vue や Svelte などからも利用できる

    View full-size slide

  7. © 2012-2021 BASE, Inc. 7
    カートの状態遷移

    View full-size slide

  8. © 2012-2021 BASE, Inc. 8

    View full-size slide

  9. © 2012-2021 BASE, Inc. 9
    ← すごく簡略化したカートの状態遷移図
    ・initializing(初期状態)
    ・empty(カートが空)
    ・checkout(カートの編集・確認・更新)
     ・editing(編集中)
     ・finalizing(注文完了後の後始末中)
     ・completed(注文および後始末完了)
    ・orderCompleted(注文完了を表示)
    簡略化してもこれだけある
    状態の遷移

    View full-size slide

  10. © 2012-2021 BASE, Inc. 10
    状態と密接に結びつくデータ
    それぞれの状態に密接に結びついたデータ(Context)を保持している
    https://xstate.js.org/docs/guides/context.html
    Context は状態遷移するときのイベントで変化したりする

    View full-size slide

  11. © 2012-2021 BASE, Inc. 11
    状態の遷移に伴う副作用
    状態の遷移に伴う副作用を宣言的に記述することができる
    https://xstate.js.org/docs/guides/effects.html
    同期的な副作用も、非同期的な副作用もどちらも扱える

    View full-size slide

  12. © 2012-2021 BASE, Inc. 12
    XState を利用すると

    View full-size slide

  13. © 2012-2021 BASE, Inc. 13
    XState を利用した簡易的なカートのデモを用意
    https://github.com/ryamakuchi/xstate-cart-demo/
    https://xstate-cart-demo.vercel.app/

    View full-size slide

  14. © 2012-2021 BASE, Inc. 14
    状態の遷移のコード
    const cartMachine = createMachine(
    {
    ...
    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

    View full-size slide

  15. © 2012-2021 BASE, Inc. 15
    状態と密接に結びつくデータのコード
    export const loadCheckout = assign(
    (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

    View full-size slide

  16. © 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

    View full-size slide

  17. © 2012-2021 BASE, Inc. 17
    実際に使ってみて

    View full-size slide

  18. © 2012-2021 BASE, Inc. 18
    実際に使ってみて
    思ったよりも多機能だった
    あれもこれもだいたいできる
    いくつか便利と思った機能をご紹介

    View full-size slide

  19. © 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',
    },
    },
    },
    },

    View full-size slide

  20. © 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 () => {
    // 後始末的なコード
    },
    },

    View full-size slide

  21. © 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 プロパティを使うことで条件による状態遷移ができる

    View full-size slide

  22. © 2012-2021 BASE, Inc. 22
    まとめ

    View full-size slide

  23. © 2012-2021 BASE, Inc. 23
    まとめ
    カートの状態とその遷移を柔軟に管理すること
    ができる
    状態に紐付くデータを管理できるため、データ
    レイヤーに秩序が生まれる
    本来やりたい開発(ユーザーへ価値を届ける実
    装)にリソースを集中できる



    View full-size slide

  24. © 2012-2021 BASE, Inc. 24
    ご静聴
    ありがとうございました!

    View full-size slide