四. Vuex的核心概念
Vuex的核心概念一共有5个
- State
- Getter
- Mutations
- Action
- Modules
这几个概念: 其中State, Mutations, Action上面都有提高过. 下面重点来看看Getter和Modules. 在看Getter之前先来看一个概念: 单一状态树
4.1 单一状态树
什么是单一状态树呢?
比如我们的个人信息,社保信息存在社保系统里, 公积金信息存在公积金系统里, 医保信息存在医保系统里. 这有有好处也有坏处, 好处是信息更安全,彼此隔离; 坏处是要是办某一件事,想要所有的信息, 就要跑很多地方取.
而单一状态树的含义就是将所有的信息都存在一个store中, 如果需要什么数据, 直接去那一个store中取就好了. 不要在系统中定义多个store, 这样不方便管理和维护.
单一状态树的含义: 在一个项目只建一个store.
4.2 Getter的使用
Getter有些类似于compute计算属性. 什么时候使用计算属性呢? 当我们需要将一个属性的值经过计算以后显示出来, 这时候我们通常使用计算属性.
Getter也是如此: 当一个state属性需要计算以后显示出来, 我们就可以使用Getter属性.
比如现在要计算counter的平方.
如果不使用计算属性,我们怎么做呢? 在calculate.vue组件里我们是这么写的.
<h2>{{$store.state.counter * $store.state.counter}}</h2>
1. 使用计算属性计算平方
然后, 如果在App.vue中也要这么用, 就再来一段. 观察: 这代码很长, 不利于维护, 我们可以将其放到Getter中. 统一计算以后返回
const store = new Vuex.Store({ state: { counter: 100 }, mutations: { increase(state) { state.counter ++; }, decrease(state) { state.counter --; } }, getters: { // getter中第一个参数是state powerCounter(state) { return state.counter * state.counter } } })
这里定义了一个计算平方的方法powerCounter(), 他的第一个参数是state, 所以, 我们可以直接拿到counter属性进行操作. 接下来在调用方如何调用呢?
<h2>{{$store.getters.powerCounter}}</h2>
通过$store.getters.powerCounter获取计算属性.
2. 在getters中定义方法的第二个参数
如果我们在另一个方法里想要使用其他getters计算方法怎么办呢? 在getters中定义的方法还有默认的第二个参数getters
例: 计算counter的平方 + 100
这时候我们可以怎么做呢? 如下定义了powerAndAdd方法, 其第二个参数默认是getters.
getters: { // getter中第一个参数是state powerCounter(state) { return state.counter * state.counter }, powerAndAdd(state, getters) { return getters.powerCounter + 100 } }
我们在powerCounter已经做了平方的操作了, 接下来我们可以直接使用这个的结果来计算.
在powerAndAdd方法中, 第一个参数依然是state, 第二个参数是getters. 我们可以通过getters获取到第一个方法powerCounter, 然后在第一个方法的基础上+100.
在calculate.vue组件中调用powerAndAdd方法
<template> <div> <h2>-------calculate.vue-------</h2> <h2>{{$store.state.counter}}</h2> <button v-on:click="add()">+</button> <button v-on:click="sub()">-</button> <h2>counter取平方: {{$store.getters.powerCounter}}</h2> <h2>平方后+100: {{$store.getters.powerAndAdd}}</h2> </div> </template>
最终效果如下:
3. 在getters中自定义带参数的方法
在getter中, 前面说了, getters中定义的方法, 第一个参数是state, 第二个参数是getters. 这两个都是默认的. 如果我有一个方法想要传自定义参数怎么办呢?
不能直接在getters后面添加, 可以使用匿名函数接收自定义参数. 比如下面的add方法
getters: { // getter中第一个参数是state powerCounter(state) { return state.counter * state.counter }, powerAndAdd(state, getters) { return getters.powerCounter + 100 }, add(state, getters) { return (num1, num2) => num1 + 100 + num2; } }
在使用的时候, 直接传递两个参数就可以了,方法如下:
<h2>累加+100: {{$store.getters.add(200, 500)}}</h2>
4.3 Mutations的使用
1. Vuex中store状态修改的唯一方式: 提交Mutations
- Mutations 主要包含两部分
- 一部分是事件类型(type)
- 另一部分是回调函数(handler), 回调函数的第一个参数是state
例如:
mutations: { increase(state) { state.counter ++; }, decrease(state) { state.counter --; } }
我们可以理解为increase是事件类型type, 方法体是回调函数. 回调函数的第一个参数是state
mutations方法定义好以后, 如果我们想要调用, 使用commit提交的方式调用
add() { this.$store.commit("increase"); },
2.Mutations传递参数
在之前计算页面有一个+和-, 如果需要+5, +10, -100这时候怎么处理呢? 我们需要定义一个方法, 接收参数 在Mutation中如何定义参数, 又如何传递参数呢?
在Mutation中有两种数据提交的方式
1. 第一种数据传递的方式
我们来看看步骤:
第一步: 在calculate.vue组件定义两个按钮, 一个+5, 一个加 +10
<button v-on:click="addCount(5)">+5</button> <button v-on:click="addCount(10)">+10</button>
在定义一个方法addCount()
methods: { add() { this.$store.commit("increase"); }, sub() { this.$store.commit("decrease") }, addCount(num) { } }
第二步: 在store中定义一个mutation方法, 并且接收一个参数, 如下increaseCount
mutations: { increase(state) { state.counter ++; }, decrease(state) { state.counter --; }, increaseCount(state, num) { state.counter += num; } }
increaseCount()方法第一个参数是state, 我们可以用第二个参数来接收变量. 这和getter是不一样的, getter需要写一个匿名函数来接收自定义变量
第三步: 在定义好的calculate组件中调用store的increaseCount方法
addCount(num) { this.$store.commit("increaseCount", num) }
在传递参数的时候, 我们将自定义参数放在第二个变量位置.
第四步: 查看效果
1. 第二种数据传递的方式
第一种数据传递的方式是具体的参数, 第二种数据传递的方式传递的是对象. 我们来看看第二种
下面来对比比较:
之前使用的是直接传递参数的方式
addCount(num) { this.$store.commit("increaseCount", num) },
使用对象传递参数怎么写呢?
addCount(num) { this.$store.commit({ type: "increaseCount", num: num }) },
需要注意的是, 写法不同, 含义有略有区别.
- 方式一传递到mutation中的是具体的参数值.
- 方式二传递到mutation中的是一个对象.
同样是在mutation中定义方法, 并接受一个参数obj
increaseCount(state, obj) { console.log(obj); },
第一种方式传递过来的是:
可以看到传递过来是具体参数的内容
第二种方式传递过来的是一个对象
观察右侧控制台, 传输传递过来的是一个对象, 并且是所有参数的对象. 所以, 如果增加num数字, 需要获取obj.num
increaseCount(state, obj) { console.log(obj); state.counter += obj.num; },
3. Mutation的响应规则
Vuex的store的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新.
但是, 需要准守对应的规则
1) 增加新属性
比如: 我们修改info的name参数.先来看效果
第一步: 在store/index.js的state中定义变量info, 并定义修改info的方法updateInfo
const store = new Vuex.Store({ state: { info: { name: "王五", age: 58, sex: "男" } }, mutations: { updateInfo(state, name) { // 修改info中name的值 state.info["name"] = name; } } })
第二步: 在calculate.vue中展示info内容, 并修改info的内容
<template> <div> <h2>-------calculate.vue-------</h2> <h2>info: {{$store.state.info}}</h2> <button v-on:click="updateInfo">修改name</button> </div> </template> <script> export default { name: "calculate", methods: { updateInfo() { this.$store.commit("updateInfo", "赵六") } } } </script>
这里直接调用的是store中的mutation方法updateInfo(). 在updateInfo()方法中, 使用state.info["name"]=name的方式重置了name的值,并且在页面立刻响应式的看到了效果
但是, 不是在任何情况使用state.info["name"]=name赋值都是响应式的, 我们来给info增加一个hobby试一试
第一步: 在store/index.js中增加mutation方法, 添加爱好hobby, hobby属性之前在info中是没有的
updateInfoHobby(state, hobby) { state.info["hobby"] = hobby console.log(state.info) }
第二步: 定义方法修改hobby
updateInfoHobby() { this.$store.commit("updateInfoHobby", "篮球") }
第三步: 看效果
我们发现, 点击按钮添加hobby以后, 页面并没有响应式的添加hobby, 但是在右侧控制台看到$store.info中确实已经有hobby属性.
这就是我们要说的Mutation修改state属性的第一个条件:
要想实现响应式展示, 需要提前在store中初始化好属性. 如果有些属性是动态添加的, 提前不知道怎么办呢? 我们需要换一种方式添加
updateInfoHobby(state, hobby) { Vue.set(state.info, "hobby", hobby); }
来看看效果:
还有一种方式: 就是使用完整的新对象给就对象赋值.
总结:
- 提前在store中初始化好需要的属性
- 当给state中的对象添加新属性的时候,使用下面的方式
方式一:使用Vue.set(obj, newProp, value)
方式二: 用新对象给就对象赋值
2) 删除属性
当我们需要删除属性的时候, 也是使用Vue.delete(obj, prop)可以做到响应式展示
4. Mutation的类型常量
在mutation中, 我们定义了很多事件类型(也就是方法名), 当我们的项目变大时, vuex管理 状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutation中的方法越来越多. 方法多了,名称就容易出错, 所以我们将Mutation中的常量提取出来. 放在一个公共文件中定义,下面来看看如何实现:
以修改counter方法为例. 我们来将自增方法increase提取出来.
第一步: 新增一个mutation-types.js 文件, 定义一个常量INCREASW
export const INCREASW = "increase"
第二步. 在store的mutation中使用常量定义方法名
// 引入mutation import { INCREASW} from './mutation-types' const store = new Vuex.Store({ mutations: { [INCREASW](state) { state.counter ++; } } })
这里使用[]来定义方法名, 作为变量传入.
第三步: 在calculate.vue中引入mutation-types并且使用commit提交到mutation
import {INCREASW} from '../store/mutation-types' add() { this.$store.commit(INCREASW); },
这样就提取了变量. 当需要修改变量名的时候, 我们不用每个地方都修改, 只需要修改mutation-types中的变量名的值.
5 Mutation同步函数
通常情况下, Vuex要求我们Mutation中的方法必须是同步方法. 为什么呢?
主要的原因是, 当我们使用devtools工具时, devtools工具可以很好的帮我们捕捉mutation的快照. 但如果是异步操作, 那么devtools将不能很好地追踪到这个操作是什么时候完成的.
举个例子:
我们将[修改name]这个动作进行异步处理. 放在setTimeout中,
updateInfo(state, name) { setTimeout(function(){ state.info["name"] = name; }, 1000) }
然后点击[修改name]按钮, 会发现将王五的名字改为赵六, 页面改了, 但是在devtools工具中没有改, 如下图:
这个问题就是上面说的, 在Mutation中尽量不要执行异步操作, 要是执行异步操作, devtools可能跟踪不上.
如果确实有异步操作, 那么就使用action. action的功能类似于Mutation, 但是它主要是处理异步操作的. 下面就来看看Action的使用
4.4 Action的使用
上面已经说过了, action的用法和Mutation的用法类似. 但action主要是处理异步操作. 如何将写在mutation中的updateInfo方法中异步操作替换到action中实现呢?
在mutation中定义的方法
updateInfo(state, name) { setTimeout(function() { state.info["name"] = name; }, 1000) }
在action中定义的方法
actions:{ aUpdateInfo(context) { setTimeout(function () { context.commit("updateInfo") }, 1000) } }, mutations: { updateInfo(state, name) { state.info["name"] = name; } }
我们定义了一个和updateInfo对应的方法 aUpdate. 入参是context, 注意这里不是state了, 而是整个store. 异步操作定义在aUpdate方法中, 然后调用Mutation中的方法.
注意: 这是官方要求的, action不要自己去修改state, 所以修改state的操作都在Mutation中进行.
接下来, 在按钮[修改name]的时候, 重新定义click事件, 这次就不能直接指向mutation了, 而是要指向action.
* 调用mutation的方法使用: this.$store.commit(...)
* 调用action的方法使用: this.$store.dispatch(...)
updateInfo() { // 调用mutation // this.$store.commit("updateInfo", "赵六") // 调用action this.$store.dispatch("aUpdateInfo", "赵六") },
效果如下图所示:
可以看到这会devtools识别了info信息的变化
4.5 Module的使用
Module的含义是模块, 我们为什么要在Vue中引入模块呢?
- Vue使用的是单一状态树, 那么也就是说很多状态会交给vuex来管理
- 当应用变得复杂是, store也会变得很臃肿,
为了解决这个问题, Vuex允许我们将store分割成模块. 每个模块都拥有自己的states, mutations, actions, getters 等等
写法如下:
const store = new Vuex.Store({ modules:{ a:{ state: { }, getters:{ }, mutations:{ }, actions: { } }, b: { } } })
如上, 在store中定义了一个modules, 里面有两个模块a和b, a模块中定义了一套getters, states, mutations, actions, b模块也可如此定义
那么定义好以后如何使用呢? 下面一个一个来看
1. state调用
在store/index.js文件中定义一个moudules, 然后定义state
const module1 = { state: { message: "这是定义在module1中的state" }, } const store = new Vuex.Store({ modules:{ module1: module1 } }
如上展示, 如何调用module1中的message呢?
<h2> message : {{$store.state.module1.message}} </h2>
2. getter 调用
在module1中增加getters
const module1 = { state: { message: "这是定义在module2中的state" }, getters:{ extraMessage: function(state){ return state.message + "aaaa"; } } }
前面介绍过getters中定义的属性, 就相当于computed计算属性.
接下来看看如何调用getters中的计算属性呢?
<h2> extraMessage: {{$store.getters.extraMessage}}</h2>
在调用的时候, 和state有些不同. 这里不需要指定modules模块名. 首先回去store中定义的getters查找, 抄不到再去modules1模块中查找,所以, 我们在定义的时候,尽量不要重名
3. mutation的调用
我们定义一个按钮来更新 message的值
const module1 = { state: { message: "这是定义在module2中的state" }, getters:{ extraMessage: function(state){ return state.message + "aaaa"; } }, mutations:{ changeMessage: function(state) { state.message = "替换成新的message" } }, actions: { } }
接下来看调用方, 定义一个按钮, 替换message的值. 然后changeMessage
<button v-on:click="changeMessage">替换message</button>
changeMessage() { this.$store.commit("changeMessage") }
我们看到在调用mutation中的方法的时候, 直接使用的是 commit. 和调用store中mutation是一样的.
这里还可以传递参数, 方式方法也和在store中一样
4. action的调用
我们在修改message信息的地方将其设置为异步修改, 写法如下:
const module1 = { state: { message: "这是定义在module2中的state" }, getters:{ extraMessage: function(state){ return state.message + "aaaa"; } }, mutations:{ changeMessage: function(state) { state.message = "替换成新的message" + ", bbbb"; } }, actions: { aChangeMessage: function(context) { setTimeout(() => { context.commit("changeMessage") }, 1000) } } }
在actions中增加了一个setTimeout, 这就是异步的. 调用方在调用的时候, 需要使用dispatch指向actions的方法
changeMessage() { this.$store.dispatch("aChangeMessage") }
以上就是在modules中定义states, getters, mutations, actions的方法的使用
至此Vuex的用法就全部完事了.