Repository: vuejs/vue
Pull request: fix(core): props getter shouldn't collect dependencies during initialization of data option (fix #7573)

<!--
Please make sure to read the Pull Request Guidelines:
https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#pull-request-guidelines
-->

<!-- PULL REQUEST TEMPLATE -->
<!-- (Update "[ ]" to "[x]" to check a box) -->

**What kind of change does this PR introduce?** (check at least one)

- [x] Bugfix
- [ ] Feature
- [ ] Code style update
- [ ] Refactor
- [ ] Build-related changes
- [ ] Other, please describe:

**Does this PR introduce a breaking change?** (check one)

- [ ] Yes
- [x] No

If yes, please describe the impact and migration path for existing applications:

**The PR fulfills these requirements:**

- [x] It's submitted to the `dev` branch for v2.x (or to a previous version branch), _not_ the `master` branch
- [x] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number)
- [x] All tests are passing: https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#development-setup
- [x] New/updated tests are included


**Other information:**
fix #7573.

Changed files:
filename: src/core/instance/lifecycle.js
patch: @@ -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()
 }

filename: src/core/instance/state.js
patch: @@ -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()
   }
 }
 

filename: src/core/observer/dep.js
patch: @@ -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
 }

filename: test/unit/features/options/data.spec.js
patch: @@ -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: `<child :msg="msg"></child>`,
+      data: {
+        msg: 'hello'
+      },
+      beforeUpdate () { calls++ },
+      components: {
+        child: {
+          template: `<span>{{ localMsg }}</span>`,
+          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: {