这里介绍的VueX是匹配Vue3的V4版本,它绝大多数功能使用都和之前基本保持一致。
先一起看一下官网对于VueX的定义:
官网定义中提到了一个现代前端中非常重要的概念状态管理 ,怎么来理解这个概念呢?
在复杂的应用中,我们会经常遇到以下的问题:
其实这些问题我们也能解决,但需要状态在组件之间反复传递,十分容易出现错误,开发和维护成本都非常高,我们为什么不把组件的共享状态抽取出来,放到一个单独的空间管理呢,这个空间可以被任意组件访问到,这其实就是VueX的设计思想
并不是所有的项目中都需要使用VueX,如果你不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果你的应用够简单,你最好不要使用 Vuex。但是如果你需要构建一个中大型应用,在组件外部管理状态往往是一个更好的选择,那么这个时候VueX便会是自然而然的选择了。
通过npm命令在项目中安装VueX
npm i vuex --save
在package.json中确认正确的版本号
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
安装好VueX后,在src目录下新建一个store文件夹,再建立一个index.js文件,我们在这个js文件中来创建我们的store容器。
- import { createStore } from 'vuex'
-
- // 创建一个新的 store 实例
- const store = createStore({
- state() {
- return {
- count: 0,
- }
- },
- mutations: {
- increment(state) {
- state.count++
- },
- },
- })
- export default store
然后在入口文件main.js中引入store并使用它。这里的store和之前的router都可以理解为Vue的插件,都可以调用use方法。
- import { createApp } from 'vue'
- import App from './App.vue'
- import store from './store'
-
- createApp(App).use(store).mount('#app')
好了,现在我们的项目中就可以通过VueX来进行全局的状态管理了,接下来我们要深入学习其中的核心概念!
npm i vuex@3
- import Vue from 'vue'
- import App from './App.vue'
- import store from './store'
-
- Vue.config.productionTip = false
-
- new Vue({
- render: h => h(App),
- store:store, // 可以简写
- }).$mount('#app')
- import Vue from 'vue'
- import VueX from 'vuex'
- Vue.use(VueX)
- const store = new VueX.Store({
- state(){
- return {
- count: 1,
- msg:'公共状态'
- }
- },
- mutations: {
- // 定义修改数据的方法
- addCount(state, payload) {
- state.count += payload.a
- }
- }
- })
- export default store
- <template>
- <div>
- <button @click="addCount">{{ count }}</button>
- <p>{{ msg }}</p>
- </div>
- </template>
- <script>
- // 映射函数,可以将公共状态映射到当前组件
- import { mapMutations, mapState } from 'vuex'
- export default {
- mounted() {
- console.log(this)
- },
- computed: {
- ...mapState({
- count: (state) => state.count,
- msg: (state) => state.msg, // 这里可以改名字,但是不推荐改
- })
- },
- methods: {
- add() {
- // 违背了数据单向流动的原则
- // this.$store.state.count++
- // this.$store.commit通过这个方法修改了公共状态值
- this.$store.commit('addCount', { a: 10, b: "haha" })
- }
- }
- }
- </script>
store中保存的状态同样是具有响应性的,我们把应用中的公共状态统一保存到state属性中。
js
- const store = createStore({
- state() {
- return {
- count: 0, //这里的count后面就可以被应用中所有的组件共享
- }
- },
- })
在项目中任意创建了一个Hello组件,现在希望在这个组件中取出刚才的count。由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:
在组件中获取到store必须调用useStore这个方法(和vue-router类似,在Vue3的最新生态中推荐使用函数的方式来获取示例,而非this)
vue
- //Hello组件中
- <template>
- <h2>{{ count }}</h2>
- </template>
- <script setup>
- import { useStore } from 'vuex'
- import { computed } from '@vue/reactivity'
- let count = computed(() => {
- return useStore().state.count
- })
- </script>
映射函数,可以将公共的状态映射到当前的组件,再在当前组件中调用就会简便很多。如果你使用的是Vue2版本的配置选项写法,使用映射函数往往非常高效。当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性:
- import { mapState } from 'vuex'
-
- export default {
- // ...
- computed: {
- ...mapState({
- // 箭头函数可使代码更简练
- count: state => state.count,
-
- // 传字符串参数 'count' 等同于 `state => state.count`
- countAlias: 'count',
-
- // 为了能够使用 `this` 获取局部状态,必须使用常规函数
- countPlusLocalState (state) {
- return state.count + this.localCount
- }
- })
- }
- }
使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。


- <template>
- <div>
- <button @click="addCount">{{ count }}</button>
- </div>
- </template>
- <script>
- import { mapMutations, mapState } from 'vuex'
- export default {
- methods: {
- ...mapMutations(["addCount"])
- }
- }
- </script>
vuex中mutations里都是同步修改数据的方法, 如果希望异步修改数据,需要把对应的方法,定义在actions的对象中
但是actions不能直接修改state,最终还是要通过触发mutations来实现。

引用的方法同样可以映射
- <template>
- <div>
- <button @click="addCount">{{ count }}</button>
- <button @click="addAsyncCount(3)">{{ count }}</button>
- <!-- 异步按钮,三秒后响应,增加3 -->
- <p>{{ msg }}</p>
- </div>
- </template>
- <script>
- import { mapMutations, mapState, mapActions } from 'vuex'
- export default {
- mounted() {
- console.log(this)
- },
- computed: {
- ...mapState({
- count: (state) => state.count,
- msg: (state) => state.msg,
- })
- },
- methods: {
- ...mapMutations(["addCount"]),
- ...mapActions(["addAsyncCount"]),
- }
- }
- </script>
有时候我们需要从 store 中的 state 中派生出一些状态,通过计算属性即可实现,但如果有多个组件需要用到这个计算属性,我们要么复制这个计算属性,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。
- const store = createStore({
- state: {
- todos: [
- { id: 1, text: '...', done: true },
- { id: 2, text: '...', done: false }
- ]
- },
- getters: {
- doneTodos (state) {
- return state.todos.filter(todo => todo.done)
- }
- }
- })
和state类似,同样可以在组件中通过store实例来读取到对应的getter。
- //Hello中
- let doneTodos = computed(()=>{
- return useStore().getters.doneTodos
- })
mapGetters 辅助函数同样可以将 store 中的 getter 映射到局部计算属性中,如果你使用配置选项仍然推荐你这么做,在setup语法糖中这样使用的意义就没那么大了。
- import { mapGetters } from 'vuex'
- export default {
- computed: {
- // 使用对象展开运算符将 getter 混入 computed 对象中
- ...mapGetters([
- 'doneTodosCount',
- 'anotherGetter',
- ])
- }
- }
VUE2实现
VueX规定必须通过mutation来能修改state中的状态。mutations中保存了修改了state的各种方法,在组件中可以通过store.commit来触发对应的方法,同时传入参数(这个参数叫做载荷)
- //store
- mutations: {
- increment(state, n) {
- state.count += n
- },
- },
- //Hello
- <template>
- <h2>{{ count }}</h2>
- <button @click="add">点我count增加</button>
- <div v-for="todo in doneTodos" :key="todo.id">
- <h3>{{todo.text}}</h3>
- </div>
- </template>
-
- <script setup>
- import { useStore } from 'vuex'
- import { computed } from '@vue/reactivity'
- const store = useStore()
- let count = computed(() => {
- return store.state.count
- })
- let doneTodos = computed(()=>{
- return store.getters.doneTodos
- })
- const add = ()=>{
- store.commit('increment',5)
- }
- </script>
VueX是规定mutations中修改状态必须是同步的,我们可以尝试写一段异步代码:
- mutations: {
- increment(state, n) {
- setTimeout(() => {
- state.count += n
- }, 2000)
- },
- },
大家肯定会疑问,代码没问题呀页面也能够正确响应状态的变化,但是VueX设计思想中需要能够捕捉到mutation前后状态的快照,异步代码无法让VueX能够了解其执行时机,这样描述可能大家此时仍然不好理解,所以现在你需要记住这个结论!
action和mutation有些类似,但不同在于:
Action 函数第一个参数接受一个与 store 实例具有相同方法和属性的 context对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
Action第二个参数也接受一个载荷。
- actions:{
- increment(ctx,payload){
- setTimeout(()=>{
- ctx.commit('increment',payload)
- },2000)
- }
- }
在组件中视图中,可以通过事件来触发action。所以之前的异步操作可以通过分发action来实现。
- const add = () => {
- store.dispatch('increment',2)
- }
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
- const moduleA = {
- state: () => ({ ... }),
- mutations: { ... },
- actions: { ... },
- getters: { ... }
- }
-
- const moduleB = {
- state: () => ({ ... }),
- mutations: { ... },
- actions: { ... }
- }
-
- const store = createStore({
- modules: {
- a: moduleA,
- b: moduleB
- }
- })
-
- store.state.a // -> moduleA 的状态
- store.state.b // -> moduleB 的状态
默认情况下,模块内部的 action 和 mutation 仍然是注册在全局命名空间的——这样使得多个模块能够对同一个 action 或 mutation 作出响应。Getter 同样也默认注册在全局命名空间。必须注意,不要在不同的、无命名空间的模块中定义两个相同的 getter 从而导致错误。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:
- const store = createStore({
- modules: {
- account: {
- namespaced: true,
-
- // 模块内容(module assets)
- state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
- getters: {
- isAdmin () { ... } // -> getters['account/isAdmin']
- },
- actions: {
- login () { ... } // -> dispatch('account/login')
- },
- mutations: {
- login () { ... } // -> commit('account/login')
- },
- // 嵌套模块
- modules: {
- // 继承父模块的命名空间
- myPage: {
- state: () => ({ ... }),
- getters: {
- profile () { ... } // -> getters['account/profile']
- }
- },
- }
- }
- }
- })
命名空间在多人协作处理大型应用的时候还是非常具有实际意义的。
vue2实现
分别为login和profile新建store项目,可以分别为不同的组件书写不同的store
- import Vue from 'vue'
- import VueX from 'vuex'
- import loginStore from "./loginStore";
- import profileStore from "./profileStore";
- Vue.use(VueX)
- const store = new VueX.Store({
- modules:{
- login:loginStore,
- profile:profileStore,
- }
- })
- export default store
在app中可以通过模板来获取到子组件store中的值
- // 代表Store中modules下,login这个store中a元素的值
- <p>{{ $store.state.login.a }}</p>
在store中,存放需要在组件中流通的公共状态
不需要流通的状态和属性还是仍然放在原本的组件中