Open theydy opened 3 years ago
在初始化 data 时,当 data 写成函数的形式,会进入 getData 函数
data
getData
function initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} //... } export function getData (data: Function, vm: Component): any { // #7573 disable dep collection when invoking data getters pushTarget() try { return data.call(vm, vm) } catch (e) { handleError(e, vm, `data()`) return {} } finally { popTarget() } }
这里有个有意思的地方,就是在真正执行 data.call(vm, vm) 取值前有一个 pushTarget() 置空 Dep.target 的操作,取值后再恢复 popTarget()。
data.call(vm, vm)
pushTarget()
Dep.target
popTarget()
Dep.target = null const targetStack = [] export function pushTarget (_target: ?Watcher) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } export function popTarget () { Dep.target = targetStack.pop() }
其实这么做的原因已经在源码的注释中写明了,就是为了解决 #7573 这个 issues。
Pitfalls of Vue dependency detection may cause redundant dependencies · Issue #7573 · vuejs/vue
这个 bug 的现象是当子组件 data 写成函数形式并且函数中使用了父组件传给子组件的 props,当父组件中做为 props 传入子组件的那个响应式数据改变时,会触发两次父组件的更新。而且触发两次更新只在数据第一次改变时发生,后续就是正常的只触发一次更新。
props
之所以会这样,是因为执行 data.call(vm, vm) 获取子组件 data 值时,里面使用了 props,此时会触发 props 的 getter,造成 props 收集依赖。由于数据初始化的时机是 beforeCreated -> created 之间,此时还没有进入子组件的渲染阶段(生成渲染 Watcher 是在 mountComponent 中),也就没有子组件的渲染 Watcher。所以这时候 Dep.target 指向的依然是父组件的渲染 Watcher。
getter
mountComponent
最终表现就是父组件的字段更新时,正确触发了一次父组件的渲染 Watcher 的 update,更新子组件的 props 时,又触发了一次父组件的渲染 Watcher 的 update。
而第一次更新后,后续收集依赖时子组件的渲染 Watcher 已经存在,所以不会收集到父组件的渲染 Watcher。
其实不只是这里,子组件的 beforeCreate、created、beforeMount 这三个生命周期钩子函数如果用了 props 的话,也会出现同样的问题,所以在 callHook 函数中也做了同样 Dep.target 置空的操作。
beforeCreate
created
beforeMount
export function callHook (vm: Component, hook: string) { // #7573 disable dep collection when invoking lifecycle hooks pushTarget() const handlers = vm.$options[hook] if (handlers) { for (let i = 0, j = handlers.length; i < j; i++) { try { handlers[i].call(vm) } catch (e) { handleError(e, vm, `${hook} hook`) } } } if (vm._hasHookEvent) { vm.$emit('hook:' + hook) } popTarget() }
在初始化
data
时,当data
写成函数的形式,会进入getData
函数这里有个有意思的地方,就是在真正执行
data.call(vm, vm)
取值前有一个pushTarget()
置空Dep.target
的操作,取值后再恢复popTarget()
。其实这么做的原因已经在源码的注释中写明了,就是为了解决 #7573 这个 issues。
Pitfalls of Vue dependency detection may cause redundant dependencies · Issue #7573 · vuejs/vue
这个 bug 的现象是当子组件
data
写成函数形式并且函数中使用了父组件传给子组件的props
,当父组件中做为props
传入子组件的那个响应式数据改变时,会触发两次父组件的更新。而且触发两次更新只在数据第一次改变时发生,后续就是正常的只触发一次更新。之所以会这样,是因为执行
data.call(vm, vm)
获取子组件data
值时,里面使用了props
,此时会触发props
的getter
,造成props
收集依赖。由于数据初始化的时机是 beforeCreated -> created 之间,此时还没有进入子组件的渲染阶段(生成渲染 Watcher 是在mountComponent
中),也就没有子组件的渲染 Watcher。所以这时候Dep.target
指向的依然是父组件的渲染 Watcher。最终表现就是父组件的字段更新时,正确触发了一次父组件的渲染 Watcher 的 update,更新子组件的 props 时,又触发了一次父组件的渲染 Watcher 的 update。
而第一次更新后,后续收集依赖时子组件的渲染 Watcher 已经存在,所以不会收集到父组件的渲染 Watcher。
其实不只是这里,子组件的
beforeCreate
、created
、beforeMount
这三个生命周期钩子函数如果用了 props 的话,也会出现同样的问题,所以在 callHook 函数中也做了同样Dep.target
置空的操作。其实不只是这里,子组件的
beforeCreate
、created
、beforeMount
这三个生命周期钩子函数如果用了 props 的话,也会出现同样的问题,所以在 callHook 函数中也做了同样Dep.target
置空的操作。