Vuex
它是一个程序里面的状态管理模式,它是集中式存储所有组件的状态的小仓库,并且保持我们存储的状态以一种可以预测的方式发生变化。
state
类似于vue中的data
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
// 定义一个name,以供全局使用
name: '张三',
// 定义一个number,以供全局使用
number: 0,
// 定义一个list,以供全局使用
list: [
{ id: 1, name: '111' },
{ id: 2, name: '222' },
{ id: 3, name: '333' },
]
},
});
export default store;
在项目文件中通过 this.$store.state.XXX
来取值。
建议将取值放在computed属性中
export default {
mounted() {
console.log(this.getName);
},
computed:{
getName() {
return this.$store.state.name;
}
},
}
修饰器 :getters
对state数据进行修饰,类似computed属性。
state: {
name: '张三',
number: 0,
list: [
{ id: 1, name: '111' },
{ id: 2, name: '222' },
{ id: 3, name: '333' },
]
},
// 在store对象中增加getters属性
getters: {
getMessage(state) { // 获取修饰后的name,第一个参数state为必要参数,必须写在形参上
return `hello${state.name}`;
}
}
在项目中通过 this.$store.getters.getMessage
来进行取值。
每次通过this.$store.xxx 来取state 和getters 的值很繁琐,可在computed中进行解构
<script>
import { mapState, mapGetters } from 'vuex';
export default {
mounted() {
console.log(this.name);
console.log(this.getMessage);
},
computed: {
...mapState(['name']),
...mapGetters(['getMessage']),
},
}
</script>
也可以取别名...mapGetters({ aliasName: 'getMessage' }), // 赋别名的话,这里接收对象,而不是数组
Mutation
类似于项目中的methods
对state中的数据进行修改。
错误做法:this.$stroe.state.xxx = xxxx
const store = new Vuex.Store({
state: {
name: '张三',
number: 0,
},
mutations: { // 增加nutations属性
setNumber(state) { // 增加一个mutations的方法,方法的作用是让num从0变成5,state是必须默认参数
state.number = 5;
}
},
});
在项目中通过this.$store.commit('setNumber');
来执行。
Mutation传多个参数
mutations: {
setNumber(state) {
state.number = 5;
},
setNumberIsWhat(state, payload) { // 增加一个带参数的mutations方法,并且官方建议payload为一个对象
state.number = payload.number;
},
},
在项目中调用:this.$store.commit('setNumberIsWhat', { number: 666 }); _// 调用的时候也需要传递一个对象_
Mutation中的函数不能有异步函数。
我们在组件中可以使用mapMutations以代替this.$store.commit(‘XXX’) 但是要写在methods中
<script>
import { mapMutations } from 'vuex';
export default {
mounted() {
this.setNumberIsWhat({ number: 999 });
},
methods: { // 注意,mapMutations是解构到methods里面的,而不是计算属性了
...mapMutations(['setNumberIsWhat']),
},
}
</script>
Actions 异步操作
const store = new Vuex.Store({
state: {
name: '张三',
number: 0,
},
mutations: {
setNumberIsWhat(state, payload) {
state.number = payload.number;
},
},
actions: { // 增加actions属性
setNum(content) { // 增加setNum方法,默认第一个参数是content,其值是复制的一份store
return new Promise(resolve => { // 我们模拟一个异步操作,1秒后修改number为888
setTimeout(() => {
content.commit('setNumberIsWhat', { number: 888 });
resolve();
}, 1000);
});
}
}
});
在actions中就是异步取提交mutations
在项目中用this.$store.dispatch('setNum');
dispatch来调用。
在项目methods中...mapActions({ setNumAlias: 'setNum' }),
解构
actions里面,方法的形参可以直接将commit解构出来,这样可以方便后续操作:
actions: {
setNum({ commit }) { // 直接将content结构掉,解构出commit,下面就可以直接调用了
return new Promise(resolve => {
setTimeout(() => {
commit('XXXX'); // 直接调用
resolve();
}, 1000);
});
},
},
nextTick
nexttick是一个微任务回调 只有watcher更新才会
在下次 DOM 更新循环结束之后执行延迟回调。
原理:
定义一个callbacks变量,将通过参数cb传入的函数用另一个函数进行封装,这个过程中会执行传入的函数以及处理异常或者失败的场景,之后添加callbacks中。调用timeFunc函数,会遍历callbacks中的函数并依次执行,因为timeFunc函数是异步函数,且通过pending变量布尔值转换来控制一次事件循环中只调用一次
什么叫下次 DOM 更新循环结束?
个人理解:下一轮eventloop执行完毕,GUI渲染完毕
Vue的dom更新是异步的(v-for 一百次不异步就卡死)
Watch
Watcher内的方法
当触发监听属性时,旧的值在this.value上,新的值通过this.get()重新求值,再将新值赋值给this.value
getAndInvoke(cb) {
const value = this.get() // 重新求值
if(value !== this.value || isObject(value) || this.deep) {
const oldValue = this.value // 缓存之前的值
this.value = value // 新值
if(this.user) { // 如果是user-watcher
cb.call(this.vm, value, oldValue) // 在回调内传入新值和旧值
}
}
}
。
watch总结:
为需要观察的数据创建并收集user-watcher,当数据改变时通知到user-watcher将新值和旧值传递给用户自己定义的回调函数。定义watch时会被使用到的三个参数:sync、immediate、deep。它们的实现原理是:sync是不将watcher加入到nextTick队列而同步的更新、immediate是立即以得到的值执行一次回调函数、deep是递归的对它的子值进行依赖收集。
Computed
部分实现过程
//将computed计算的值变成响应式数据
function defineComputed(target, key) {
...
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get: createComputedGetter(key),
set: noop
})
}
//响应式数据的get
function createComputedGetter (key) { // 高阶函数
return function () { // 返回函数
const watcher = this._computedWatchers && this._computedWatchers[key]
// 原来this还可以这样用,得到key对应的computed-watcher
if (watcher) {
if (watcher.dirty) { // 在实例化watcher时为true,表示需要计算
watcher.evaluate() // 进行计算属性的求值
}
if (Dep.target) { // 当前的watcher,这里是页面渲染触发的这个方法,所以为render-watcher
watcher.depend() // 收集当前watcher
}
return watcher.value // 返回求到的值或之前缓存的值
}
}
}
//---------------------------
class Watcher {
...
evaluate () {
this.value = this.get() // 计算属性求值
this.dirty = false // 表示计算属性已经计算,不需要再计算
}
depend () {
let i = this.deps.length // deps内是计算属性内能访问到的响应式数据的dep的数组集合
while (i--) {
this.deps[i].depend() // 让每个dep收集当前的render-watcher
}
}
}
conputed总结:
为什么计算属性有缓存功能?因为当计算属性经过计算后,内部的标志位会表明已经计算过了,再次访问时会直接读取计算后的值;
为什么计算属性内的响应式数据发生变更后,计算属性会重新计算?
因为内部的响应式数据会收集computed-watcher,变更后通知计算属性要进行计算,也会通知页面重新渲染,渲染时会读取到重新计算后的值。