Przeglądaj źródła

ensure updated hook is called after children are updated as well (fix #4599)

Evan You 9 lat temu
rodzic
commit
2ee516dfc8

+ 2 - 3
src/core/instance/lifecycle.js

@@ -109,9 +109,8 @@ export function lifecycleMixin (Vue: Class<Component>) {
     if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
       vm.$parent.$el = vm.$el
     }
-    if (vm._isMounted) {
-      callHook(vm, 'updated')
-    }
+    // updated hook is called by the scheduler to ensure that children are
+    // updated in a parent's updated hook.
   }
 
   Vue.prototype._updateFromParent = function (

+ 14 - 2
src/core/observer/scheduler.js

@@ -2,6 +2,7 @@
 
 import type Watcher from './watcher'
 import config from '../config'
+import { callHook } from '../instance/lifecycle'
 import {
   warn,
   nextTick,
@@ -32,6 +33,7 @@ function resetSchedulerState () {
  */
 function flushSchedulerQueue () {
   flushing = true
+  let watcher, id, vm
 
   // Sort queue before flush.
   // This ensures that:
@@ -46,8 +48,8 @@ function flushSchedulerQueue () {
   // do not cache length because more watchers might be pushed
   // as we run existing watchers
   for (index = 0; index < queue.length; index++) {
-    const watcher = queue[index]
-    const id = watcher.id
+    watcher = queue[index]
+    id = watcher.id
     has[id] = null
     watcher.run()
     // in dev build, check and stop circular updates.
@@ -67,6 +69,16 @@ function flushSchedulerQueue () {
     }
   }
 
+  // call updated hooks
+  index = queue.length
+  while (index--) {
+    watcher = queue[index]
+    vm = watcher.vm
+    if (vm._watcher === watcher && vm._isMounted) {
+      callHook(vm, 'updated')
+    }
+  }
+
   // devtool hook
   /* istanbul ignore if */
   if (devtools && config.devtools) {

+ 28 - 0
test/unit/features/options/lifecycle.spec.js

@@ -156,6 +156,34 @@ describe('Options lifecyce hooks', () => {
         expect(spy).toHaveBeenCalled()
       }).then(done)
     })
+
+    it('should be called after children are updated', done => {
+      const calls = []
+      const vm = new Vue({
+        template: '<div><test ref="child">{{ msg }}</test></div>',
+        data: { msg: 'foo' },
+        components: {
+          test: {
+            template: `<div><slot></slot></div>`,
+            updated () {
+              expect(this.$el.textContent).toBe('bar')
+              calls.push('child')
+            }
+          }
+        },
+        updated () {
+          expect(this.$el.textContent).toBe('bar')
+          calls.push('parent')
+        }
+      }).$mount()
+
+      expect(calls).toEqual([])
+      vm.msg = 'bar'
+      expect(calls).toEqual([])
+      waitForUpdate(() => {
+        expect(calls).toEqual(['child', 'parent'])
+      }).then(done)
+    })
   })
 
   describe('beforeDestroy', () => {

+ 6 - 1
test/unit/modules/observer/scheduler.spec.js

@@ -1,6 +1,11 @@
 import Vue from 'vue'
 import config from 'core/config'
-import { queueWatcher } from 'core/observer/scheduler'
+import { queueWatcher as _queueWatcher } from 'core/observer/scheduler'
+
+function queueWatcher (watcher) {
+  watcher.vm = {} // mock vm
+  _queueWatcher(watcher)
+}
 
 describe('Scheduler', () => {
   let spy