资料来源:coderwhy老师
状态管理模式
Vuex就是状态管理的一个工具而已。
集中式存储管理
应用的所有组件状态,并以相应的规则保证状态以一种可预测的方式发生变化。devtools extension
,提供了诸如零配置的time-travel调试,状态快照导入导出等高级调试功能。状态管理模式
和集中式存储管理
其实就是设置一个公共的全局变量
。可以把理解成单例对象(单例模式),为什么官方还要专门出一个插件Vuex呢?难道我们就不能自己封装一个对象来管理吗?
假设我们自己来封装一个,示例代码如下方:
// 所有组件继承Vue原型
const shareObj = {
name: 'why'
}
Vue.prototype.shareObj = shareObj
Vue.component('cpn1', {
this.shareObj.name; //从prototype直接拿过来
})
Vue.component('cpn2', {
})
const app = new Vue({
el: '#app',
data: {
}
})
// 上方的做法不好,因为它不是响应式的,如果在const shareObj重新设置name的值的时候,其他地方并不会跟着改,也就是说它不是响应式的。
// 在data修改的数据都被加入到Vue的响应式系统里边了,而我们上边那个shareObj并没有被加入到响应式系统里边。
上方的我们自己做的状态管理模式的代码并没有做到响应式。就是说我们在shareObj修改name属性的时候,其他界面的这个变量值并没有自动改变。
一般在我们开发里面,一般什么样的东西会放在Vuex里面进行管理呢?一般什么放Vuex里面,什么放自己组件里面呢?我们并不是所有的东西都放在Vuex里面,只有那些在多个组件需要共享的,层级关系太多的时候,我们才会放进Vuex。像父子组件只有一层关系的这种,就没必要放进Vuex里面了。那我们平时喜欢将什么状态放进Vuex里面呢?或者说, 有什么状态时需要我们在多个组件间共享的呢?
变量一般是用来保存状态的,在单个页面的data中相当于图中的state了,一个state可以定义多个变量,多个状态,这个state是放在View里面显示的。state的data在view展示,而Actions(如@click事件)又控制了State的data,而data又更新了View显示,如此反复循环。
我们在用CLI搭建的Vue项目时,并没有包含Vuex插件,需要我们手动安装:
cnpm install vuex --save
这个vuex是运行时所用到的插件,所以要使用--save
Vuex所在的文件夹一般叫store
,中文名之仓库
,意为管理很多东西,
上方图中,橙色、紫色、红色组成的部分是Vuex。Vue Components则是组件,组件是能够引用Vuex的State的,这个就叫引用,这个操作是单向的(即不能从组件直接修改State)。
我们在修改State的时候,不要通过Components直接修改,而是要通过Components -> Actions -> Mutations -> State这个线路修改,也可以从Components直接修改Mutations。
这个Actions用来干啥呢?Actions是用来做异步操作的,比如网络请求,Actions连接了Backend API(即后台接口,end-to-end是端到端的意思,end就是端的意思
,那么前端就是frontend
),我们从这个Backend API获取数据的数据的时候,可能非常耗时,故是一个异步操作。操作完成后再提交到Mutations(为了方便Devtools跟踪),而Mutations是做同步操作的,类似一个单线程的东西。Devtools是Vue开发的一个浏览器插件,用来查看State修改的记录,方便追踪错误和调试Bug。比如很多个界面(组件)都会改State,但是你怎么知道哪个组件改了呢?这个时候,我们通过Detools查看就可以知道了。所以,如果我们绕过了Mutations环节,那么Devtools就无法跟踪。
Devtools调试界面如下:
我们至少要通过Mutations修改State中的数据,不然Devtools无法找到相应的数据。
我们修改State的东西,都是通过mutations来修改的。然后,如果有异步操作的话,是通过actions书写代码后commit到mutations的。
下方看一个简单的案例:
下方示例的是将Vuex挂载到Vue实例中:
下方演示的是Vuex中的一个计数器count:
Vuex有几个比较核心的概念:
Getters类似我们单一组件的计算属性computed。我们什么时候会用到计算属性呢?计算属性是我们一些数据需要进过一系列变化之后再展示的,就需要用到计算属性。
具体看代码注释吧…
还有最下方的ppt…
index.js
:
import Vue from 'vue'
import Vuex from 'vuex'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
import moduleA from './modules/moduleA'
// 1.安装插件
Vue.use(Vuex) // 这个代码底层会去执行install方法
// 2.创建对象, 对应官网中https://vuex.vuejs.org/zh/ 那个图中的state
const 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}
],
info: {
name: 'kobe',
age: 40,
height: 1.98
}
}
// 1.0版本:创建对象
const store = new Vuex.Store({
state: {
counter: 1000
},
mutations: {
// 这里面可以写很多方法
increment(state) { //这里state是默认存在的,不需要自己去书写额外获取state的代码
state.counter++;
},
decrement(state) {
state.counter--;
}
},
actions: {
},
getters: {
},
modules: {
}
});
// 2.0版本:并不是直接const store = Vuex(); Store是一个类
const store = new Vuex.Store({
state,
mutations, // mutations里面可以定义很多方法
actions,
getters,
modules: {
a: moduleA
}
})
// 3.导出store独享
export default store
// 对象的解构
const obj = {
name: 'why',
age: 18,
height: 1.88,
address: '洛杉矶'
}
const {name, height, age} = obj;
App.vue
:
<h2>----------App内容: getters相关信息----------</h2>
<h2>{{$store.getters.powerCounter}}</h2>
<h2>{{$store.getters.more20stu}}</h2>
<h2>{{$store.getters.more20stuLength}}</h2>
<h2>{{$store.getters.moreAgeStu(12)}}</h2>
getters.js
:
export default {
// state是默认参数,每一个函数都会默认带有的。
powerCounter(state) {
return state.counter * state.counter
},
more20stu(state) {
return state.students.filter(s => s.age > 20)
},
// 获取年龄超过20岁的学生个数
// 参数中的getters就是Vuex里面的getters那个属性
more20stuLength(state, getters) {
return getters.more20stu.length
},
moreAgeStu(state) {
// 牢记
// 返回一个函数,这里应该是闭包,这个age是外边传进来的参数
return function (age) {
return state.students.filter(s => s.age > age)
}
return age => {
return state.students.filter(s => s.age > age)
}
}
}
// 可能在state前面加上this关键字
// 解答:传参了,所以不需要加this关键字
上方图片中,stuByID通过这里传入的id,去拿到对应的东西。上方代码中,嵌了三个箭头函数,但是在第1层的stuByID不推荐使用箭头函数,直接写原始那种函数格式就好。
Vuex的store状态的更新唯一方式:提交Mutation
事件类型(type)
回调函数(handler)
,该回调函数的第一个参数就是statemutations: {
// 这里面可以写很多方法
increment(state) { //这里state是默认存在的,不需要自己去书写额外获取state的代码
state.counter++;
},
decrement(state) {
state.counter--;
}
}
上方中increment
和decrement
是事件类型,(state) { state.counter++ }
和 (state){ state.counter-- }
是回调函数。
mutations: {
increment(state) {
state.count++
}
}
increment: function() {
this.$store.commit('increment')
}
上方的increment
是事件类型,而且会调用state作为第1个参数。
App.vue
:
<button @click="addCount(5)">+5</button>
<button @click="addCount(10)">+10</button>
Mutations
:
incrementCount(state, count) {
state.counter += count;
}
以上是传入一个参数的情况,那么如果我们需要传入多个参数的话该怎么处理呢?
我们以提交一个学生stu为例:
index.js
:
const 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}
],
info: {
name: 'kobe',
age: 40,
height: 1.98
}
}
App.vue
:
<button @click="addStudent">添加学生</button>
methods: {
addStudent() {
const stu = {id: 114, name: 'alan', age: 35}
this.$store.commit('addStudent', stu)
}
}
mutations
:
addStudent(state, stu) {
state.students.push(stu)
}
上面的通过commit进行提交是一种普通的方式
Vue还提供了另外一种风格,它是一个包含type属性的对象
2种不同的提交风格示例代码:
addCount(count) {
// payload: 负载
// 1.普通的提交封装
this.$store.commit('incrementCount', count)
// 2.特殊的提交封装
this.$store.commit({
type: 'incrementCount',
count //原本写法是count:count
})
}
在特殊的提交方式中,commit里面跟的是一个对象,type就是对象类型(可以说是方法名),如果是这种特殊的提交方式,那么在mutations中,commit内整个对象就变成了payload对象。用的时候,就变成了
incrementCount(state, payload) {
// console.log(count);
state.counter += payload.count
}
Vuex的store中的state是响应式的,当state中的数据发生改变时,Vue组件会自动更新。
这就要求我们必须遵守一些Vue对应的规则(成为响应式的要求):
在State里面的对象的所有属性,被会被Vue的watcher所监视(每个属性都有一个watcher),也就是动态响应,估计里面是用到了观察者模式,
如在state中,有个info对象:
info: {
name: 'kobe', Dep -> [Watcher]
age: 40, Dep -> [Watcher]
height: 1.98 Dep -> [Watcher]
}
比如name会有特定的Dep,Dep是个数组,里面存放了好多个Watcher,一个Watcher对应一个有name的地方,若name有变化,就会通知Watcher进行变化,
name,age, height等等这些属性都会被加入到响应式系统中,而响应式系统会监听属性的变化,当属性发生变化时,会通知所有界面中用到该属性的地方,让界面发生刷新。
在Mutations中,先下方这种代码,是不会被加入到响应式系统中的,因为它是后来加上的(参考第1条要求)。
updateInfo(state) {
state.info['address'] = 'Los Angeles'
}
那有什么办法使得新加入的属性具备响应式的能力呢?我们都知道js中的push和pop方法都是响应式的。
第1中方法是使用Vue.set()方法:
Vue.set(state.info, 'address', 'Los Angeles')
这个Vue.set()是个响应式方法,所以在里面设置的属性,必将加入到响应式系统中。
假设使用delete来删除属性,看看是不是响应式的。
updateInfo(state) {
delete state.info.age
}
经过测试后,age属性是删除了,但是delete方法并不是响应式的。
下方的Vue.delete()则是响应式:
Vue.delete(state.info, 'age')
我们来考虑下面对问题:
在mutation中,我们定义了很多事件类型(也就是其中对方法名称)
当我们对项目增大时,Vuex管理对状态越来越多,需要更新状态的情况越来越多,那么意味着Mutation中的方法越来越多
方法过多,使用者需要花费大量的经历去记住这些方法,甚至是多个文件间来回切换,查看方法名称,甚至如果不是复制的时候,可能还会出现写错的情况。
我们可以创建一个专门存放Mutations常量的文件,如mutations-types.js
,代码示例如下:
export const INCREMENT = 'increment' // 普通导出(即没有default)
然后在外部引用的时候,可以这样写:
import {INCREMENT} from "./mutations-types"; // 普通的导入只能写大括号
export default {
// 方法
[INCREMENT](state) {
state.counter++
}
}
通常情况下,Vuex要求我们Mutation中的方法必须是同步方法。
主要原因是当我们使用detools时,Devtools可以帮助我们捕捉Mutation的快照
但是如果是异步操作,那么Devtools将不能很好的追踪这个操作什么时候会被完成
Vue三大知识点:组件、路由、状态管理