Vuex全局数据状态管理

前言

在一般中大型的Vue应用程序中,通常我们都会遇到组件之间传递或者是共享数据(状态)的需求,如果是单个组件之间互相传递数据我们可以使用props、全局事件总线等多种方式,但是当我们在整个应用中需要多个组件之间共享一些数据(状态)并且要求这些数据做到响应式,那么我们可以借助Vue生态下官方出品的Vuex。

需要说明的是,在Vue2时代主要是使用Vuex作为集中式状态管理的工具,但是在Vue3中,官方推出了Pinia产品作为Vuex的平替,但是Vuex仍然支持在Vue2/Vue3中进行使用。

在下面文章内容中,我主要以在Vue2中使用Vuex来讲述。

0、搭建环境

在这里呢,我选择采用Vite作为构建工具来创建Vue2的项目,但是Vite支持的模板中好像并没有Vue2的应用,所以在这里我们需要先创建vue3的工程然后手动修改为Vue2。

pnpm create vite

PS C:\Users\Smile\Desktop> pnpm create vite
√ Project name: ... vue2-vuex-demo
√ Select a framework: » Vue
√ Select a variant: » JavaScript

Scaffolding project in C:\Users\Smile\Desktop\vue2-vuex-demo...

Done. Now run:

  cd vue2-vuex-demo
  pnpm install
  pnpm run dev

PS C:\Users\Smile\Desktop>

 

 

pnpm install

C:\Users\Smile\Desktop> cd .\vue2-vuex-demo\
C:\Users\Smile\Desktop\vue2-vuex-demo> pnpm install
Packages: +30
++++++++++++++++++++++++++++++
Progress: resolved 70, reused 30, downloaded 0, added 30, done

dependencies:
+ vue 3.5.13

devDependencies:
+ @vitejs/plugin-vue 5.2.0
+ vite 5.4.11

Done in 1.3s
 C:\Users\Smile\Desktop\vue2-vuex-demo>

 

 

pnpm run dev

C:\Users\Smile\Desktop\vue2-vuex-demo> pnpm run dev

> vue2-vuex-demo@0.0.0 dev C:\Users\Smile\Desktop\vue2-vuex-demo
> vite

Port 5173 is in use, trying another one...

  VITE v5.4.11  ready in 582 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

 

 

此时我们可以在浏览器打开本地的5173端口服务看到基于vue3模板的工程应用。

修改为Vue2工程

修改main.js文件

// main.js

// import { createApp } from 'vue'
// import './style.css'
// import App from './App.vue'

// createApp(App).mount('#app')

import Vue from 'vue';
import App from './App.vue';
 
Vue.config.productionTip = false;
 
new Vue({
  render: h => h(App),
}).$mount('#app');

 

 

修改vite.conf.js

//vite.conf.js

import { defineConfig } from 'vite'
// import vue from '@vitejs/plugin-vue'
import vue from '@vitejs/plugin-vue2'

// https://vite.dev/config/
export default defineConfig({
  plugins: [vue()],
})

 

 

修改package.json 文件

dependencies 中删除 vue,

devDependencies 中删除 @vitejs/plugin-vue

// package.json

"dependencies": {
    // "vue": "^3.5.12"
  },
"devDependencies": {
    // "@vitejs/plugin-vue": "^5.1.4",
    "vite": "^5.4.10"
}

 

JSON文件中不允许有注释,此处只是说明删除。

安装vue2和vite插件

pnpm add vue@2.7.16
pnpm add -D @vitejs/plugin-vue2@2.3.1

 

 

安装之后的package.json 文件

// package.json
"dependencies": {
   "vue": "2.7.16"
},
"devDependencies": {
   "@vitejs/plugin-vue2": "2.3.1",
   "vite": "^5.4.10"
}

重启项目

需要修改App.vue,可以把components下的HelloWorld.vue组件删除。

//App.vue
<template>
 <div>
  <h1>Hello World</h1>
 </div>
</template>

 

 

重启项目:pnpm run dev

参考官方文档学习:https://v3.vuex.vuejs.org/zh/

1、安装Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension (opens new window),提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。

Vuex同时支持Vue2、Vue3版本,分别对应着Vuex的3和4版本,安装命令如下:

- Vue2版本安装vuex@3
pnpm install vuex@3 --save
​
- Vue3版本安装vuex@next
pnpm install vuex@next
\
pnpm install vuex@4

也可以使用你喜欢的包管理工具进行安装,建议Vue3版本还是使用Pinia状态管理工具吧。

在一个模块化的打包系统中,您必须显式地通过 Vue.use() 来安装 Vuex:

import Vue from 'vue'
import Vuex from 'vuex'
​
Vue.use(Vuex)

 

 

当使用全局 script 标签引用 Vuex 时,不需要以上安装过程。

折叠内容,点击展开

Vuex 依赖 Promise 。如果你支持的浏览器并没有实现 Promise (比如 IE),那么你可以使用一个 polyfill 的库,例如 es6-promise

你可以通过 CDN 将其引入:

<script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.js"></script>

然后 window.Promise 会自动可用。

如果你喜欢使用诸如 npm 或 Yarn 等包管理器,可以按照下列方式执行安装:

npm install es6-promise --save # npm
yarn add es6-promise # Yarn

或者更进一步,将下列代码添加到你使用 Vuex 之前的一个地方:

import 'es6-promise/auto'

2、 创建Vuex的核心对象Store

每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。

Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT (opens new window))”而存在。

这也意味着,每个应用将仅仅包含一个 store 实例。如下面代码所示,在整个应用只需要new一个Vuex.Store对象。

👍 好处:单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。

⚠️ 提示:单状态树和模块化并不冲突——在后面我们会将状态和状态变更事件分布到各个子模块中。

Vuex 和单纯的全局对象有以下两点不同:

  • 1、Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

  • 2、你不能直接改变 store 中的状态。

    改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

0.状态管理模式

上图展示了 Vuex 工作流程的示意图,Vue.js 专用状态管理库。以下是对各个部分和它们之间关系的理解:

  1. State(状态):这是应用程序中共享数据的地方。所有组件都可以访问这个单一的数据源。

  2. Mutations:这些是唯一可以改变 state 中数据的方法集。它们必须同步执行,并且总是以相同的顺序调用,这使得调试变得容易。

  3. Actions:这些方法允许我们封装异步操作并触发 mutations。它们提供了更灵活的方式来处理复杂的业务逻辑。

  4. Getters:虽然在图中没有明确显示出来,但 getters 是从 state 对象派生出其他值的方式。它们类似于计算属性,但是专门用于读取 state。

  5. Vue Components:这些是使用 Vuex 管理状态的应用程序中的视图层。他们通过 dispatch 方法触发 actions,然后接收由 state 和 getters 提供的数据进行渲染。

  6. Backend API:这是与 Vuex 不直接相关的外部服务或数据库接口。actions 可能会调用这些 API 来获取或更新数据,然后通过 commit 触发相应的 mutations 更新 state。

  7. Devtools:这是一个可选的部分,它提供了一个界面来监视和调试 Vuex store 的变化。这对于开发和测试非常有用。

整个过程的工作方式如下:

  • 组件需要更改状态时,它会 dispatch 一个 action。

    Action 可能涉及与后端 API 进行交互或其他异步操作。

  • 完成后,action 将 commit 一个 mutation。

    Mutation 直接修改 state。

  • 所有监听 state 或相关 getters 的组件将重新渲染以反映新的状态。

这种模式确保了状态的变化是可预测的、易于跟踪的,并且有助于保持代码的清晰和模块化。

下面为了方便在vue应用中任意组件访问 this.$store property,前提我们需要有一个Vuex提供的Store对象实例,如下代码所示:

// src/vuex/store.js
​
const store = new Vuex.Store({
   // 接收三个核心对象配置
   state: {}, // 集中式管理的数据对象(状态)
   mutations: {}, // 更改 Store 中 state 集中式管理的数据对象(状态)的唯一方法
   actions: {}, // 可以包含任意异步操作,然后通过调用mutations中的方法修改数据对象(状态)
})

 

通常我们可以把Store实例的三个核心配置对象单独出来,下面单独来说说。

1. State

state(状态)作为Vuex中核心对象Store(仓库)的一个必要属性对象,其定义了存储在 Vuex 中的数据(状态),也就是需要进行全局集中式管理的数据,简言之就是让全局各个VUE组件中共享的数据信息。

⚠️ 需要注意的是,存储在 Vuex 中的数据和 Vue 实例中的 data 遵循相同的规则。

  • 对象必须是纯粹的对象 (含有零个或多个的 key/value 对)

  • 浏览器 API 创建的原生对象,原型上的 property 会被忽略

大概来说,data 应该只能是数据 - 不推荐观察拥有状态行为的对象。详细参考:https://v2.cn.vuejs.org/v2/api/#data

// src/vuex/store.js
const state = {
   count: 0,
   currentUser: {
       uname: 'admin',
       roles: ['admin', 'test'],
       phone: '15936****42',
       info: 'www.imyjs.cn'
  }
}

 

 

说明:

使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。

虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。

如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。

MapState辅助函数

  1. 在组件中引入mapState函数

    import { mapState } from 'vuex'

     

  2. computed 当中使用

    (1) 第一种方式:对象形式
    computed: {
       ...mapState({users: 'users'})
    },
    (2) 第二种方式:数组形式
    computed: {
       ...mapState(['users'])
    },

     

插值语法就可以修改为:{{ users }}

⚠️ 需要注意的是,...mapState( )是一个方法。

2.Mutation

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:

  • 每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。

简言之,mutations对象包含了一系列函数,方法名可视为是事件类型 (type),函数体则为一个回调函数。如下increment函数。

// src/vuex/store.js
increment (state) {
   // 变更状态
   state.count++
},

 

 

⚠️需要注意的是,当你想要修改state中数据状态时,你不能直接调用一个 mutation 处理函数。如store.mutations.increment()是错误的使用。

mutations 这个选项更像是事件注册:“当触发一个类型为 increment 的 mutation 时,调用此函数。”

要唤醒一个 mutation 处理函数,你需要以相应的 type 调用 store.commit 方法:store.commit('increment')

Mutation 需遵守 Vue 的响应规则

既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:

  1. 最好提前在你的 store 中初始化好所有所需属性。

  2. 当需要在对象上添加新属性时,你应该

  • 使用 Vue.set(obj, 'newProp', 123), 或者

  • 以新对象替换老对象。例如,利用对象展开运算符 我们可以这样写:

    state.obj = { ...state.obj, newProp: 123 }

提交载荷(Payload)

你可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload):

incrementNumber(state, number){
   state.count += number
}

 

 

调用方式:store.commit('incrementNumber', 20)

⚠️需要注意的是,在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读。

当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此 handler 保持不变:

incrementCount(state, payload){
   state.count += payload.count
}

 

 

调用方式:store.commit('incrementCount', {count: 20})

// src/vuex/store.js
const mutations = {
   incrementNumber(state, number){
       state.count += number
  },
​
   incrementCount(state, payload){
       state.count += payload.count
  },
}

 

 

使用常量替代 Mutation 事件类型

通过上面的学习,我们知道了

  • 通过store对象提供的commit方法来提交 mutation 进而去更改 Vuex 的 store 中的状态

  • 以及mutations中定义了一系列方法,这些方法可认为是注册的事件(用于修改集中式管理状态的事件),其中方法名为事件类型 (type) 和 一个 回调函数 (handler)

  • 以及任意方法它会接受 state 作为第一个参数,该参数是store提供的用于方便去修改state中的数据(状态)

  • 并且我们可以给mutations中的方法(注册的事件)提供负载(参数),而且最优做法是提供对象参数会更易读

那么除了以上的学习,我们对于mutations还应该注意:

  • 使用常量替代 mutation 事件类型(方法名):这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然。

  • 但是用不用常量取决于你——在需要多人协作的大型项目中,这会很有帮助。但如果你不喜欢,你完全可以不这样做。

新建mutation-types.js文件:

/**
* 使用常量替代 mutation 事件类型(方法名)
*/
export const INCREMENT_NUMBER = 'INCREMENT_NUMBER'
export const INCREMENT_COUNT = 'INCREMENT_COUNT'
export const SOME_MUTATION = 'SOME_MUTATION'

 

 

在mutations中使用:

// src/vuex/store.js
​
import { INCREMENT_COUNT, INCREMENT_NUMBER } from './mutation-types'
const mutations = {
  [INCREMENT_COUNT](state, payload) {
       state.count += payload.count
  }
}

 

 

Mutation 必须是同步函数

除此之外,最重要原则就是要记住 mutation 必须是同步函数。

mutations: {
   someMutation (state) {
       api.callAsyncMethod(() => {
       state.count++
      })
  }
}

 

 

现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。

然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:

因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。

使用Mutation

最后就是你可以在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。

MapMutations

  1. 在组件中引入mapState函数

    import { mapMutations } from 'vuex'

  2. methods 当中使用

    methods : {
        // 对象写法
       ...mapMutations({add:’plusOne’,reverseName:’reverseName’})
        // 数组写法(前提是:保证methods中的方法名和actions中的方法名一致)
       ...mapMutations([‘plusOne’, ‘reverseName’]
    }

官方示例

import { mapMutations } from 'vuex'
​
export default {
 // ...
 methods: {
   ...mapMutations([
     'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
​
     // `mapMutations` 也支持载荷:
     'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
  ]),
   ...mapMutations({
     add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
  })
}
}
⚠️ 需要注意的是,...mapMutations( )是一个方法。

3.Action

在 mutation 中混合异步调用会导致你的程序很难调试。

例如,当你调用了两个包含异步回调的 mutation 来改变状态,你怎么知道什么时候回调和哪个先回调呢?这就是为什么我们要区分这两个概念。

通过上面对mutations的了解,在 Vuex 中,mutation 都是同步事务,为了处理异步操作,让我们来看一看 Action

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。

  • Action 可以包含任意异步操作。

// src/vuex/store.js
const actions = {
   increment (context) {
       context.commit('INCREMENT_COUNT')
  },
}

 

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。

但是当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了。

实践中,我们会经常用到 ES2015 的参数解构来简化代码(特别是我们需要调用 commit 很多次的时候)如下代码所示:

increment ({ commit }) {
   commit('INCREMENT_COUNT')
},

 

分发 Action

经过上面的学习我们知道了如何定义action,那么我们应该如何在组件中去调用(触发)这些action呢?

Action 通过 store.dispatch 方法触发:store.dispatch('increment')

乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?

实际上并非如此,还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作:

incrementAsync ({ commit }) {
   setTimeout(() => {
       commit('increment')
  }, 1000)
},

 

除此之外,Actions 支持同样的载荷方式和对象方式进行分发:

  • 以负载形式进行分发

    store.dispatch('incrementAsync', {amount: 100})

  • 以对象形式进行分发

    store.dispatch({
        type: 'incrementAsync',
        amount: 100
    })

你在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store).

MapActions

  1. 在组件中引入mapState函数

    import { mapActions } from 'vuex'
  2. methods 当中使用

    methods : {
        // 对象写法
       ...mapActions({add:’plusOne’,reverseName:’reverseName’})
        // 数组写法(前提是:保证methods中的方法名和actions中的方法名一致)
       ...mapActions([‘plusOne’, ‘reverseName’]
    }

    ⚠️ 需要注意的是,...mapActions( )是一个方法。

官方示例:

import { mapActions } from 'vuex'
​
export default {
 // ...
 methods: {
   ...mapActions([
     'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
​
     // `mapActions` 也支持载荷:
     'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
  ]),
   ...mapActions({
     add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
  })
}
}

 

组合 Action

Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?

首先,你需要明白 store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise:

actionA ({ commit }) {
   return new Promise((resolve, reject) => {
     setTimeout(() => {
       commit('someMutation')
       resolve()
    }, 1000)
  })
},
   
store.dispatch('actionA').then(() => {
// ...
})
​
// 在另外一个 action 中也可以:
actionB ({ dispatch, commit }) {
   return dispatch('actionA').then(() => {
       commit('someOtherMutation')
  })
},

 

最后,如果我们利用 async / await,我们可以如下组合 action:

// 假设 getData() 和 getOtherData() 返回的是 Promise
async actionA ({ commit }) {
   commit('gotData', await getData())
对象为什么不是   },
async actionB ({ dispatch, commit }) {
   await dispatch('actionA') // 等待 actionA 完成
   commit('gotOtherData', await getOtherData())
}
​

 

一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。

4.Getter

有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数:

computed: {
 doneTodosCount () {
   return this.$store.state.todos.filter(todo => todo.done).length
}
}

 

如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

Getter 接受 state 作为其第一个参数:

const store = new Vuex.Store({
 state: {
   todos: [
    { id: 1, text: '...', done: true },
    { id: 2, text: '...', done: false }
  ]
},
 getters: {
   doneTodos: state => {
     return state.todos.filter(todo => todo.done)
  }
}
})

 

Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值:

store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

 

Getter 也可以接受其他 getter 作为第二个参数:

getters: {
 // ...
 doneTodosCount: (state, getters) => {
   return getters.doneTodos.length
}
}
store.getters.doneTodosCount // -> 1

 

我们可以很容易地在任何组件中使用它:

computed: {
   doneTodosCount () {
       return this.$store.getters.doneTodosCount
   }
}

 

注意,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的.

你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。\

getters: {
  // ...
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }

 

注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果

MapGetters辅助函数

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}

 

如果你想将一个 getter 属性另取一个名字,使用对象形式:

...mapGetters({
 // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
 doneCount: 'doneTodosCount'
})
⚠️ 需要注意的是,...mapGetters( )是一个方法。

5.Modules

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

 

命名空间

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。

如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:

const store = new Vuex.Store({
  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']
          }
        },

        // 进一步嵌套命名空间
        posts: {
          namespaced: true,

          state: () => ({ ... }),
          getters: {
            popular () { ... } // -> getters['account/posts/popular']
          }
        }
      }
    }
  }
})

 

启用了命名空间的 getter 和 action 会收到局部化的 getterdispatchcommit。换言之,你在使用模块内容(module assets)时不需要在同一模块内额外添加空间名前缀。更改 namespaced 属性后不需要修改模块内的代码。

更多参考官方文档:Module | Vuex

3、注入vue实例

首先需要我们导出上面创建的Vuex.Store对象:

// src/vuex/store.js
const store = new Vuex.Store({
   state,
   mutations,
   actions
})
​
// 导出Vuex中核心对象Store(仓库)
export default store;
​
// 简写导出
// export default new Vuex.store({state,mutations,actions})

 

vuex的仓库对象导出之后,为了方便在vue应用中任意组件访问 this.$store property, Vuex 提供了一个从根组件向所有子组件,以 store 选项的方式“注入”该 store 的机制:

// main.js
​
import Vue from 'vue'
import App from './App.vue'
// 引入自定义Store对象
import store from './vuex/store.js'
​
Vue.config.productionTip = false
​
new Vue({
   render: h => h(App),
   store // 以 store 选项的方式“注入”
}).$mount('#app')

 

后面我们就可以从任意组件的方法提交一个变更:

methods: {
   increment() {
       this.$store.commit('定义在mutations中的方法名字')
       // 再次强调,我们通过提交 mutation 的方式,而非直接改变 store.state.data,是因为我们想要更明确地追踪到状态的变化
  }
}

 

由于 store 中的状态是响应式的,在组件中调用 store 中的状态简单到仅需要在计算属性中返回即可。触发变化也仅仅是在组件的 methods 中提交 mutation。

4、组件之间的使用

最简单示例

<template>
  <div>
    <h1>使用vuex提供的Store对象管理全局数据状态</h1>
    <div>
      <h3>{{ $store.state.count }}</h3>
      <button @click="$store.commit('increment')">点我加一</button>
      <button @click="$store.dispatch('incrementNumber')">点我加二</button>
    </div>
  </div>
</template>

<script>
export default{
  name: 'App',
  data(){
    return {}
  },
  methods: {}
}
</script>

 

综合案例

5、项目结构

Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:

  1. 应用层级的状态应该集中到单个 store 对象中。

  2. 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。

  3. 异步逻辑都应该封装到 action 里面。

只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。

对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:

├── index.html
├── main.js
├── api
│   └── ... # 抽取出API请求
├── components
│   ├── App.vue
│   └── ...
└── store
    ├── index.js          # 我们组装模块并导出 store 的地方
    ├── actions.js        # 根级别的 action
    ├── mutations.js      # 根级别的 mutation
    └── modules
        ├── cart.js       # 购物车模块
        └── products.js   # 产品模块

 

6、其他

  • 插件 | Vuex

    Vuex 的 store 接受 plugins 选项,这个选项暴露出每次 mutation 的钩子。Vuex 插件就是一个函数,它接收 store 作为唯一参数:

    const myPlugin = store => {
      // 当 store 初始化后调用
      store.subscribe((mutation, state) => {
        // 每次 mutation 之后调用
        // mutation 的格式为 { type, payload }
      })
    }
  • 然后像这样使用:

    const store = new Vuex.Store({
      // ...
      plugins: [myPlugin]
    })

     

    详细内容请参考官方文档。

  • 严格模式 | Vuex

  • 表单处理 | Vuex

  • 测试 | Vuex

  • 热重载 | Vuex

阅读剩余
THE END