发布于 2026-01-06 3 阅读
0

用 XState 替换 Vuex

用 XState 替换 Vuex

过去几个月我一直在学习 XState 和状态机,我非常喜欢它们。我决定用 Vue 和 XState 开发一个应用。我想和大家分享一下,因为我看到很多关于如何将 XState 集成到 React 中的帖子、视频、教程等等,但关于如何将其集成到 Vue 中的却不多。

一开始,我结合使用 Vuex 和 XState。我深受 Phillip Parker 的这篇文章以及他创建的这个GitHub 代码库的启发。如果你还没读过这篇文章并看过代码,我强烈建议你读一读,我从中受益匪浅。

基本上,应用程序的每个功能我都对应一个 Vuex 模块和一个 XState 状态机。虽然运行良好,但我知道我并没有充分利用 XState 的潜力。

经过进一步研究,我找到了一种完全摆脱 Vuex 的方法,同时还能利用 Vue 的各项功能实现全局响应式状态,并结合强大的有限状态机,从而实现 XState 的所有特性。这种方法更接近XState 文档中展示的Vue 实现方案

我不用 Vuex,而是使用事件总线模式来管理全局状态。这意味着创建一个新的 Vue 实例,并将需要在组件间共享的任何内容传递给它。对于简单的应用,可能只需要一个实例,但大多数应用可能更适合拆分成多个模块(就像使用 Vuex 那样)。

然后,您只需将所需的 XState 机器数据传递给这个 Vue 实例即可。我编写了一个函数,该函数返回一个 Vue 实例,该实例暴露了机器的状态、上下文和send()方法,并对机器的变化做出反应。

import Vue from "vue";
import { interpret } from "xstate";

export const generateVueMachine = machine => {
    return new Vue({
        created() {
            this.service
                .onTransition(state => {
                    this.current = state;
                    this.context = state.context;
                    if (process.env.NODE_ENV === "development") {
                        console.log(`[ ${machine.id.toUpperCase()} STATE ]`, this.current.value);
                    }
                })
                .start();
        },
        data() {
            return {
                current: machine.initialState,
                context: machine.context,
                service: interpret(machine)
            };
        },
        methods: {
            send(event) {
                this.service.send(event);
            }
        }
    });
};

然后,您可以创建一个新文件,例如 fetchMachine.js,在其中创建一个 XState 状态机。您可以使用该generateVueMachine()函数并将您的状态机作为参数传递给它,该函数会返回一个您可以导出的 Vue 实例。

import { Machine, assign } from "xstate";
import { generateVueMachine } from "./generateVueMachine";

const machine = Machine({ /*...machine config */ });

export const fetchMachine = generateVueMachine(machine);

现在我可以在我的应用程序中的任何地方引用这台机器,并使用 Vue 的计算属性对其变化做出反应。

<template>
    <button @click="onFetch" v-if="!fetchState.matches('fetching')">Fetch<button>
    <p>{{ fetchContext.fetchResult }}</p>
</template>

<script>
// fsm
import { fetchMachine } from "./fsm/fetchMachine";

export default {
    computed: {
        fetchState() {
            return fetchMachine.current;
        },
        fetchContext() {
            return fetchMachine.context;
        }
    },
    methods: {
        onFetch() {
            fetchMachine.send({type: 'FETCH'});
        }
    }
};
</script>

就这样。

这是我的应用程序仓库的链接,您可以查看我如何在实际环境中应用它(状态机文件位于client/fsm)。

我非常希望得到一些反馈,告诉我哪些方面可以改进。

编辑:
我在 npm 上创建了一个 Vue 插件,以简化此设置并消除一些样板代码。您可以在https://github.com/f-elix/vue-xstate-plugin找到它。

文章来源:https://dev.to/felix/replacing-vuex-with-xstate-3097