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

Vuex基于“单向数据流”理念:

Vuex 单向数据流示意图

Vuex的工作模式如下:

Vuex 工作模式

Vuex应用的核心就是Store(仓库)。Store基本上就是一个容器,它包含着应用中大部分的状态(State)。

使用Vuex也可以实现任意组件间通信。但是官方在文档中提到,如果要构建一个简单的网页应用,使用Vuex可能是繁琐冗余的。

如果应用够简单,最好不要使用Vuex。一个简单的Store模式就足够了。

如果需要构建一个中大型单页应用,就需要考虑如何更好地在组件外部管理状态,Vuex将会成为自然而然的选择。


安装 Vuex

在Node.js环境下,可以运行下方命令安装Vuex:

npm i vuex

但是需要注意的是,目前不指定Vuex版本的情况下,安装的是vuex@4,而vuex@4适用于Vue3,适用于Vue2的Vuex版本是vuex@3

# 安装适用于 Vue2 的 Vuex 3
npm i vuex@3

安装好后,就可以在项目中导入Vuex:

import Vuex from 'vuex'

导入Vuex后,需要在Vue中开启Vuex插件:

Vue.use(Vuex)

配置 Vuex

Vuex的基本配置如下(Vuex配置文件可以是项目根目录下的vuex/store.jsstore/index.js):

import Vue from 'vue'
import Vuex from 'vuex' // 引入 Vuex

Vue.use(Vuex) // 使用 Vuex

// Actions 用于响应组件中的动作
const actions = {
  /* ... */
}

// Mutations 用于操作数据
const mutations = {
  /* ... */
}

// State 用于存储数据
const state = {
  /* ... */
}

// Getters 用于将 State 中的数据进行加工
const getters = {
  /* ... */
}

// 创建并导出 Store
export default new Vuex.Store({
  actions,
  mutations,
  state,
  getters,
})

创建好Store之后,需要将其导入到main.js中,并配置在Vue实例上:

import Vue from 'vue'
import store from './store'

/* ... */

new Vue({
  /* ... */
  store,
  /* ... */
}).$mount('#app')

配置好后,当前Vue实例,以及Vue实例下的所有组件,都能通过this.$store访问Store的API。


使用 Store

Store中,常用的API如下:

  • $store.dispatch():将数据分发给对应的Action。
  • $store.commit():将数据(载荷)提交给对应的Mutation.

Store中,常用的数据对象如下:

  • $store.state:访问State中的状态数据(可以认为是一个全局的data)。
  • $store.getters:对State中的状态数据进行相应处理,并获取对应的处理结果(可以认为是一个全局的,没有Setter的computed)。

dispatch

$store.dispatch(actionName, data)

  • actionName:指定一个Action的名称,String类型。对应store配置中actions配置对象下的一个相同名称的方法actionName()
  • data:分发给actionName()的数据,任意类型。actionName()使用第2个形参value接收。

每一个Action,在actions中都有一个与之对应的方法。在组件实例中,使用$store.dispatch()来将数据分发给对应的Action方法处理。每个Action都有一个唯一的actionName

通常,actionName是以小驼峰规则命名。

this.$store.dispatch('demoAction', value)

demoAction对应的Action:

const actions = {
  /* ... */
  demoAction(context, value) {
    /* demoAction的处理逻辑... */
  },
  /* ... */
}

context是一个上下文对象,每一个Action都会接收到这个参数。context封装了$store中的一些方法。通过context,可以将当前处理转发给其它Action,或是将处理提交到某个Mutation。

context中封装了如下方法和对象:

  • context.dispatch()
  • context.commit()
  • context.state
  • context.getters

这些方法和对象的用法与$store中的相同。

context可以看作是一个小型的$store,它封装了$store中常用的方法和对象。使用者可以根据处理逻辑(上下文)调用context中的方法,所以才命名为context

在Action中,应该将context.state视为只读的。因为Action的职责不是对状态进行修改,并且Vue.js devtools(Vue.js开发者工具)捕获不到Action对context.state的修改(尽管这些修改可能会生效),所以应该尽量避免在Action中对context.state的修改。

通常情况下,Action是用来对数据进行一些校验或简单的处理,然后将数据转派给下一个Action或提交给某个Mutation。

$store.dispatch()在调用时可以仅传递一个参数,也就是actionName。例如:

this.$store.dispatch('plusOne')

commit

$store.commit(mutationName, payload)

  • mutationName:指定一个Mutation的名称(事件类型),String类型。对应store配置中mutations配置对象下的一个相同名称的方法mutationName()
  • payload:提交给mutationName()的数据(载荷),任意类型。mutationName()使用第2个形参value接收。

通常,mutationName是以全大写+下划线规则命名。

$store.commit()的用法和$store.dispatch()十分相似,不同的是$store.commit()是将载荷提交给某个Mutation。

this.$store.commit('demoMutation', payload)

demoMutation对应的Mutation:

const mutations = {
  /* ... */
  demoMutation(state, payload) {
    /* demoMutation的处理逻辑... */
  },
  /* ... */
}

state即对应Store配置中的state配置对象(相当于$store.state)。每一个Mutation都会接收到这个state对象。通过state对象,Mutation可以获取State中的状态数据,并对状态数据进行编辑。

与Action不同,Mutation通常是作为状态数据处理的角色存在。Mutation可以直接修改State中的数据;而Action通常不修改State中的数据,Action一般只是读取State中的数据。

Action不用作修改数据还有一个原因,如果在Action中修改State中的数据,Vue.js devtools并不能捕获到这些修改。即使在Action对State的修改能生效,也不建议在Action中对State进行修改,在Actions中,应该将State(context.$state)视为只读的。

一般情况下(官方给的示意图中),Store对数据的处理流程是:$store.dispatch $\Rightarrow$ $store.commit。实际上,对一些简单的操作,不需要对传递的数据进行校验或处理的情况下,可以在组件实例中直接调用$store.commit将数据提交给Mutation处理。

$store.commit()在调用时可以仅传递一个参数,也就是mutationName。例如:

this.$store.commit('plusOne')

state

在组件中读取State中的数据,可以通过this.$store.state.dataName的形式读取(dataName指要读取的数据)。需要注意的是,在组件实例中不要修改State中的数据。也就是说,在组件实例中应该将this.$store.state视为只读。

例如在Store的state配置项中定义一个状态数据:

const state = {
  siteTitle: 0, // 网站的标题
}

在组件实例中获取state中的数据:

this.$store.state.siteTitle

在组件实例中推荐使用computed来获取state中的数据:

export default {
  /* ... */
  computed: {
    siteTitle() {
      return this.$store.state.siteTitle
    },
  },
  /* ... */
}

也可以通过computed的Setter来修改state中的数据:

export default {
  /* ... */
  computed: {
    /* ... */
    siteTitle: {
      get() {
        return this.$store.state.siteTitle
      },
      set(payload) {
        this.$store.commit('SET_SITE_TITLE', payload)
      }
    },
    /* ... */
  },
  /* ... */
}
const mutations = {
  /* ... */
  SET_SITE_TITLE(state, payload) {
    state.siteTitle = payload
  }
  /* ... */
}

接着,就可以在组件实例中,像使用一般的数据一样,对siteTitle进行各种操作。


getters

$store.getters就像是一个全局的computed,可以在获取State中的数据之前,对数据进行一些处理。但与computed不同的是,getters只能用于获取,不能用于修改数据。

在Store的getters配置中,每个getter以函数的形式被定义,且每个getter接收一个state参数。这个state参数用于获取State中的数据。

每个getter都有一个唯一的名称,并且就像computed一样,在Vue实例中使用this.$store.getterName来获取相应的gettergetterNamegetter定义时的函数名称相同。getter同样也是使用返回值来确定值。

const getters = {
  /* ... */
  demoGetter(state) {
    /* demoGetter 的处理逻辑... */
    return /* demoGetter 的值 */
  },
  /* ... */
}

例如要获取一个转为大写的siteTitle

const getters = {
  siteTitleUpperCase(state) {
    return state.siteTitle.toUpperCase()
  },
}

在组件实例中获取该值:

this.$store.gettets.siteTitleUpperCase

同样可以使用computed来读取该值:

siteTitleUpperCase() {
  return this.$store.gettets.siteTitleUpperCase
}

Vuex 映射

在组件实例中,使用Store中的stategetter时,配合computed可以使代码更加精简。而Vuex原型正好提供了一些辅助的API,可以在组件实例中快速地为stategetters中的数据创建对应的computed

  • mapState()
  • mapGetters()
  • mapMutations()
  • mapActions()

在使用这些辅助函数之前,需要先引入。这些辅助函数的引入方法都是相同的,下面进行例举了辅助函数的引入方式:

import { mapState } from 'vuex' // 引入 mapState
import { mapState, mapGetters } from 'vuex' // 引入 mapState 和 mapGetters

mapState

mapState()可以将State中的数据映射到组件实例中的computed中。

computed: mapState({
  computedAttr1: 'stateAttr1',
  computedAttr2: 'stateAttr2',
  computedAttr3: 'stateAttr3',
  /* ... */
}),

或者使用对象扩展运算符:

computed: {
  ...mapState({
    computedAttr1: 'stateAttr1',
    computedAttr2: 'stateAttr2',
    computedAttr3: 'stateAttr3',
    /* ... */
  }),
  /* ... */
}

上方所示的两个mapState()相当于在computed中进行如下定义:

computed: {
  computedAttr1() {
    return this.$store.state.stateAttr1
  },
  computedAttr2() {
    return this.$store.state.stateAttr2
  },
  computedAttr3() {
    return this.$store.state.stateAttr3
  },
  /* ... */
}

也就是说,mapState()返回的是一个类似于computed配置对象的对象。这个对象中的每个属性都是函数类型,相当于一个个设置了Setter的computed属性。

使用mapState()生成的computed被称为vuex bindings(可以在Vue开发者工具中查看)。

mapState()可以传入两种类型的参数:

  • 一种就是如上所示的Object类型参数。

    传入的对象中,其每个属性的key是作为实例中的computed属性名称,每个属性的valueString类型的形式指定了State中某个属性的名称。

    该方式通过传入对象中的每个属性的value指定State中的属性,然后以每个属性的key作为它们的computed属性来生成vuex bindings

  • 另一种方式就是传入字符串数组类型的参数。

    mapState([
      'stateAttr1',
      'stateAttr2',
      'stateAttr3',
      /* ... */
    ]),
    

    这种方式通过字符串数组指定一系列要作为computedvuex binding)属性使用的State属性。这种方式可以直接使用对应State属性的名称来使用这些vuex binding属性。

    上方mapState()相当于在computed中配置了以下内容:

    stateAttr1() {
      return this.$store.state.stateAttr1
    },
    stateAttr2() {
      return this.$store.state.stateAttr2
    },
    stateAttr3() {
      return this.$store.state.stateAttr3
    },
    

mapGetters

mapGetters()可以将getters中的数据映射到组件实例中的computed中。生成的属性同样被称为vuex bindings

mapGetters()的用法和mapState()几乎相同,它们都可以传入两种类型的参数,并且返回值也是一个属性都为函数类型的对象。

mapGetters({
  gettersAttr1: 'gettersAttr1',
  gettersAttr2: 'gettersAttr2',
  gettersAttr3: 'gettersAttr3',
  /* ... */
})
mapGetters([
  'gettersAttr1',
  'gettersAttr2',
  'gettersAttr3',
  /* ... */
])

上述两种使用方式,都是相当于在computed进行如下配置:

computed: {
  gettersAttr1() {
    return this.$store.getters.gettersAttr1
  },
  gettersAttr2() {
    return this.$store.getters.gettersAttr2
  },
  gettersAttr3() {
    return this.$store.getters.gettersAttr3
  },
  /* ... */
}

mapMutations

mapMutations()用于在methods中快速生成提交(commit)对应Mutations事件的方法。mapMutations()同样拥有两种形参类型。

mapMutations()作用于methods,但是用法基本上与mapState()mapGetters()相同。并且mapMutations()传入的也是一个属性为function类型的对象。

methods: mapMutations({
  increment: 'INCREMENT',
  decrement: 'DECREMENT',
}),

相当于在methods中进行如下配置:

methods: {
  increment(value) {
    this.$store.commit('INCREMENT', value)
  },
  decrement(value) {
    this.$store.commit('DECREMENT', value)
  },
}

所以在调用或者绑定事件时,需要以increment()increment(value)的形式。否则,以increment(如@click="increment")的形式绑定事件,传入的value参数就是当前绑定事件的对象(可能会造成数据错误)。

mapMutations()数组参数写法如下:

methods: mapMutations([
  'INCREMENT',
  'DECREMENT',
]),

相当于:

methods: {
  INCREMENT(value) {
    this.$store.commit('INCREMENT', value)
  },
  DECREMENT(value) {
    this.$store.commit('DECREMENT', value)
  },
}

mapActions

mapActions()用于在methods中快速生成分发(dispatch)对应Action的方法。

mapMutations()的用法基本上与mapMutations()相同。

methods: mapActions({
  increment: 'increment',
  decrement'decrement',
}),
methods: mapActions([
  'increment',
  'decrement',
]),

上述两种方式相当于:

methods: {
  increment(value) {
    this.$store.dispatch('increment', value)
  },
  decrement(value) {
    this.$store.dispatch('decrement', value)
  },
}

同样使用increment()increment(value)的形式调用或绑定事件。