vue|Vuex的作用、使用、核心概念(State、Mutations、Getters、Actions、Modules)、文件抽离

vue入门–基础命令+axios+案例练习 vue入门–vue常用属性、生命周期、计算属性、过滤器、组件、虚拟DOM、数组的响应式方法、页面闪烁、ES6简单语法增强 vue入门–js高阶函数(箭头函数)、v-model数据绑定、组件化、父子组件通信及访问 vue入门–插槽(具名、匿名、作用域插槽)+ES6模块化导入导出+webpack的使用(基本使用+配置使用+如何一步步演化成cli脚手架)+webpack插件使用(搭建本地服务器、配置文件分离) vue-cli脚手架2版本及3+版本安装、目录解析、only和compiler的区别、3+版本如何改配置、箭头函数及this的指向 vue-router基本使用、路由传参、懒加载、嵌套路由、导航守卫、keep-alive Promise基本使用、三种状态、链式调用及简写、all方法 Vuex的作用、使用、核心概念(State、Mutations、Getters、Actions、Modules)、文件抽离 Vuex作用 官方解释:

Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并且以相应的规则保证状态以一种可预测的方式发生变化。
Vuex也集成到Vue的官方调试工具,提供了诸如零配置的time-travel、状态快照导入导出等高级调试功能。
那么状态管理、集中式存储管理到底是什么呢?
其实,我们可以简单将其看做是多个组件共享的变量全都存放到一个对象里面,然后将这个对象放入到顶层的Vue实例中,让其他组件可以使用。而且Vuex还是响应式的状态管理.
那么先来了解下,假如在要在Vue中放置一个全局对象,能做到吗?当然能,但是这个对象不是一个响应式的对象。
假如已经导入了Vue相关的依赖
const obj = { name: "wlh", age: 21 } Vue.prototype.obj = obj // 在Vue中放置一个全局对象 Vue.component('aaa', { // 由于其他组件都是继承自Vue的,都能拿到这个对象,但是这个对象不是响应式的 this.obj.name this.obj.age })

那么Vuex就是为了解决这样不是全局响应式变量的问题存在的。
管理哪些状态 最常见的就是用户登录后的一些信息如:用户名称、头像、token等全局性的一些变量,还比如商品的收藏,购物车中的商品等等,而且还是实现的响应式的。
像常见的父子组件通信类的一些变量就没有必要去放到Vuex中进行管理了,直接父子通信方式传递数据就行了。
看一幅图:
vue|Vuex的作用、使用、核心概念(State、Mutations、Getters、Actions、Modules)、文件抽离
文章图片

其中State其实就是指我们的状态(变量数据,如在vue组件中定义的数据),然后会放到View,也就是视图上面去展示出来。再通过某个事件我们可以控制State(数据的状态),不停循环。
单页面状态管理
代码说明:
> export default { name: 'App', data() { return { counter: 0 } } }

上述代码块中, data()中定义的变量就是我们的State状态,然后页面上将 counter变量渲染了出来,当我点击+、-按钮时触发事件然后更改data()中定义的变量,也就是更改State状态,形成一个循环圈。
Vuex使用 多页面状态管理 假如在App.vue组件中用到了另一个组件,想要两个组件都能共用一个变量。当然这种简单的场景使用父子组件间的Props传值完全可以实现,这里只是为了说明如何进行全局性的状态管理。
安装vuex
npm install vuex --save

vuex是一个插件,我们需要使用 Vue.use来使用插件。
使用且访问
目录下新建一个store的文件夹(约定俗成的命名),创建一个index.js文件
import Vue from 'vue' import Vuex from 'vuex'// 1.安装插件 Vue.use(Vuex) // 2.创建对象 const store = new Vuex.Store({ // 保存状态 state:{ counter: 1000 } })// 3.导出store对象 export default store

在main.js中,导入store,并且给vue实例使用
import Vue from 'vue' import App from './App' import store from './store'Vue.config.productionTip = false/* eslint-disable no-new */ new Vue({ el: '#app', store, render: h => h(App) })

放置到vuex的state对象中的变量,在其他组件中都可以使用 $store.state.xxx的方式调用它们
比如上面我们在store的state中存放了一个counter为1000的变量,然后我们在一个组件中使用它们:
两种都可以使用
> export default { name: 'HelloVuex', data() { return { // 这种方式有一个坏处:因为data()是每次vue组件实例化完后返回的,这个counter如果不进行维护,那么只会在实例化组件的时候返回一次,后面如果 store.state.counter变化了,这里是不能及时响应的。 counter: this.$store.state.counter } } } >

npm run dev跑起来项目,如果没有跑起来报错了,那么大多都是因为版本的问题:
如果你的vue版本是 2.X ,将vuex升到 3.X.X 就能够解决,使用命令方式安装或者直接在package.json中改版本然后 npm install的方式都可以
npm install --save vuex@3.6.2

如果你的vue版本是 3.X ,将vuex升到 4.X.X 就能够解决
npm install --save vuex@4.0.0

页面上正常显示我们在vuex的state中定义的状态:
vue|Vuex的作用、使用、核心概念(State、Mutations、Getters、Actions、Modules)、文件抽离
文章图片

修改
访问是没问题了,那么如果我要修改vuex中state中的变量时该怎么做呢?
可能我们都理想当然这样用:
App.vue
> import HelloVuex from './components/HelloVuex'export default { name: 'App', components: { HelloVuex } }

HelloVuex.vue
> export default { name: 'HelloVuex', data() { return { counter: this.$store.state.counter } } }

vue|Vuex的作用、使用、核心概念(State、Mutations、Getters、Actions、Modules)、文件抽离
文章图片

在HelloVuex中第二个counter是调用的data()中的,然后data()函数中的counter只在实例化时返回了一次,后面并没有维护,那它始终都是store.state.counter的初始值。
按照vuex中定义好的规范,我们在修改vuex中的state时不能随意更改,比如: $store.state.counter++,这样子是不符合vuex中的规范的。我们需要在触发vue组件中的事件后,commit到Mutations中进行修改。后面我们要使用一个浏览器插件 DevTools,为了能够让我们使用这个插件更好地进行跟踪是谁修改了 state的状态,方便差错及快速定位,修改state时要在Mutations中执行。
我们先把DevTools安装到谷歌浏览器上面。
大多数人的谷歌浏览器应该没办法打开谷歌商店的,可以去网上下载一下。极简插件下载,下载完解压一下然后直接将解压完的文件拖入到谷歌的扩展程序中。
整完后重启一下浏览器,再次打开vue程序,选择好vue插件的页面。
vue|Vuex的作用、使用、核心概念(State、Mutations、Getters、Actions、Modules)、文件抽离
文章图片

那么开始定义真正对store中的state操作的部分,也就是 Mutations中的定义:
mutations中定义的方法中,参数默认会传递一个 state变量,这个state就是store中的state对象。
const store = new Vuex.Store({ state:{ counter: 1000 }, mutations:{ increment(state){ // state是固定的,要写 state.counter++; }, decrement(state){ state.counter--; } } })

我们在组件中要对vuex中的state修改时,就需要去调用上面的 mutations中定义的规定好的方法来执行。
>export default { name: 'App', methods: { add(){ this.$store.commit('increment') }, minus(){ this.$store.commit('decrement') } } }

调用 $store.commit方法,传入mutations中相应的方法名称,直接调用方法来操作store中的state.
Vuex核心概念
  • State
  • Getters
  • Mutation
  • Action
  • Module
vue|Vuex的作用、使用、核心概念(State、Mutations、Getters、Actions、Modules)、文件抽离
文章图片

此图中说明了vue组件在访问和修改vuex中的state时的完整流程,其中Actions走不走都可以,但是一定要牢记:修改vuex中state的状态时,一定是经过Mutations的
State单一状态树 Single Source of Truth : 单一数据源。只创建一个Vuex实例对象,任何访问vuex中state的状态时,都需要经过这定义的唯一一个vuex实例对象(单一的数据源)。便于我们管理和维护。
Getters 类似vue中的计算属性,计算缓存(一个计算属性被获取到后,会产生一个缓存数据)和及时更新数据,只不过处于不同的对象中。
有一场景:state中有定义一个counter的属性,但是在使用时都想要这个counter的平方值,那么我可以定义一个getters
在定义vuex中的index.js文件中
getters:{ powerCounter(state){ return state.counter * state.counter } }

那么如何调用呢?
$store.getters.powerCounter直接调用即可,这也是一个属性
---------------App.vue counter的平方 {{$store.getters.powerCounter}}

vue|Vuex的作用、使用、核心概念(State、Mutations、Getters、Actions、Modules)、文件抽离
文章图片

getters中的参数传参:
假如state中有一个数组,我们要筛选出>20的数的数据,并且还要得到数量。我们怎么做呢?
const store = new Vuex.Store({ // 保存状态 state:{ arrs:[1,2,30,40,23,25] }, getters:{ getArr(state){ return state.arrs.filter(x => x > 20) }, // 此方法中 state虽然没有用到,但是一定不能省略,否则就直接报错了 // 方法中,state和getters可以命名为其他名称,但是实际上第一个参数就是 指的state,第二个参数就是指的 getters引用,与名称无关 getArrLength(state, getters){ return getters.getArr.length } } })

那么假如我并不确定筛选大于多少的数据怎么办呢?需要外界给传入一个参数
那么,我们可以:定义一个getters,这个getters不返回一个属性,而是返回一个函数,可以让外界调用并且传入参数
getActiveArrLen(state){ return age => { return state.arrs.filter(x => x > age) } }

如何调用?
筛选出 > 30 的数据
{{$store.getters.getActiveArrLen(30)}}

Mutation携带传参。 当需要更改Vuex中store状态时,唯一的修改方式就是:提交 Mutation
Mutation主要包括两部分:
  • 字符串的事件类型
  • 一个回调函数(handler),该回调函数的第一个参数就是state
调用时给传入参数,可以被称之为 是 mutations的 负载(payload)
比如
mutatsions:{ // increment就是事件的类型 // 整个方法体就是一个 回调函数。,而且第一个参数就是 Vuex的 state increment(state){ state.counter++ } }

既然第一个参数默认就是state,那么假如有其他的参数我该如何传给它呢?
第一个是默认的state,后面可以加上我们需要接收的参数即可。
const store = new Vuex.Store({ // 保存状态 state:{ counter: 1000 }, mutations:{ // 这里记得接收参数 count incrementCount(state, count){ state.counter += count } } })

调用时传入参数:
> export default { name: 'App', methods: { addCounter(count){ // 调用 commit到mutations中,记得传入参数 this.$store.commit('incrementCount', count) } } }

载荷(负载)同时也支持传入对象类型的参数:
addCounter(count){ const stu = {id:1, name: "wlh"} this.$store.commit('incrementCount', stu) }

mutation的提交风格
除了通过 代码中的 commit方式进行提交(普通方式)外,Vuex还提供了一种包含type属性的对象方式提交。
addCounter(count){ // 普通提交 this.$store.commit('incrementCount', count)// 特殊提交 this.$store.commit({ type: 'incrementCount', count }) }

上面两种方式都可以进行提交,那么两种方式的提交有什么不同的呢?打印下日志看下
incrementCount(state, count){ console.log(count)}

vue|Vuex的作用、使用、核心概念(State、Mutations、Getters、Actions、Modules)、文件抽离
文章图片

也就是说,特殊方式的提交mutations中接收到的是一个 payload对象,这里写为 payload更易理解
incrementCount(state, payload){ console.log(payload) }

mutations中的类型常量
实际开发中,我们在mutations中定义的方法名称和在vue组件中使用commit提交时的名称很可能不小心写错产生问题,那么我们可以统一管理这些名称。
在store目录中创建一个 mutations-types.js文件,这个文件中定义我们用到的名称:
export const UPDATESTU = 'updateStu' ...

在vue组件中使用:
import {UPDATESTU} from './store/mutations-types' // 导入export default { name: 'App', methods: { update(){ this.$store.commit(UPDATESTU) // 提交时直接使用即可 } } }

那么在vuex的mutations中同样也要使用同一个常量:
import {UPDATESTU} from './mutations-types'const store = new Vuex.Store({ ... mutations:{ // 直接定义即可 [UPDATESTU](state){} }})

mutations同步函数
通常情况下,Vuex要求我们Mutation中的方法必须是同步方法,原因
  • 当我们使用devtools时,devtools可以帮助我们捕捉mutation的快照
  • 但是如果是异步操作,那么devtools将不能很好跟踪这个操作什么时候会完成
数据的响应式原理 Vuex的state中的state是响应式的,当state中的数据发生变化时,Vue组件会自动更新,这就要求我们必须遵守一些Vuex对应的规则:
  • 提前在store中初始化好所需要的属性
  • 当给state中的对象添加新的属性时,使用方式:
    • 方式一:Vue.set(obj, ‘newProp’, 123)
    • 方式二:用新对象给就对象重新赋值
state中定义的变量都会被加入到响应式系统中,响应式系统会监听属性的变化,当属性发生变化时,会通知界面中所有用到该属性的地方,让界面发生刷新。
如果是给state中的对象新增新的属性时,它不是响应式的,那么我们就需要使用到上述的的两种方式来使它自动刷新。
这里使用的vue2,动态新增属性时不能做到响应式,需要进行Vue.set或其他方式。但是在高本版中的vue中不会存在这种情况,需要注意一下
const store = new Vuex.Store({ // 保存状态 state:{ info:{ name:'wlh', age: 21 } }, mutations:{ updateStu(state){ // 动态新增属性,不能做到响应式 //state.info['address'] = 'sdfjkldsfs'// 使用Vue.set,把这个属性增加到 响应式系统中 Vue.set(state.info, 'address', '北京市')} }})

Vue.delete删除属性,能够做到响应式
Vue.delete(state.info, 'age')

Action 我们前面说到不要在mutation中进行异步操作,但是某些情况下,我们确实希望在Vuex中进行一些异步操作,比如网络请求,必然是异步的操作,该怎么处理呢?
Action类似于Mutation,但是是用来代替Mutation进行异步操作的。
先来回忆一下上面的图:
vue|Vuex的作用、使用、核心概念(State、Mutations、Getters、Actions、Modules)、文件抽离
文章图片

因为要异步操作state的状态,那么我们需要增加一个Actions环节,先由vue组件dispatch到Actions中,由Actions去commit到Mutations中,然后Mutations对state进行修改
定义actions:
const store = new Vuex.Store({ mutations:{ updateStu(state){ // 处理 state.xxx = 'xxx' } }, // 定义actions actions:{// 需要用到一个 context上下文,这个context就是我们的 store对象 aUpdateInfo(context){ setTimeout(() => {// 函数回调中, commit到 mutations的方法中,由 mutations中的方法对state进行状态更新 context.commit('updateStu') }, 1000); } }})

既然定义好的 actions中已经 commit到了 mutations中,那么在vue的组件中调用时,不用再去commit到mutations中了,直接dispatch到actions中的方法中即可。
>export default { name: 'App', methods: { update(){ // 调用转发到 指定的actions中 this.$store.dispatch('aUpdateInfo') } } }

与mutations一样,actions中也可以接收一些参数,传入参数时支持普通传入和对象传入。
actions:{ aUpdateInfo(context, payload){ setTimeout(() => { console.log(payload) context.commit(UPDATESTU)}, 1000); } }

那么当actions中的异步方法执行完成后,如何给一个回调呢?
那就需要用到我们之前学过的Promise了,给异步操作做一个包装,使代码的可读性更高。
在actions中当被访问到时,直接给返回一个 Promise对象,然后后续的回调处理交给调用者。
actions:{ aUpdateInfo(context, payload){ return new Promise((resolve, reject) => { setTimeout(() => { console.log(payload) context.commit(UPDATESTU) resolve('1234') }, 1000); }) } }

也就是当我们执行dispatch后actions那里会返回一个Promise对象,然后交个调用者处理回调
methods: { update(){ this.$store.dispatch('aUpdateInfo', 'payload') .then(res => { console.log('方法回调完毕') console.log('参数是' + res) }) } }

Modules modules指的是vuex对象中的模块。当应用变得非常复杂时,store的state可能变得非常臃肿,为了解决这个问题,Vuex允许我们将store分割成模块(module),而每个模块中又有自己的 state、mutations、actions、getters等(简单来讲,就是类似于一种树结构,能够无限套子集)
定义module
// 创建对象 const moduleA = { state: { name: 'hhh' }, mutations:{ updateName(state, payload){ state.name = payload } } }const store = new Vuex.Store({ modules:{ a: moduleA } })

访问state
定义了一个名称为a的模块,那么vue组件中如何使用呢?
{{$store.state.a.name}}

实际上,我们定义的模块是有一个子模块,访问时需要从root级父模块中的state中寻找。
访问mutations
那么访问子模块中的 mutations,该怎么访问呢?
>export default { name: 'App', methods: { updateName(){ this.$store.commit('updateName', 'ls') } } }

可以看到,直接commit即可,它会先去root级模块中找,找不到再去子模块中去找。所以:所有模块中的mutations定义的名称不要重复
访问getters
定义一个getters
const moduleA = { state: { name: 'hhh' }, getters:{ fullname(state){ return state.name + '123' } } }

访问其实和之前一样,这些都是全局性的
{{$store.getters.fullname}}

那么不免会遇到,当子module中想要访问root级中的一些state时怎么办?假如root级state中有一个counter属性
getters:{ fullname(state, getters, rootState){ return state.name + '123' + rootState.counter } }

前面说到getters中的参数,第一个是state,第二个getters,那么第三个就是 rootState(随意写的名称),rootState就是root级模块中的state。须知:mutations、getters都是全局性的,无论你是定义在哪个模块中
访问actions
actions与mutations、getters有些异同
const moduleA = { state: { name: 'hhh' }, mutations:{ updateName(state, payload){ state.name = payload } }, actions:{ aUpdateName(context){ console.log(context) setTimeout(() => { // 这里的上下文就不是 store对象了,而是当前的一个子模块对象 context.commit('updateName', 'zs'); }, 1000); } } }

在子模块的actions中,执行commit时,只会提交到自己模块中的mutations中
访问actions时还是直接访问即可,所以再次提醒,无论是mutations、getters、actions中的方法名称都不要重复了。
updateName(){ this.$store.dispatch('aUpdateName') }

我们看一下打印的context信息:
vue|Vuex的作用、使用、核心概念(State、Mutations、Getters、Actions、Modules)、文件抽离
文章图片

是一个对象类型,有getters,rootGetters,rootState,state等属性。
那么对对象结构比较熟悉的兄弟可能就产生了一种想法,既然这个接收的context是一个对象类型,那我能不能直接给他结构成为几个对象呢,当然可以.
// 将context 结构为几个 参数,注意:是按照名称分配的,跟顺序无关 aUpdateName({state, commit, rootState}){setTimeout(() => { commit('updateName', 'zs'); }, 1000); }

需要注意到一点:有些属性root级模块是没有的,比如:rootState,root级模块本身就是root,它已经没有上一个父级了
store的目录组织 上面的所有的代码都是写到了一个index.js文件中,实际项目中我们会进行导入导出模块来将这些进行抽离。让我们的项目结构更加清晰。
vue|Vuex的作用、使用、核心概念(State、Mutations、Getters、Actions、Modules)、文件抽离
文章图片

抽离state root级目录中state可以这样抽离一下:
const state = { name: 'wlh' }const store = new Vuex.Store({ state })

抽离mutations root级目录中mutations可以抽离为文件,然后导入这个文件即可。
import {UPDATESTU} from './mutations-types'export default { [UPDATESTU](state){ state.info.name = "ls" } }

导入使用
import mutations from './mutations'const store = new Vuex.Store({ mutations })

actions和getters同理。
抽离modules 在store目录下新建文件夹modules,比如有一个 cart的module,就创建一个 cart.js
export default { state: { name: 'hhh' }, mutations, getters:{ fullname(state, getters, rootState){ return state.name + '123' + rootState.counter } }, actions:{ aUpdateName(context){ console.log(context) setTimeout(() => { context.commit('updateName', 'zs'); }, 1000); } } }

【vue|Vuex的作用、使用、核心概念(State、Mutations、Getters、Actions、Modules)、文件抽离】在index.js中直接导入使用
import Cart from './modules/cart'const store = new Vuex.Store({ modules:{ Cart } })

vue入门–基础命令+axios+案例练习 vue入门–vue常用属性、生命周期、计算属性、过滤器、组件、虚拟DOM、数组的响应式方法、页面闪烁、ES6简单语法增强 vue入门–js高阶函数(箭头函数)、v-model数据绑定、组件化、父子组件通信及访问 vue入门–插槽(具名、匿名、作用域插槽)+ES6模块化导入导出+webpack的使用(基本使用+配置使用+如何一步步演化成cli脚手架)+webpack插件使用(搭建本地服务器、配置文件分离) vue-cli脚手架2版本及3+版本安装、目录解析、only和compiler的区别、3+版本如何改配置、箭头函数及this的指向 vue-router基本使用、路由传参、懒加载、嵌套路由、导航守卫、keep-alive Promise基本使用、三种状态、链式调用及简写、all方法 Vuex的作用、使用、核心概念(State、Mutations、Getters、Actions、Modules)、文件抽离

    推荐阅读