注意 Vuex 状态变化!

2025-05-28

注意 Vuex 状态变化!

这是我在 Dev.to 上的第一篇文章,所以非常期待任何反馈,希望它们能帮助我提升整体写作水平,并帮助我补充一些我可能忘记写和解释的内容!第一段写完了,让我们开始 Vue 吧!


今天有人在 Slack 上提问,关于如何在 Vue 组件中处理不同的状态。他想要的是这样的:你发起一个请求,它有 3 个基本状态(待处理/正在加载、成功、失败/错误)。如何在 Vue 组件中处理这个问题?他问可以用 Vuex 来实现(他自己也在用 Vuex),但我先不提这个问题,因为没必要用 Vuex 来实现(不过我也会探索 Vuex 的世界)。

首先,我们有 3 个状态,并且需要针对每个状态做出不同的行为。下面的代码片段展示了一种实现方法:

<template>
  <h1 v-if="status === 'success'">Success</h1>
  <h1 v-else-if="status === 'error'">Error</h1>
  <h1 v-else>Loading</h1>
</template>
Enter fullscreen mode Exit fullscreen mode

它基本上根据状态显示不同的消息,这是所需的行为。

让我们首先假设它是一个单一组件,并且请求的数据不会在其他任何地方(父组件或兄弟组件)需要,这使得方法变得简单(我稍后将探讨其他方法)。

我假设你对 Vue.js 有点熟悉,这意味着你了解createdmethodsdata。现在让我们为该特定组件实现所需的行为(api.get模拟延迟 1 秒的 API 请求,以便我们可以看到状态的转换)。

import api from '@/api';

export default {
  name: 'simple',
  data() {
    return {
      status: 'pending',
    };
  },
  created() {
    console.log(`CREATED called, status: ${this.status}`);

    this.handleCreated();
  },
  methods: {
    async handleCreated() {
      try {
        await api.get();

        this.status = 'success';
      } catch (e) {
        console.error(e);

        this.status = 'error';
      }
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

这里没什么大不了的,因为所有操作都在组件内部处理,而提问者的情况并非如此。我想他的情况可能有点不同。在他的例子中,状态需要在其他组件之间共享,而不仅仅是这个组件的子组件。在这种情况下,我们可能会有一个共享状态,这时 Vuex 就派上用场了(你可以用 Vuex 来实现同样的效果Event Bus,而且它比仅仅为这个状态添加 Vuex 更好)。

现在让我们更新组件,使用来自 Vuex Store 的状态,而不是本地值。首先,我们创建状态

export default new Vuex.Store({
  state: {
    status: 'pending',
  },
  mutations: {

  },
  actions: {

  },
});
Enter fullscreen mode Exit fullscreen mode

现在让我们更新我们的组件以使用state.status

<template>
  <h1 v-if="status === 'success'">Success</h1>
  <h1 v-else-if="status === 'error'">Error</h1>
  <h1 v-else>Loading</h1>
</template>

<script>
import { mapState } from 'vuex';

export default {
  name: 'vuex1',
  computed: mapState(['status']),
};
</script>
Enter fullscreen mode Exit fullscreen mode

下一步是在调用 API 后更新状态。我们可以像之前一样实现,只需引用Vuex Store 中的状态即可,但这是非常糟糕的做法。现在正确的做法是派发一个 Vuex Action 来处理,因此我们首先创建 Action 来处理它:

export default new Vuex.Store({
  state: {
    status: 'pending',
  },
  getters: {
    status: state => state.status,
  },
  mutations: {
    updateStatus(state, status) {
      Vue.set(state, 'status', status);
    },
  },
  actions: {
    async fetchApi({ commit }) {
      try {
        await api.get();

        commit('updateStatus', 'success');
      } catch (e) {
        console.error(e);

        commit('updateStatus', 'error');
      }
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

一旦我们假设状态在其他组件之间共享,并且我们不希望每个组件反复地调度相同的 Action,那么从组件中调度 Action 就毫无意义了。因此,我们会在我们的App.vue文件或任何其他对您的应用有意义的组件中(例如在视图的主组件中)调度 Action。以下是调度已创建 Action 的文件代码片段App.vue

<template>
  <div>
    <simple />
    <vuex1 />
  </div>
</template>

<script>
import Simple from '@/components/Simple.vue';
import Vuex1 from '@/components/Vuex1.vue';

export default {
  name: 'app',
  components: {
    Simple,
    Vuex1,
  },
  created() {
    this.$store.dispatch('fetchApi');
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

太棒了,现在它按预期工作了!但我什么都没告诉你。他试图解决的问题比这个更深层次。他希望某些受此状态更新的组件在状态发生变化时能够表现不同。想象一下,你可能希望在这个 API 调用成功后为每个组件调度不同的操作,那么,在只调度页面中渲染的组件的操作的情况下,该如何实现这一点呢?

我的目的是向你展示几种处理这种情况的可能性。我事先声明,对于我们大多数人来说,这种情况可能听起来很尴尬,但请尝试抽象一下我向你展示的场景,并专注于你能从我这里展示的功能中实现什么(你可能会遇到完全不同的场景,这个解决方案比这里更合适)。

手表

实现我们期望解决方案的最简单方法。您可以监视属性更新并按您想要的方式处理它。在下面的示例中,我们需要更新一个“复杂”的对象,否则我们的组件将崩溃:

<template>
  <h1 v-if="status === 'success'">Success {{ complex.deep }}</h1>
  <h1 v-else-if="status === 'error'">Error</h1>
  <h1 v-else>Loading</h1>
</template>

<script>
import { mapState } from 'vuex';

export default {
  name: 'vuex2',
  data() {
    return {
      complex: null,
    };
  },
  computed: mapState(['status']),
  watch: {
    status(newValue, oldValue) {
      console.log(`Updating from ${oldValue} to ${newValue}`);

      // Do whatever makes sense now
      if (newValue === 'success') {
        this.complex = {
          deep: 'some deep object',
        };
      }
    },
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

Vuex 手表

你知道也可以使用 Vuex 来监听变化吗?这里是文档。唯一的要求是,它监听一个函数,该函数接收 State 作为第一个参数,Getters 作为第二个参数,并返回另一个函数,该函数将监听其结果。

使用 Vuex watch 时需要注意一点:它会返回一个unwatch函数,如果你想停止 watcher,则应该在你的 hook 中调用该函数beforeDestroy。如果你不调用这个函数,watcher 仍然会被调用,这并非我们期望的行为。

这里要记住的一件事是,反应性发生在监视回调被调用之前,这意味着我们的组件将在设置复杂对象之前更新,所以我们需要注意这里:

<template>
  <h1 v-if="status === 'success'">Success {{ complex && complex.deep }}</h1>
  <h1 v-else-if="status === 'error'">Error</h1>
  <h1 v-else>Loading</h1>
</template>

<script>
import { mapState } from 'vuex';

export default {
  name: 'vuex3',
  data() {
    return {
      complex: null,
    };
  },
  computed: mapState(['status']),
  created() {
    this.unwatch = this.$store.watch(
      (state, getters) => getters.status,
      (newValue, oldValue) => {
        console.log(`Updating from ${oldValue} to ${newValue}`);

        // Do whatever makes sense now
        if (newValue === 'success') {
          this.complex = {
            deep: 'some deep object',
          };
        }
      },
    );
  },
  beforeDestroy() {
    this.unwatch();
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

Vuex 订阅

您可以订阅突变,这意味着每当突变提交时,您的处理程序都会被调用(您可以使用subscribeAction对操作执行相同的操作)。这有点棘手,因为我们不会只订阅特定的突变,所以我们必须在这里小心。

使用 Vuex subscribe 时需要注意一点:它会返回一个unsubscribe函数,如果你想停止订阅者,则应该在你的 hook 中调用该函数beforeDestroy。如果你不调用这个函数,订阅者仍然会被调用,这并非我们期望的行为。

这里的缺点是我们丢失了旧值,但由于第一种情况,它在响应式发生之前就被调用了,因此我们避免了重复检查(如果有问题的话)。结果如下代码片段所示:

<template>
  <h1 v-if="status === 'success'">Success {{ complex.deep }}</h1>
  <h1 v-else-if="status === 'error'">Error</h1>
  <h1 v-else>Loading</h1>
</template>

<script>
import { mapState } from 'vuex';

export default {
  name: 'vuex4',
  data() {
    return {
      complex: null,
    };
  },
  computed: mapState(['status']),
  created() {
    this.unsubscribe = this.$store.subscribe((mutation, state) => {
      if (mutation.type === 'updateStatus') {
        console.log(`Updating to ${state.status}`);

        // Do whatever makes sense now
        if (state.status === 'success') {
          this.complex = {
            deep: 'some deep object',
          };
        }
      }
    });
  },
  beforeDestroy() {
    this.unsubscribe();
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

结论

正如我之前提到的,我的想法不仅仅是解决 Slack 那个家伙提出的问题。我想分享一个更广泛的视角,看看有哪些可用的解决方案以及如何使用它们。

你可能遇到不同的问题,这些解决方案可能很合适,但正如我在本文中提到的:保持简单!我一开始就针对一个特定问题提供了一个非常简单的解决方案,你也应该这样做。等到性能问题或重构出现后,再考虑复杂的解决方案。

如果你愿意,你也可以在 Github 上查看它:vue-listen-to-change-example

更新

文章来源:https://dev.to/viniciuskneves/watch-for-vuex-state-changes-2mgj
PREV
2025 年 15+ 个最佳图标库
NEXT
Next.js 垃圾课程 - 第 1/3 部分 Next.js