在Vue项目中使用Vuex

什么是Vuex

“Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。”

那什么是状态呢?以我的理解就是在vue组件的data中的属性需要共享给其他vue组件使用的部分,就叫做状态。简单的说就是data中需要共用的属性。所以Vuex可以这么理解:集中管理所有组件中需要共用的属性(数据),并且规定了对这些属性的操作。每次对这些属性进行操作都要遵守规定的形式,这样可以保证这些属性“以一种可预测的方式发生变化”。

关于Vuex的详细介绍可以直接移步官方文档:https://vuex.vuejs.org/zh-cn/。本文的内容主要是在实际项目中使用Vuex。

Vuex组成

Vuex的主要概念如下:

  • Store
    表示对Vuex对象的全局引用。组件通过Store来访问Vuex对象中的State(下面讲到)
  • State
    Vuex对象的状态,即其所拥有的数据
  • Getter
    相当于Store的计算属性。因为就像计算属性一样,Getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。下面会说到具体的使用场景
  • Mutation
    定义了对State中数据的修改操作。组件使用State中的数据的时候并不能直接对数据进行修改操作,需要调用Mutation定义的操作来实现对数据的修改。这也是Vuex定义中所说的用相应的规则来让数据发生变化的具体实现
  • Action
    Mutation中定义的操作只能执行同步操作,Vuex中的异步操作在Action中进行,Action最终通过调用Mutation的操作来更新数据
  • Module
    StoreState之间的一层,便于大型项目管理,Store包含多个ModuleModule包含StateMutationAction

使用Vuex

安装Vuex

当我们想在一个vue项目中使用Vuex的话,首先需要先安装Vuex依赖。可以直接利用npm安装:

npm install vuex --save
//加上 --save 表示的是这个依赖在部署之后仍需要
Store、State和Mutation

在项目的源代码文件夹下(如src文件夹)新建一个store文件夹(叫别的名字也行)。store文件夹下新建一个store.js文件,用来存放Vuex实例。
假设我们的项目是一个阅读类的应用,需要维护一个叫做books的表示书籍对象的数组。应用可以做的操作是添加新书籍,则其最基本的store.js文件的内容如下:

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

const store = new Vuex.Store({
  state: {
    books: [],
  },
  mutations: {
    //所有mutations中的方法的第一个参数一定是state变量,用来进行对state中的状态的操作
    //第二个参数是可选参数,用于调用该 mutations 方法的时候传参
    initBooks (state, books) {
      state.books = books
    },
    addNewBook (state, book) {
      state.books.unshift(book)
    }
  }
})

export default store

使用的场景如下:
在需要使用store中的共有变量的组件中(如newbook.vue),先import store对象。获得store对象的引用,以便后续对其进行操作。

import store from '@/store/store'

然后在组件的methods中使用store实例的commit属性来进行一个对Storestate中的数据的操作(如增加、减少等):

methods: {
  onSubmit: function() {
    this.form.id = index++
    store.commit('addNewBook', this.form)
  }
}

只能通过提交commit的方式来更新数据会让人觉得束手束脚甚至多此一举。因为直接在组件中修改数据不是更方便吗?提交commit的方式只不过把应该写在组件中的操作代码写到了store文件中?那为什么Vuex还这样规定呢?因为Vuex的目的是“以相应的规则保证状态以一种可预测的方式发生变化”。组件只能通过提交Mutation中规定的方法对数据进行操作,保证了状态变化的可预测性。如果说允许组件原地对state中的状态进行更改操作,那状态的变化方式难免千奇百怪,并且难以跟踪数据到底在哪里发生了变化、发生了什么变化。

这样子,本来应该在newbook.vue中维护的books数组(或者是在其父组件中维护的books数组,这里只是举个例子)被放在Store中维护。并且每一个需要books数组的组件都要从Store中获取,每一次对books数组进行操作都要提交commitmutations中的方法相当于提供了一个更改Store中状态的接口。有了Vuex,我们终于可以摆脱一层层传递信息的麻烦和混乱。

前面说到了storemutationstate的基本使用场景,下面说一下剩下的。

Action

mutation中只能进行同步操作,那如果需要用到异步操作该怎么办呢?如上面的例子,阅读类的应用,假设应用启动的时候需要从服务器获取到在数据库中的所有书籍的信息,这个操作相对于其他基本操作来说会比较耗时,为了保证应用的流畅性,应该选择异步获取书籍数据。因为这个操作要更新books数组,需要用提交的方式通知Vuex更新数据,但是mutation只能进行同步操作。Vuex提供了进行异步操作的方式,即Action。在Action中定义操作的形式与在mutation中差不多,区别是Action中的操作都是异步操作,且它不能直接修改state中的状态,对于状态的修改最终还是要通过提交commit的方式修改。增加了Action之后的store.js文件如下:

import Vue from 'vue'
import Vuex from 'vuex'
// 使用axios作为http服务模块
import axios from 'axios'
Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    books: [],
  },
  mutations: {
    //所有mutations中的方法的第一个参数一定是state变量,用来进行对state中的状态的操作
    //第二个参数是可选参数,用于调用该 mutations 方法的时候传参
    initBooks (state, books) {
      state.books = books
    },
    addNewBook (state, book) {
      state.books.unshift(book)
    }
  },
  actions: {
    //{ commit }是参数解构的写法,详见ES6语法
    fetchData ({ commit }) {
      axios.get('http://127.0.0.1:8081/api/books')
          .then(function (response) {            
            commit('initBooks', response.data)
          })
          .catch(function (error) {
            console.log(error)
          })
    },
    //book是调用该操作时传过来的附加参数
    addItem ({ commit }, book) {
      return axios.post('http://127.0.0.1:8081/api/add', book)
              .then(function (response) {
                if (!response || response.status !== 200 || response.data.err) {
                  return true
                } else {
                  commit('addNewBook', book)
                  return false
                }});
    }
  }
})

export default store

使用的场景如下:
在组件中调用Action中的方法用的不是提交commit的方法,而是使用“分发”:通过 store.dispatch 方法触发:

if (store.state.books.length === 0) {
  store.dispatch('fetchData')
}

当然还可以在分发的时候传递参数:

methods: {
  onSubmit: function() {
    this.form.id = index++
    this.form.bookname = "《" + this.form.bookname + "》"
    store.dispatch('addItem', this.form)
        .then((err) => {
          if (!err) {
            this.$message({
              message: '添加成功!',
              type: 'success'
            })
          } else {
            this.$message.error('添加失败!')
          }
        })
  }
}

还可以通过对象的方法进行分发:

store.dispatch({
  type: 'addItem',
  newbook: this.form
}).then((err) => {
        if (!err) {
          this.$message({
            message: '添加成功!',
            type: 'success'
          })
        } else {
          this.$message.error('添加失败!')
        }
      })
Getter

getter可以看成是store的计算属性,getter的值会根据他的依赖被缓存起来,当其依赖发生变化的时候其值才会被重新计算:

const store = new Vuex.Store({
  state: {
    books: [],
  },
  getters: {
    doneBooks: state => {
      return state.books.filter(book => book.done)
    }
  }
})

在组件中的使用:
Getter 会暴露为 store.getters 对象,通过属性的形式访问这些值:

store.getters.doneBooks

最后,什么情况下应该使用 Vuex

Vuex的官方文档提醒,Vuex虽然可以帮助我们管理共享状态,但也附带了更多的概念和框架。简单的说就是如果你的项目较小,其状态没有复杂到需要使用Vuex来管理的时候,就不必使用Vuex。

Logo

前往低代码交流专区

更多推荐