【Vue学习笔记_17】Vuex状态管理

【Vue学习笔记_17】Vuex状态管理

配套可执行代码示例 => GitHub

Vuex初识

Vuex:一个专为Vuejs应用程序开发的状态管理模式,应用于多界面状态管理。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

状态管理:可以简单的理解为把需要多个组件共享的变量存储在一个对象里面,然后将这个对象放在顶层的Vue实例中,让其他组件可以使用。

自己封装一个对象也可以实现状态管理,但是要想所有的属性都做到响应式就比较麻烦了。Vuex就是为了提供这样一个在多个组件之间共享状态的插件。

Vuex基本思想:全局单例模式。将需要共享的状态抽取出来,统一进行管理。之后每个视图按照规定好的格式进行访问和修改等操作。而Vuex就是提供统一管理的工具。

有哪些状态需要我们在多个组件之间共享呢?

  • 用户的登录状态(token)、用户名、头像、地理位置等
  • 收藏、购物车中的商品

Vuex安装npm install vuex --save

Vuex核心概念:State,Getters,Mutations,Actions,Modules

【Vue学习笔记_17】Vuex状态管理

Vuex基本使用

第一步,在store/index.js文件中创建并导出Vuex.Store实例

import Vuex from 'vuex'

//1.Vue.use(插件):安装Vuex插件
Vue.use(Vuex)

//2.创建Vuex.Store实例
const store = new Vuex.Store({
  state: {},
  getters: {},
  mutations: {},
  actions: {},
  modules: {}
})

//3.导出实例
export default store

第二步,在main.js文件的Vue实例中挂载创建的store实例

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

new Vue({
  el: '#app',
  store,
  render: h => h(App)
})

第三步,项目中任何一个组件都可以通过$store访问到该实例

<h2>{{$store.state.counter}}</h2>

Vuex-State

如果状态信息保存到多个Store对象中,那么之后的管理和维护都会变得特别困难,所以Vuex使用了单一状态树(Single Source of Truth,单一数据源)来管理应用层级的全部状态。也就是说,一个项目里有一个Store就可以了。

//store/index.js
state: {
  counter: 0,
  students: [
    {name: 'Perry', age: 19},
    {name: 'Bob', age: 20},
    {name: 'Alice', age: 21}
  ]
}

Vuex-Getters

类似组件的计算属性。有时候需要从Store中获取一些处理后的State,就可以使用Getters。

  • getters方法的第一个参数是state,组件使用的时候不用传,默认是$store.state,即当前store实例的state
  • getters方法的第二个参数是getters,组件使用的时候也不用传,默认是$store.getters,即当前store实例的getters
  • getters方法如果需要自定义参数,可以在方法里再返回一个有参函数,组件使用的时候就相当于调用了这个函数,就可以传递相应的参数了
//store/index.js
getters: {
  powerCounter(state) {
    return state.counter * state.counter
  },
  more20stu(state) {
    return state.students.filter(s => s.age>20)
  },
  more20stuLength(state, getters) {
    return getters.more20stu.length
  },
  moreAgeStu(state) {
    return age => {
      return state.students.filter(s => s.age>age)
    }
  }
}

在组件中使用:

<h2>{{$store.getters.powerCounter}}</h2>
<h2>{{$store.getters.more20stu}}</h2>
<h2>{{$store.getters.more20stuLength}}</h2>
<h2>{{$store.getters.moreAgeStu(18)}}</h2>

Vuex-Mutations

Devtool是Vue开发者工具,如果Vuex的State是通过提交Mutation修改的,那么Devtool可以跟踪到State的变化过程;如果直接操作State,则跟踪不到。所以官方指定Vuex的Store状态的更新唯一方式是提交Mutations。

Mutations主要包括两部分:

  • 事件类型(type),也就是提交和定义的回调参数的名称
  • 回调函数(handler),第一个参数是state
//store/index.js
mutations: {
  increment(state) {
    state.counter++
  }
}

在组件中使用:

methods: {
  add() {
    this.$store.commit('increment')
  }
}

Mutations传递参数

在通过Mutations更新数据的时候,有时候希望携带一些额外的参数,这些参数被称为Mutations的载荷(Payload)。如果需要传递多个参数,通常会放到一个对象中传递,也就是Payload是一个对象,然后再从对象中取出相关信息。

//store/index.js
mutations: {
  incrementCount(state, count) {
    state.counter += count
  },
  addStu(state, stu) {
    state.students.push(stu)
  }
}

在组件中使用:

methods: {
  addCount(count) {
    this.$store.commit('incrementCount', count)
  },
  addStu() {
    const stu = {name: 'Mary', age: 23}
    this.$store.commit('addStu', stu)
  }
}

总结mutations方法的参数:

  • 第一个参数是state,组件使用的时候不用传,默认是$store.state,即当前store实例的state
  • 第二个参数是payload,组件通过commit提交事件时携带的信息,可选参数

Mutations提交风格

上面是一种普通的提交风格,Vue还提供了另外一种风格:它是一个包含事件类型type属性的对象,mutations中的处理方式是将整个commit的对象作为payload。

//store/index.js
mutations: {
  incrementCountSpec(state, payload) {
    state.counter += payload.count
  }
}

在组件中使用:

methods: {
  addCount(count) {
    this.$store.commit({
      type: 'incrementCountSpec',
      count
    })
  }
}

Mutations响应规则

Vuex的state是响应式的:state中定义的属性会被加入到响应式系统中,而响应式系统会监听属性的变化,当属性发生变化时,会通知所有组件中用到该属性的地方,让界面更新。

更多关于Vue响应式原理,可以看这篇笔记:Vue响应式原理简述

但这要求我们必须遵守一些Vuex的相应规则:只有在state中初始化好的属性是响应式的;当给state中的对象添加新属性或删除属性时,需要使用Vue.set()或Vue.delete(),否则不是响应式的。

Vue.set(state.info, 'address', 'Nanjing')
Vue.delete(state.info, 'height')

Mutations类型常量

在mutations中,我们定义了很多事件类型,也就是方法名称。当方法过多时,需要花费大量精力去记住这些方法,还可能会出现写错的情况。因此,最好将事件类型定义成常量。

在store目录下创建mutations-types.js文件,专门用于导出常量事件类型:

//store/mutations-types.js
export const INCREMENT = 'increment'

在store/index.js中使用:

//store/index.js
import {
  INCREMENT
} from './mutations-types'

mutations: {
  [INCREMENT](state) {
    state.counter++
  }
}

在组件中使用:

import {
  INCREMENT
} from './store/mutations-types'

methods: {
  add() {
    this.$store.commit(INCREMENT)
  }
}

Vuex-Actions

Vuex要求Mutations中的方法必须是同步的,devtools可以帮助我们捕捉mutations的快照。但如果是异步操作,devtools将不能很好地追踪这个操作的state变化。

但在某些情况下,我们确实希望在Vuex中进行一些异步操作,比如网络请求。Actions类似于Mutations,是用来代替Mutations进行异步操作的 。

actions方法的参数:

  • 第一个参数是context,组件使用的时候不用传,默认是$store,即当前store实例
  • 第二个参数是payload,组件通过dispatch提交事件时携带的信息,可选参数
//store/index.js
actions: {
  aUpdateInfo(context, payload) {
    return new Promise(((resolve, reject) => {
      setTimeout(() => {
        context.commit('updateInfo')
        console.log(payload)
        resolve('回调成功的参数')
      }, 1000)
    }))
  }
}

在组件中使用:

updateInfo() {
  this.$store
    .dispatch('aUpdateInfo', '携带信息')
    .then(res => {
      console.log('成功');
      console.log(res);
    })
}

注:this.$store.dispatch这部分相当于调用 aUpdateInfo方法返回的 Promise,所以后面接 then。

更多关于Promise对象以及其他ES6常用语法,可以看这篇笔记:ES6常用语法总结

Vuex-Modules

Vuex使用单一状态树,意味着所有状态都会交给一个state来管理,当应用变得非常复杂时,state就可能变得相当臃肿。为了解决这个问题,Vuex允许我们将store按模块划分,每个模块拥有自己的state、mutations、actions、getters等。

//store/index.js
const moduleA = {
  state: {
    name: 'A'
  },
  mutations: {
    updateName(state, payload) {
      state.name = payload
    }
  },
  actions: {
    //context:包括本级模块的commit、getters、rootState等
    aUpdateName(context) {
      setTimeout(() => {
        context.commit('updateName', 'zhangsan')
      }, 1000)
    }
  },
  getters: {
    fullname(state) {
      return state.name + '111'
    },
    //getters:取本级模块的getters、rootState:取上级模块的state
    fullname2(state, getters, rootState) {
      return getters.fullname + rootState.counter
    },
  }
}
const moduleB = {
  ...
}
const store = new Vuex.Store({
  state: {
    counter: 0
  },
  modules: {
    a: moduleA,
    b: moduleB
  }
})

在组件中使用:

<template>
  <h2>{{$store.state.a.name}}</h2>
  <h2>{{$store.getters.fullname}}</h2>
  <h2>{{$store.getters.fullname2}}</h2>
  <button @click="asyncUpdateName">异步更新name</button>
  <button @click="updateName">更新name</button>
</template>
<script>
  updateName() {
    this.$store.commit('updateName', 'frog')
  },
  asyncUpdateName() {
    this.$store.dispatch('aUpdateName')
  }
</script>

注:在组件中使用 $store.state.模块名.属性名获取模块中的属性,而使用 getters、commit、dispatch时不用注明是哪个模块的属性或方法。

Vuex-store目录结构

当getters、mutations、actions内容很多的时候,可以把每部分单独提取成一个文件,重构store目录:
【Vue学习笔记_17】Vuex状态管理

//store/index.js
import moduleA from './modules/moduleA'
import moduleB from './modules/moduleB'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'

const state = {
  ...
}

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

export default store
//store/mutations.js
export default {
  ...
}
//store/modules/moduleA.js
export default {
  state: {...},
  mutations: {...},
  actions: {...},
  getters: {...}
}
上一篇:vuex数据状态的持久化


下一篇:vue学习---Vuex