From 7f09eef05ef2788d0f52165c2fed4ec9d02dec15 Mon Sep 17 00:00:00 2001 From: AchillesJ Date: Sat, 10 Mar 2018 02:13:16 +0800 Subject: [PATCH] fix(core): disable dependency collection in lifecycle hooks and data getter (#7596) This fixes the parent being updated more than necessary due to collecting child props as dependencies during its own update computation. fix #7573 --- src/core/instance/lifecycle.js | 4 ++++ src/core/instance/state.js | 6 ++++- src/core/observer/dep.js | 2 +- test/unit/features/options/data.spec.js | 32 +++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/core/instance/lifecycle.js b/src/core/instance/lifecycle.js index 27756af66fc..c9c3b43c1ae 100644 --- a/src/core/instance/lifecycle.js +++ b/src/core/instance/lifecycle.js @@ -7,6 +7,7 @@ import { createEmptyVNode } from '../vdom/vnode' import { observerState } from '../observer/index' import { updateComponentListeners } from './events' import { resolveSlots } from './render-helpers/resolve-slots' +import { pushTarget, popTarget } from '../observer/dep' import { warn, @@ -315,6 +316,8 @@ export function deactivateChildComponent (vm: Component, direct?: boolean) { } 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++) { @@ -328,4 +331,5 @@ export function callHook (vm: Component, hook: string) { if (vm._hasHookEvent) { vm.$emit('hook:' + hook) } + popTarget() } diff --git a/src/core/instance/state.js b/src/core/instance/state.js index ddabe12a0c3..3a395c84a1f 100644 --- a/src/core/instance/state.js +++ b/src/core/instance/state.js @@ -1,8 +1,8 @@ /* @flow */ import config from '../config' -import Dep from '../observer/dep' import Watcher from '../observer/watcher' +import Dep, { pushTarget, popTarget } from '../observer/dep' import { isUpdatingChildComponent } from './lifecycle' import { @@ -150,11 +150,15 @@ function initData (vm: Component) { } 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() } } diff --git a/src/core/observer/dep.js b/src/core/observer/dep.js index 5d55e9cd421..abf3b275ce4 100644 --- a/src/core/observer/dep.js +++ b/src/core/observer/dep.js @@ -48,7 +48,7 @@ export default class Dep { Dep.target = null const targetStack = [] -export function pushTarget (_target: Watcher) { +export function pushTarget (_target: ?Watcher) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } diff --git a/test/unit/features/options/data.spec.js b/test/unit/features/options/data.spec.js index cb8a75ad18d..ac431bbd939 100644 --- a/test/unit/features/options/data.spec.js +++ b/test/unit/features/options/data.spec.js @@ -93,6 +93,38 @@ describe('Options data', () => { expect(vm.$refs.test.b).toBe(1) }) + it('props should not be reactive', done => { + let calls = 0 + const vm = new Vue({ + template: ``, + data: { + msg: 'hello' + }, + beforeUpdate () { calls++ }, + components: { + child: { + template: `{{ localMsg }}`, + props: ['msg'], + data () { + return { localMsg: this.msg } + }, + computed: { + computedMsg () { + return this.msg + ' world' + } + } + } + } + }).$mount() + const child = vm.$children[0] + vm.msg = 'hi' + waitForUpdate(() => { + expect(child.localMsg).toBe('hello') + expect(child.computedMsg).toBe('hi world') + expect(calls).toBe(1) + }).then(done) + }) + it('should have access to methods', () => { const vm = new Vue({ methods: {