知识预备,vue是如何在data变化后就更新dom的
vue模板渲染时怎么对各属性值产生依赖的?且看mounted源码
function mountComponent (){
...
} else {
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
}
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
...
}
实际上是给vm对象的模板生成/更新函数注册了个Watcher
而Watcher默认是非lazy的,就是说不是懒执行的,而是初始化就会立刻执行,所以vue内部调用mountComponent时,会直接调用updateComponent渲染真实dom树
Watcher简略版构造函数:
function Watcher(vm, fn, cb, options){
this.getter = fn
this.lazy = !!options.lazy
this.value = this.lazy ? undefined : this.getter()
}
而Watcher本质上是订阅者,会在执行其更新函数getter过程中订阅所有getter的依赖项,至于具体如何绑定依赖,可以阅读此文章:vue computed原理
当依赖产生后,一旦vm.data中的被依赖项发生了变化,就会触发Watcher去重新执行getter,也就是mountComponent里生成的updateComponent函数,从而更新dom
vuex响应式原理
一旦理解了vue的模板如何响应数据变化,那么vuex就好理解了
vuex本质上是将state值绑定到了一个vue对象上,请看超简略源码:
class Store {
constructor(options){
this.state = new Vue({
data:options.state
})
}
}
于是当我们在test.vue中写出这种代码:
<template>
<div>{{ $store.state.xx }}</div>
</template>
test.vue实例mount的时候执行updateComponent,就会为updateComponent函数绑定一个依赖:Store.state.xx这个属性的Dep对象(暂时命名为xxDep,便于后续说明)
那么一旦通过commit或其他手段更新了属性Store.state.xx,xxDep就会通知updateComponent所绑定的Watcher去执行update
Watcher.prototype.update = function(){
if (this.lazy) {
...
} else {
// 将此watcher加入队列,在nextick中执行
// 最终会执行到Watcher.getter,本例中也就是updateComponent
queueWatcher(this);
}
}
从而最终又执行到了updateComponent去更新dom树,而在执行updateComponent过程中解析dom树时会重新获取{{ $store.state.xx }},从而正确的更新了dom,实现了store.state到vue对象的绑定
store.getters
上面讲了store.state如何绑定到vue对象,那么store.getters呢?
var wrappedGetters = store._wrappedGetters;
var computed = {};
forEachValue(wrappedGetters, function (fn, key) {
computed[key] = partial(fn, store);
Object.defineProperty(store.getters, key, {
get: function () { return store._vm[key]; },
enumerable: true // for local getters
});
});
store._vm = new Vue({
data: {
$$state: state
},
computed: computed
});
可以看到对于每个getters的值,最终放在两个地方:store.getters, store内部的vue对象上的computed属性,computed属性的双向绑定机制跟data属性类似,这里不多讲
而通过store.getters.key获取的值根据以上代码,得到的是store._vm[key],而这个就是computed[key],因为computed属性都会绑定到vm对象上。所以store.getters[key]===computed[key],是完完全全的同一个值
-----------------------2022/4/8更新----------------------------
装载到vue
vue2中使用vuex需要执行vue.use(vuex)。最终会执行到vuex的install方法
// 初始化全局Vue对象时挂载store,并在跟元素上生成
new Vue({
store,
...
})
function install() {
Vue.mixin({
beforeCreate() {
if (this.$options.store) {
this.$store = this.$options.store // 这里对应根组件
return
}
this.$store = this.$parent.$store // 其他组件逐级向上取
}
})
}
通过生命周期给每个组件单独挂载$store,而不是直接Vue.prototype.$store =,这样可以防止声明多个vuex实例后覆盖
vue3中挂载vuex要执行app.use(store)。最终会执行到Store.prototype.install
function install (app, injectKey) {
// globalProperties属性上挂载的属性可以在app下所有组件实例中访问到
app.config.globalProperties.$store = this;
}