1. Vuex是做什么的?
-
官方解释:Vuex是个专为 Vue.js应用程序开发的状态管理模式。
- 它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
- Vuex也集成到Vue的官方调试工具 devtools extension,提供了诸如零配置的time-travel调试、状态快照导入导出等高级调试功能
-
状态管理到底是什么?
- 状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透
- 其实,你可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。
- 然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用
- 那么,多个组件是不是就可以其享这个对象中的所有变量属性了呢?
-
等等,如果是这样的话,为什么官方还要专门出一个插件Vuex呢?难道我们不能自己封装一个对象来管理吗?
- 当然可以,只是我们要先想想VueJS带给我们最大的便利是什么呢?没错,就是响应式。
- 如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是自己封装可能梢微麻烦一些
- 不用怀疑,Vuex就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了。
2. 管理什么状态?
- 但是,有什么状态时需要我们在多个组件间共享的呢?
- 如果你做过大型开发,你一定遇到过多个状态,在多个界面间的共享问题
- 比如用户的登录状态、用户名称、头像、地理位置信息等等。
- 比如商品的收藏、购物车中的物品等等。
- 这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的
3. 单界面的状态管理
- 我们知道,要在单个组件中进行状态管理是一件非常简单的事情
- 什么意思呢?我们来看下面的图片。
- 这图片中的三种东西,怎么理解呢?
- State:不用多说,就是我们的状态。(姑且当做是data中的属性)
- View:视图层,可以针对State的变化,显示不同的信息。
- Actions:这里的Actions主要是用户的各种操作:点击、输入等,会导致状态的改变
4. 多界面的状态管理
- 全局单例模式(大管家)
- 我们现在要做的就是将共享的状态抽取出来,交给我们的大管家,统一进行管理。
- 之后,你们每个视图,按照我规定好的规定,进行访问和修改等操作。
- 这就是Vuex背后的基本思想。
5. Vuex状态管理图例
官方图片:
6. Vuex的使用
6.1 Vuex核心概念
- Vuex有几个比较核心的概念:
- State
- Getters
- Mutation
- Action
- Module
State单一状态树
也就是说,vuex的配置文件中只有一个Store对象管理状态等信息。
- 如果你的状态信息是保存到多个 Store对象中的,那么之后的管理和维护等等都会变得特别困难。
- 所以Vuex也使用了单一状态树来管理应用层级的全部状态。
- 单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
6.2 state变量(配合简单mutations方法)
以点击按钮触发变量加减为例:
Vuex配置文件(store -> index.js):
// Vuex配置文件
import Vue from 'vue'
import Vuex from 'vuex'
// 1.安装插件
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
state:{
// 属性
counter:1000
},
mutations:{
// 方法
increment(state){
state.counter++
},
decrement(state){
state.counter--
}
},
actions:{
//异步操作
},
getters:{
},
modules:{
}
})
// 3. 导出
export default store
组件中使用:
<template>
<div class=''>
{{ $store.state.counter }} <!-- state中变量使用 -->
<p>
<input type="button" value="+" @click="add()">
</p>
<p>
<input type="button" value="-" @click="substract()">
</p>
</div>
</template>
<script>
export default {
methods: {
add(){
this.$store.commit('increment') // 调用mutations中的方法
},
substract(){
this.$store.commit('decrement') // 调用mutations中的方法
}
}
}
</script>
通过vuex-devtools观察状态变化:
6.3 Getters基本使用
类似于computed属性
Vuex配置文件(store -> index.js):
// Vuex配置文件
import Vue from 'vue'
import Vuex from 'vuex'
// 1.安装插件
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
state:{
// 属性
counter:1000,
students:[
{id:110,name:'why',age:18},
{id:111,name:'kobe',age:24},
{id:112,name:'james',age:30},
{id:113,name:'curry',age:10}
]
},
mutations:{
},
actions:{
//异步操作
},
getters:{
// counter平方
powerCounter(state){
return state.counter * state.counter
},
// 输出年龄大于20的学生
more20stu(state){
return state.students.filter(s=>s.age>20)
},
// 输出年龄大于20的学生的数量
more20stuLength(state,getters){
return getters.more20stu.length
},
// 输出年龄大于指定数字的学生
moreAgeStu(state){
// return function(age){
// return state.students.filter(s => s.age>age)
// }
return age => state.students.filter(s=>s.age > age)
}
},
modules:{
}
})
// 3. 导出
export default store
组件中使用:
<template>
<div>
<!-- counter平方 -->
{{ $store.getters.powerCounter }}
<!-- 输出年龄大于20的学生 -->
{{ $store.getters.more20stu }}
<!-- 输出年龄大于20的学生的数量 -->
{{ $store.getters.more20stuLength }}
<!-- 输出年龄大于指定数字的学生 -->
{{ $store.getters.moreAgeStu(12) }}
</div>
</template>
6.4 Mutation状态更新
-
Vuex的store状态的更新唯一方式:提交Mutation
-
Mutation主要包括两部分:
- 字符串的事件类型(type)
- 一个回调函数(handler),该回调函数的第一个参数就是state
-
mutation的定义方式:
mutations:{
increment(state){
state.counter++
}
}
- 通过mutation更新
add(){
this.$store.commit('increment') // 调用mutations中的方法
}
- 在通过mutation更新数据的时候,有可能我们希望携带一些额外的参数
- 参数被称为是mutation的载荷(Payload)
- Mutation中的代码:
incrementCount(state, count){
state.counter += count
},
- component中methods调用:
addCount(count){
// payload:载荷
this.$store.commit('incrementCount', count)
},
- 但是如果参数不是一个呢?
- 比如我们有很多参数需要传递
- 这个时候,我们通常会以对象的形式传递,也就是payload是一个对象
- 这个时候可以再从对象中取出相关信息
addStu(state, stu){
state.students.push(stu)
}
addStudent(){
const stu = {id:114,name:'alan',age:35}
this.$store.commit('addStu',stu)
}
6.5 Mutation提交风格
- 上面的通过commit进行提交是一种普通的方式
- Vue还提供了另外一种风格,它是一个包含type属性的对象
// 1. 普通提交
// this.$store.commit('incrementCount', count)
// 2. type属性
this.$store.commit({
type: 'incrementCount',
count: count
})
- Mutation中的处理方式是将整个commit的对象作为payload使用,所以代码没有改变依然如下
// incrementCount(state, count){
// state.counter += count
// },
incrementCount(state, payload){
console.log(payload) // {type: "incrementCount", count: 5}
state.counter += payload.count
}
6.6 Mutation响应规则
- Vuex的store中的state是响应式的,当state中的数据发生改变时,Vue组件会自动更新。
- 这就要求我们必须遵守一些Vuex对应的规则:
- 提前在store中初始化好所需的属性
- 当给state中的对象添加新属性时,使用下面的方式:
- 方式一:使用Vue.set(obj,‘newProp’,123)
- 方式二:用新对象给旧对象重新赋值
state:{
info:{
name: 'kobe',
age: 40,
height: 1.98
}
}
mutations:{
updateInfo(state){
// 添加属性
Vue.set(state.info,'address','洛杉矶') // 在info中加入address属性及相应值
// 删除属性
Vue.delete(state.info,'age') // 删除info中age属性及相应值
}
}
6.7 Mutation常量类型 - 概念
- 在 mutation中,我们定义了很多事件类型(也就是其中的方法名称)
- 当我们的项目增大时,Vuex管理的状态越来越多,需要更新状态的情况越来越多,那么意味着Mutation中的方法越来越多
- 方法过多,使用者需要花费大量的经历去记住这些方法,甚至是多个文件间来回切换,查看方法名称,甚至如果不是复制的时候可能还会出现写错的情况
Vuex官方推荐使用Mutation定义引入常量的方式,具体来说:
- 在store文件夹下新建名为
mutations-types.js
的文件,用于存放定义的常量
以之前用到的increment函数名为例,定义其常量为increment
export const INCREMENT = 'increment'
- index.js中引入,并在mutations中调用
import { INCREMENT } from './mutations-types.js'
mutations:{
// 方法
[INCREMENT](state){
state.counter++
}
//increment(state){
// state.counter++
//}
}
- 组件中引用,并在methods方法中提交
import { INCREMENT } from '../store/mutations-types.js'
methods: {
add(){
// this.$store.commit('increment') // 调用mutations中的方法
this.$store.commit(INCREMENT) // 调用mutations中的方法
}
}
6.8 Action的基本定义(异步操作)
- 不要在Mutation中进行异步操作
- 但是某些情况,我们确实希望在Vuex中进行一些异步操作,比如网络请求,必然是异步的。这个时候怎么处理呢?
- Action类似于Mutation,但是是用来代替 Mutation进行异步操作的
index.js:
actions: {
// 异步操作
// context:上下文
// 方法1:
// aUpdateInfo(context,payload){
// setTimeout(()=>{
// context.commit('updateInfo')
// console.log(payload.message)
// payload.success()
// },2000)
// }
// 方法2(推荐):
aUpdateInfo(context, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
context.commit('updateInfo')
console.log(payload) // 我是携带的信息
resolve('1111111')
}, 2000)
}) // 返回promise对象
}
}
组件methods中:
updateInfo() {
// 方法1 对应
// this.$store.commit('updateInfo')
// this.$store.dispatch('aUpdateInfo') // 不传参
// this.$store.dispatch('aUpdateInfo',{
// message:'我是携带的信息',
// success:() => {
// console.log('里面已经完成了');
// }
// }) // 传参
// 方法2(推荐) 对应
this.$store.dispatch("aUpdateInfo", "我是携带的信息").then(res => {
console.log("里面完成了提交");
console.log(res); // 1111111
});
}
补充:Action的其他写法
- actions的写法是接收一个context参数对象
- 局部状态通过
context.state
暴露出来,根节点状态则为context.rootState
(针对下面Module中的补充)
- 局部状态通过
const moduleA = {
actions:{
incrementIfOddOnRootSum({state,commit,rootState}){
if((state.count + rootState.count)%2 === 1){
commit('increment')
}
}
}
}
- 如果getters中也需要使用全局状态,可以接受更多的参数
const moduleA = {
getters:{
sumWithRootCount(state,getters,rootState){
return state.count + rootState.count
}
}
}
注:ES6中的解构
const obj = {
name:'why',
age:18,
height:1.88
}
const {name,age,height} = obj // 顺序可以不固定
console.log(name) // why
6.9 认识Module
- Module是模块的意思,为什么在Vuex中我们要使用模块呢?
- Vue使用单一状态树,那么也意味着很多状态都会交给Vuex来管理
- 当应用变得非常复杂时, store对象就有可能变得相当臃肿
- 为了解决这个问题,Vuex允许我们将 store分割成模块(Module),而每个模块拥有自己的state、 mutations、action、 getters等
index.js配置文件中建立module对象
const moduleA = {
state:{
name: 'zhangsan'
},
mutations:{
// 模块中的名字和store中的名字不可以重复
updateName(state, payload){
state.name = payload
}
},
actions:{
aUpdateName(context){
setTimeout(()=>{
context.commit('updateName','wangwu')
}, 1000)
}
},
getters:{
fullname(state){
return state.name + '1111'
},
fullname2(state,getters){
return getters.fullname + '2222'
},
fullname3(state,getters,rootState){
return getters.fullname2 + rootState.counter
}
}
}
module属性中调用:
modules: {
// a 默认在state里面
a: moduleA
}
组件中关于module的使用:
<h1>module使用</h1>
{{ $store.state.a.name }}
<button @click="updateName()">修改名字</button>
fullname:{{ $store.getters.fullname }}
fullname2:{{ $store.getters.fullname2 }}
fullname3:{{ $store.getters.fullname3 }}
<button @click="asyncUpdateName()">异步修改名字</button>
methods:{
updateName(){
this.$store.commit('updateName','lisi')
},
asyncUpdateName(){
this.$store.dispatch('aUpdateName')
}
}
7. 项目结构
重点Vuex部分: