Motivation
在Vuex的官方文档中,有这么一段话。
Vuex宣称自己的store数据是响应式的。当Vue组件从store里获取到状态时, Vue组件会有效更新视图去反应状态的变化。
我们都知道,在Vue中,我们可以使用computed和watch两种技术监听响应式的data。那么,同理,我们应该可以使用computed和watch来对Vuex的state进行监听和回调。
vue数据和视图绑定
通读Vuex的文档,我们可以发现,文档中只提到了使用computed技术来观察store里的state(或者直接在template里面使用store数据),以此来绑定数据和视图。那么,为什么没有提到watch的方式呢?那我们可不可以使用watch技术来实现数据和视图的绑定呢?可不可以watch vuex store数据呢?怎么watch呢?(可以直接看后面关于watch store数据部分)
这里提到的computed和watch技术包括通过option创建的computed和watch, 还有 composition api 创建的computed和watch。 composition api中关于computed 和 watch方法和option api类似,这里只讲option api。
代码解释
来看看option api: 创建computed watcher和watch watcher是通过initComputed和initWatch来创建的。
首先了解下,为什么使用computed技术,能实现store state和视图的绑定。下面是initComputed的简化版
const computedWatcherOptions = { lazy: true }
function initComputed (vm, computed) {
// 组件上的computedWatchers对象
var watchers = vm._computedWatchers = Object.create(null);
// 遍历options中的computed对象
for (var key in computed) {
// 定义在computed中的方法,通过computed中的key值获取
var userDef = computed[key];
// 赋值给新变量getter
var getter = typeof userDef === 'function' ? userDef : userDef.get;
// 创建wather, 收集依赖,并将Watcher实例放入组建的wather队列里
// Watcher构造器接受的入参:
// vm,expOrFn,cb,options,isRenderWatcher
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
// 用于定义computed属性为响应式。
defineComputed(vm, key, userDef);
}
}
这段代码通俗点来讲的话,大概分如下几个步骤:
- 为当前组件创建computedWatchers对象
- 接着,遍历computed的key,为每一个key创建一个watcher,用于观察computed[key]方法中所‘touch’(vue说法)到的可观察数据。
- 定义computed属性(例如vm.a)的getter方法,使其在被‘touch’的时候,能够被当前正在创建的Watcher所依赖(所谓的依赖收集)。
touch数据?
在vue的官方文档中,关于‘Reactivity in Depth’中有这么一幅图,提到了render去touch数据。什么是touch数据? 其实就是调用数据的getter方法。不过这个getter方法已经在defineReactive方法里面被劫持过,拥有被观察的能力。
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// 当被touch时,亦即该property被调用getter时,检查当前是否有全局Watcher
// 依赖收集逻辑
if (Dep.target) {
// 如果存在全局Watcher,那么让此Watcher收集当前data为其依赖
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
}
)
回到initComputed来。在该函数中,通过为每个computed的key创建Watcher来touch在computed[key]中被使用的data(observed data)。 在我们的分析中,就是touch在store里定义的数据。 这样,当store中的数据被修改后,watcher就会接收到通知。 不过,由于computedWatcherOptions = {lazy: true}, 所创建的computed watcher是惰性watcher,即,当watcher所观察的依赖发生变化时,并不是立即执行watcher的callback,而是将watcher标记为dirty,当然,computed watcher是没有callback的(其实有一个noop空函数)。
到此为止, 当store的数据发生变化时,是不会更新视图的。我们需要使这个computed属性也是可以被touch的。这就有了defineComputed方法。
defineComputed方法的会为每一个computed属性定义一个新的getter,该getter使其具备被touch的能力,即在mount的时候,被touch,且被更新视图的Watcher依赖上。
下面的代码是computed属性被定义上的新的getter。当被touch(get)时,检查其computed watcher是否dirty,就是检查该computed所观察的数据是否发生过改变(我们前面提到过,computed watcher是惰性watcher)。若果发生改变的话,重新求值。 接下来,检查global的watcher,检查当前是否在发生依赖收集,如果是的话,让这个watcher依赖上我们的computed 属性(视图更新Watcher就是这是依赖上computed属性的)。最后返回watcher的值。
// 获取对应的watcher
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 检查watcher是否有变化,有变化的话,会调用watcher上面的evaluate来获取新的值
if (watcher.dirty) {
watcher.evaluate()
}
// 检查当前是否有watcher在收集依赖
if (Dep.target) {
watcher.depend()
}
// 这个返回值就是所谓的cache上的computed值(在watcher不是dirty的情况下)
return watcher.value
}
使用watch来观察store的数据?
那我们是否可以使用watch的方式来绑定vuex数据和视图呢?答案是肯定的,不过需要不一样的技术。先看看initWatcher
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
在initWatch里面,首先遍历option里的watch对象,然后为watch对象的每一个key值创建一个或多个watcher(当watch[key]的value为一个数组。数组的元素可以是组件method的名字,也可以是包含handler的object,也可以是函数)。这里使用了createWatcher方法,createWatcher内部逻辑如下:
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
createWatcher封装了一些入参的判断逻辑,最后,调用了vue的$watch方法来观察expOrFn的变化。这里的expOrFn的入参类型为string, 即为watch的key。
到这里,我们可以发现computed的初始化和watch的初始化的最大不同,就是这里的expOrFn和handler.
computed的expOrFn传入的是computed[key](为function),handler为noop。 watch的expOrFn则传入的是watch的key (string ), handler为watch[key]。
本质区别
这就造成了computed和watch的本质区别。
所以,如果我们有如下代码:
computed: {
a: () => { store.state.count++ }
},
watch: {
b: () => { this.a++ }
}
computed watcher 观察的是store.state.count的变化(touch了store.state.count),而watch watcher观察的是b的变化。
computed观察的是computed[key]函数中调用过getter的可观察数据, 所以在computed[key]中调用了多个可观察数据的getter的话,就可以观察多个数据。而watch观察的是唯一的key, 所以只能进行唯一数据观察。同时,computed没有handler,所以当computed的可观察数据发生变化时,computed的watcher没有调用回调函数的操作,同时由于设置了lazy属性,所以只是标记自己(watcher)为dirty。而wach的可观察数据发生变化时,将会调用handler (watch[key])。
他们的关系大概如下:
watch在store中的数据
那如果我们想直接watch在vuex store中的数据呢,同时又不产生不必要的computed属性的话呢?如下图的红线标示。
如果使用option api的watch, 可以这样做: 不过这样watch vuex store中的data是不会更新视图的。
watch: {
'$store.state.count': function (newval) {
console.log(newval);
}
},
我们同时可以利用$watch来做到。 当然,使用这种方法,也是不会更新视图的。
created() {
// top-level property name
this.$watch('a', (newVal, oldVal) => {
// do something
})
// function for watching a single nested property
this.$watch(
() => this.c.d,
(newVal, oldVal) => {
// do something
}
)
// function for watching a complex expression
this.$watch(
// every time the expression `this.$store.state.a + this.$store.state.b` yields a different result,
// the handler will be called. It's as if we were watching a computed
// property without defining the computed property itself
() => this.$store.state.a + this.$store.state.b,
(newVal, oldVal) => {
// do something
}
)
}
为了更新视图,你可以在handler里面对一个可观察数据进行赋值,然后再在视图层使用这个值,就能在回调handler以后,更新视图啦。
总结
综上所述,我们知道,如果想追踪store中的数据,并及时更新视图的最简单方法就是使用computed技术来观察数值变化。同时,我们还了解到在组件中使用watch技术来追踪store中的数据来触发回调函数。 我们还在文中通过查看源代码探讨了computed和watch技术的异同点,总结了在组件中观察vuex数据的不同方法。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!