Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,类似 React 中的 Redux

装包

  1. npm install vuex --save

注入 store

导入 store

  1. import store from './store'

将 store 注入到组件中

  1. new Vue({
  2. el: '#app',
  3. store,
  4. router,
  5. template: '<App/>',
  6. components: { App }
  7. })

创建 store

我们这里根据官网给出的例子,创建 store

数据结构为:

数据结构

其中index.js 在 store 中创建 store 并将数据组合的作用,类似 Redux 中的 combineReducers

  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. //引用 vuex
  4. import comment from './modules/comment'
  5. // 导入 comment 数据
  6. Vue.use(Vuex)
  7. // 安装使用 vuex 插件
  8. export default new Vuex.Store({
  9. // 创建 store 并导出
  10. modules: {
  11. comment
  12. }
  13. })

Vuex 通过 Vue.use(Vuex) 提供了一种机制将状态从根组件『注入』到每一个子组件中 store 选项,在组件需要 store 时,用 (this.$store.state)调用

  1. const state = {
  2. all: [
  3. { text: 'haha' },
  4. { text: 'gege' }
  5. ]
  6. }
  7. export default {
  8. state
  9. }

在 modules 文件夹中写入数据并导出

参考https://vuex.vuejs.org/zh-cn/state.html

读取数据

Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在 计算属性中返回某个状态

  1. <script>
  2. export default {
  3. name: 'comment-box',
  4. // 在计算属性 computed 中
  5. computed: {
  6. //comments 方法
  7. comments: function () {
  8. //通过返回值拿到 store 中的数据
  9. return this.$store.state.comment.all
  10. }
  11. }
  12. }
  13. </script>

通过 comments 方法 返回的this.$store.state.comment.all拿到 store 中的值

将数据渲染到页面上

  1. <template>
  2. <li v-for="comment in reversedMessage">
  3. {{ comment.text }}
  4. </li>
  5. </template>

对 store 中的数据进行修改

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation

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

  1. const mutations = {
  2. [函数名] (state) {
  3. // 变更状态
  4. state.count++
  5. }
  6. }
  7. })

提交载荷(Payload)

Payload 就是接受的数据

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

  1. const mutations = {
  2. [increment] (state, n) {
  3. //n 为组件传过来的数据
  4. state.count += n
  5. }
  6. }

使用常量替代 Mutation 事件类型

在 store 文件夹下创建 mutation-types.js文件,将 Mutation 事件类型写在里面

  1. export const SOME_MUTATION = 'SOME_MUTATION'
  2. //声明 一个常量作为函数名

Mutaion 函数

store 类似于 React 中的 action

  1. //导入 事件类型
  2. import * as types from '../mutation-types'
  3. //数据 state
  4. const state = {
  5. all: [
  6. { text: 'fooo' },
  7. { text: 'barr' }
  8. ]
  9. }
  10. //创建一个 mutations 将接受的数据放入 state 中
  11. const mutations = {
  12. [types.ADD_COMMENT] (state, { text }) {
  13. state.all.push({ text })
  14. }
  15. }
  16. export default {
  17. state,
  18. mutations
  19. }
  20. //导出 state mutations

在组件中提交 Mutations

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

  1. <script>
  2. import * as types from '../store/mutation-types'
  3. //导入 mutation 函数类型
  4. export default {
  5. name: 'comment-box',
  6. computed: {
  7. reversedMessage: function () {
  8. return this.comments.slice().reverse()
  9. },
  10. comments: function () {
  11. return this.$store.state.comment.all
  12. }
  13. },
  14. methods: {
  15. addComment: function () {
  16. const input = document.getElementById('commentForm')
  17. this.$store.commit(types.ADD_COMMENT, { text: input.value })
  18. input.value = ''
  19. }
  20. }
  21. }
  22. </script>
  1. <template>
  2. <div class="comment-box">
  3. <div class="form">
  4. <input type="text" id="commentForm" />
  5. <button type="submit" v-on:click="addComment">发布</button>
  6. </div>
  7. <ul>
  8. <li v-for="comment in reversedMessage">
  9. {{ comment.text }}
  10. </li>
  11. </ul>
  12. </div>
  13. </template>

actions

action 类似于 React 中的 Thunk

Action 类似于 mutation,不同在于:

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

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

小例子

  1. const store = new Vuex.Store({
  2. state: {
  3. count: 0
  4. },
  5. mutations: {
  6. increment (state) {
  7. state.count++
  8. }
  9. },
  10. //发出 actions
  11. actions: {
  12. increment (context) {
  13. context.commit('increment')
  14. //通过 commit 触发 mutations
  15. }
  16. }
  17. })

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取 state 和 getters。当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了。

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

  1. actions: {
  2. increment ({ commit }) {
  3. commit('increment')
  4. }
  5. }

参考https://vuex.vuejs.org/zh-cn/mutations.html

分发 Action

Action 通过 store.dispatch 方法触发:

  1. store.dispatch('increment')

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

实际上并非如此,还记得 mutation 必须同步执行这个限制么?

Action 就不受约束!我们可以在 action 内部执行异步操作:

  1. actions: {
  2. incrementAsync ({ commit }) {
  3. setTimeout(() => {
  4. commit('increment')
  5. }, 1000)
  6. }
  7. }

Actions 支持同样的 载荷方式 和对象方式进行分发:

  1. // 以载荷形式分发
  2. store.dispatch('incrementAsync', {
  3. amount: 10
  4. })
  5. // 以对象形式分发
  6. store.dispatch({
  7. type: 'incrementAsync',
  8. amount: 10
  9. })

购物车示例

涉及到调用异步 API 和 分发多重 mutations:

  1. actions: {
  2. checkout ({ commit, state }, products) {
  3. // 把当前购物车的物品备份起来
  4. const savedCartItems = [...state.cart.added]
  5. // 发出结账请求,然后乐观地清空购物车
  6. commit(types.CHECKOUT_REQUEST)
  7. // 购物 API 接受一个成功回调和一个失败回调
  8. shop.buyProducts(
  9. products,
  10. // 成功操作
  11. () => commit(types.CHECKOUT_SUCCESS),
  12. // 失败操作
  13. () => commit(types.CHECKOUT_FAILURE, savedCartItems)
  14. )
  15. }
  16. }

注意我们正在进行一系列的异步操作,并且通过提交 mutation 来记录 action 产生的副作用(即状态变更)

在组件中分发 Action

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

  1. import { mapActions } from 'vuex'
  2. export default {
  3. // ...
  4. methods: {
  5. ...mapActions([
  6. 'increment' // 映射 this.increment() 为 this.$store.dispatch('increment')
  7. ]),
  8. ...mapActions({
  9. add: 'increment' // 映射 this.add() 为 this.$store.dispatch('increment')
  10. })
  11. }
  12. }

组合 Actions

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

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

  1. actions: {
  2. actionA ({ commit }) {
  3. return new Promise((resolve, reject) => {
  4. setTimeout(() => {
  5. commit('someMutation')
  6. resolve()
  7. }, 1000)
  8. })
  9. }
  10. }

现在你可以:

  1. store.dispatch('actionA').then(() => {
  2. // ...
  3. })
  4. 在另外一个 action 中也可以:
  5. actions: {
  6. // ...
  7. actionB ({ dispatch, commit }) {
  8. return dispatch('actionA').then(() => {
  9. commit('someOtherMutation')
  10. })
  11. }
  12. }

最后,如果我们利用 async / await 这个 JavaScript 即将到来的新特性,我们可以像这样组合 action:

  1. // 假设 getData() 和 getOtherData() 返回的是 Promise
  2. actions: {
  3. async actionA ({ commit }) {
  4. commit('gotData', await getData())
  5. },
  6. async actionB ({ dispatch, commit }) {
  7. await dispatch('actionA') // 等待 actionA 完成
  8. commit('gotOtherData', await getOtherData())
  9. }
  10. }

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

参考