Vuex 是一个遵循 Flux 模式的状态管理系统。它让我们可以在我们的 Vue 3 应用程序中创建一个全局存储和单向数据流。
虽然 Vue 3 的反应性库使我们更容易创建我们自己的数据存储解决方案,但 Vuex 仍然是一个很棒的工具,因为它带有内置的 DevTools、文档和一个帮助解决问题的社区。
在本文中,我们将学习如何...
- 将 Vuex 添加到我们的 Vue 3 项目中
- 创建基本商店
- 看看如何在 Composition API 中使用 Vuex
让我们直接跳进去。
我们什么时候需要 Vuex
当您的应用程序需要组件之间复杂的共享数据时,Vuex 是一个很好的解决方案。
虽然许多数据共享选项(props、provide/inject)非常适合父/子组件,但 Vuex 将我们的数据从组件的上下文中提取到它自己的存储中 - 允许我们项目中的任何组件访问它。
此外,Vuex 带有一个内置的 Vue DevTools 扩展,让我们能够准确地看到对我们的状态进行更改的时间。
Vuex 的基础知识
Vuex 创建了一种单向数据流,允许我们以可预测的方式修改我们的数据,而无需担心保持同步。
让我们看看 Vuex 是如何融入 Vue 项目的——Vuex 系统有 3 个核心组件。
- 状态- 我们应用程序的“事实来源”
- 突变- 改变我们状态的方法(必须同步)
- 动作- 调用突变的方法(可以是异步的)
这是来自 Vuex 文档的图表,可能会有所帮助。
好的 - 让我们更深入地研究这些不同的部分,看看我们如何将它包含在我们的 Vue 应用程序中。
将 Vuex 添加到您的 Vue 3 项目中
要将 Vuex 安装到我们的项目中,让我们运行npm install vuex@next --save
然后添加到我们的应用程序中,让我们转到 main.js,创建我们的商店,并告诉我们的应用程序使用它。
main.js
javascript
import { createApp } from "vue";
import { createStore } from "vuex";
// Create a new store instance or import from module.
const store = createStore({
/* state, actions, mutations */
});
const app = createApp();
app.use(store);
app.mount("#app");
太好了 - 现在我们都准备好建立我们的商店了。
Vuex 状态
我们要做的第一件事是给我们的 Vuex 存储一个状态。并且 Vuex 使用单个状态树——这意味着单个对象作为我们应用程序的真实来源。
main.js
javascript
import { createApp } from "vue";
import { createStore } from "vuex";
const store = createStore({
state: {
count: 0,
},
});
const app = createApp();
app.use(store);
app.mount("#app");
访问 Vuex 状态 - 选项 API
然后,在我们应用程序的任何组件中,我们可以通过我们的 Vuex 存储访问我们的状态。
App.vue - 选项 API
Vue
<script>
export default {
mounted() {
console.log(this.$store.state.count); // this.$store
}
};
</script>
一旦我们可以访问我们的商店,我们就可以访问我们的状态,但是为了保持反应性,我们想使用一个计算属性来做到这一点。
App.vue - 选项 API
Vue
<template> {{ count }} </template>
<script>
export default {
computed: {
count() {
return this.$store.state.count;
},
},
};
</script>
太棒了——如果我们现在查看我们的页面,我们会看到我们的组件通过 Vuex 正确地提取了我们的价值。
使用 Vuex mapState
因此,我们必须为要在组件中使用的每个状态创建一个计算属性。这可能会变得乏味,但幸运的是,Vuex 为我们提供了一个mapState帮助程序,可以节省我们一些时间。
我们可以给它一个带有我们想要的状态属性的字符串数组,或者一个我们执行一些自定义映射的对象。
App.vue - 选项 API
Vue
<script>
import { mapState } from "vuex";
export default {
computed: mapState({
count: "count",
// OR
count: state => state.count,
// OR IF WE NEED ACCESS TO `this`
countPlusMultiplier (state) {
return state.count + this.multiple
}
}),
};
</script>
访问 Vuex 状态 - 组合 API
我们可以按照这些相同的步骤(访问我们的商店,创建计算属性)使用 Vue 3 的 Composition API 访问我们的 Vuex 状态。
在 Composition API 中,我们可以使用useStorefrom Vuex 来执行此操作。
App.vue - 组合 API
Vue
<script setup>
import { computed } from "vue";
import { useStore } from "vuex";
const store = useStore();
const count = computed(() => store.state.count);
</script>
<template> {{ count }} </template>
在组合 API 中使用 Vuex 助手
需要注意的一点是,当我们使用 Composition API(我更喜欢这样做)时,使用不同的 Vuex 映射器会有点棘手。这是一个很好的包,可以帮助解决这个问题。
我们可以使用npm install vuex-composition-helpers@next(Vue 3 的下一个)安装它,然后使用useState帮助器映射我们的状态。这会从我们的 Vuex 状态中获取我们想要的一组属性名称,并返回一个包含所有这些值作为响应式计算属性的对象。
App.vue - 组合 API
Vue
<script setup>
import { useState, useActions } from "vuex-composition-helpers/dist";
const { count } = useState(["count"]);
</script>
<template> {{ count }} </template>
好的,所以我们可以从我们的组件访问我们的状态,但是如果我们想改变它呢?
Vuex 突变
在 Vuex 中,突变是改变我们的 state 的唯一方法。把这些想象成我们用字符串注册函数处理程序的事件。
重要的是,突变必须是同步的!!! 因为当我们使用 DevTools 时,我们想要跟踪数据的前后截图。如果我们的突变是异步发生的,这是不可能的。
一个 Vuex 突变可以接受 2 个参数:
- 当前的 Vuex 状态
- 一个可选的有效载荷,我们可以使用它来将数据传递给我们的突变
main.js
javascript
const store = createStore({
state: {
count: "",
},
mutations: {
INCREMENT_COUNT(state, payload) {
state.count += payload;
},
},
});
然后,我们可以从应用程序中的任何位置触发这个突变处理程序来更新我们的状态。我们不能直接调用突变。
有两种触发突变的方法,我们可以使用两个参数(字符串,有效负载)格式或单个参数,我们在对象样式提交中定义突变类型。
增加计数的 2 种方法
javascript
// Option 1
store.commit("INCREMENT_COUNT", 5);
// Option 2
store.commit({
type: "INCREMENT_COUNT",
amount: 5,
});
这是提交突变的样子,调用 store.commit。
应用程序.vue
Vue
<template>
{{ count }}
<button @click="store.commit('INCREMENT_COUNT', 1)">Increment</button>
</template>
所以每当我们的按钮被点击时,我们想要触发 INCREMENT_COUNT 并通过我们的有效负载增加 state 的值。
映射 Vuex 突变 - 选项 API
要触发 Vuex 突变,我们想调用store.commit,但一个很好的做法是使用类似于我们在 state 中看到的 mapState 的工作将您的突变映射到组件方法。 mapMutations
App.vue - 选项 API
Vue
<template>
{{ count }}
<button @click="INCREMENT_COUNT(1)">Increment</button>
</template>
<script>
import { mapMutations } from "vuex";
export default {
computed: {
count() {
return this.$store.state.count;
},
},
methods: mapMutations(["INCREMENT_COUNT"]),
};
</script>
映射 Vuex 突变 - 组合 API
但是我们也可以在这里使用我们的 Vuex Composition Helpers 库。重要的一点是,与 useState 类似,useMutations 接受一个键字符串数组并返回一个包含我们指定的所有函数的对象。
我们可以使用具有我们突变的确切名称的对象解构来从我们的存储中访问突变。
App.vue - 组合 API
Vue
<script setup>
import { useState, useMutations } from "vuex-composition-helpers/dist";
const { count } = useState(["count"]);
const { INCREMENT_COUNT } = useMutations(["INCREMENT_COUNT"]);
console.log(useMutations);
</script>
<template>
{{ count }} <button @click="INCREMENT_COUNT(1)">Increment</button>
</template>
Vuex 动作
Vuex 动作类似于突变,除了它们不直接改变状态,而是提交突变。
这些是必要的,因为突变必须是同步的。但是,在操作中,我们可以执行异步操作(如 API 调用),然后使用突变来改变状态。
一个动作有两个属性:
- 上下文 - 包含我们商店的详细信息
- 有效负载 - 我们可以传递数据的地方
main.js
javascript
const store = createStore({
state: {
count: 0,
},
mutations: {
INCREMENT_COUNT(state, payload) {
state.count += payload;
},
},
actions: {
incrementCount(context, payload) {
context.commit("INCREMENT_COUNT", payload);
},
},
});
Vuex 动作可以使用store.dispatch("actionName", payload).
如果我们看一下 Vue 开发工具,我们就会很好地了解动作和突变之间的区别。
正如我们所见,动作记录了它们的开始和结束时间,而突变被记录为单个事件。
这是因为尽管动作可能是重叠的(由于 API 响应时间等),但突变是同步的,因此我们可以保证知道突变前后的确切状态。
因此,如果我们要使用 来模拟异步方法setTimeout,我们的应用程序仍将按预期工作,一次只会发生一个突变。
main.js
javascript
const store = createStore({
// ...
actions: {
incrementCount(context, payload) {
setTimeout(() => {
context.commit("INCREMENT_COUNT", payload);
}, 1000);
},
},
// ...
});
映射 Vuex 操作
我想你已经开始根据我们如何映射我们的状态和突变来进行练习,所以我会保持简短。
在 Options API 中,我们可以使用mapActions.
App.vue - 选项 API
Vue
<template>
{{ count }}
<button @click="incrementCount(1)">Increment</button>
</template>
<script>
import { mapActions } from "vuex";
export default {
computed: {
count() {
return this.$store.state.count;
},
},
methods: mapActions(["incrementCount"]),
};
</script>
在 Composition API 中,我们可以使用useActions.
App.vue - 组合 API
Vue
<script setup>
import { useState, useActions } from "vuex-composition-helpers/dist";
const { count } = useState(["count"]);
const { incrementCount } = useActions(["incrementCount"]);
</script>
<template>
{{ count }} <button @click="incrementCount(1)">Increment</button>
</template>
Vuex 吸气剂
Vuex 的最后一个核心部分是 getter。
我们可以将 Vuex Getter 视为我们存储中的计算属性。
Getter 允许我们从 Vuex 状态中获取一个值。这对于创建可在整个应用程序中使用的可重用属性很有用。
他们收到两个参数
state- 我们可以用它来得出我们的价值
getters- 我们商店内的其他吸气剂
main.js
javascript
const store = createStore({
state: {
count: 0,
},
mutations: {
INCREMENT_COUNT(state, payload) {
state.count += payload;
},
},
actions: {
incrementCount(context, payload) {
context.commit("INCREMENT_COUNT", payload);
},
},
getters: {
doubleCount(state, getters) {
return state.count * 2;
},
doubleCountPlusOne(state, getters) {
return getters.doubleCount + 1;
},
},
});
然后,只要我们可以访问我们的商店,我们就可以在应用程序的其余部分访问这个 getter。
App.vue - 选项 API
Vue
<script>
export default {
computed: {
doubleCountPlusOne() {
return this.$store.getters.doubleCountPlusOne;
},
},
methods: mapActions(["incrementCount"]),
};
</script>
Getter 还可以返回一个接受任何类型参数的函数。当我们要根据特定值查询/过滤时,这很有用。例如,我们需要查看是否count大于 10。
main.js
javascript
映射我们的 Vuex Getter
与 state 类似,我们可以将 getter 映射到组件的本地计算属性中。
在选项 API...
App.vue - 选项 API
Vue
<template>
{{ doubleCountPlusOne }}
<button @click="incrementCount(1)">Increment</button>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
export default {
computed: mapGetters(["doubleCountPlusOne"]),
methods: mapActions(["incrementCount"]),
};
</script>
在组合 API 中……
App.vue - 组合 API
Vue
<script setup>
import { useActions, useGetters } from "vuex-composition-helpers/dist";
const { doubleCountPlusOne } = useGetters(["doubleCountPlusOne"]);
const { incrementCount } = useActions(["incrementCount"]);
</script>
<template>
{{ doubleCountPlusOne }} <button @click="incrementCount(1)">Increment</button>
</template>
包起来
尽管这绝不是 Vuex 中所有可用功能的完整列表,但它是 Vue 3 中复杂状态管理的一个很好的开始。
我强烈建议阅读Vuex 4(这是 Vue 3 的版本)文档,以了解更多关于 Vuex 背后的基础知识。
但我希望这会有所帮助,并且编码愉快!