diff --git a/src/core/instance/lifecycle.js b/src/core/instance/lifecycle.js index 169a54a6bb3..c5dc7668b98 100644 --- a/src/core/instance/lifecycle.js +++ b/src/core/instance/lifecycle.js @@ -50,9 +50,6 @@ export function initLifecycle (vm: Component) { export function lifecycleMixin (Vue: Class) { Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this - if (vm._isMounted) { - callHook(vm, 'beforeUpdate') - } const prevEl = vm.$el const prevVnode = vm._vnode const prevActiveInstance = activeInstance @@ -197,7 +194,13 @@ export function mountComponent ( // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined - new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */) + new Watcher(vm, updateComponent, noop, { + before () { + if (vm._isMounted) { + callHook(vm, 'beforeUpdate') + } + } + }, true /* isRenderWatcher */) hydrating = false // manually mounted instance, call mounted on self diff --git a/src/core/observer/scheduler.js b/src/core/observer/scheduler.js index fce86e5f40b..e7ccf57dd25 100644 --- a/src/core/observer/scheduler.js +++ b/src/core/observer/scheduler.js @@ -53,6 +53,9 @@ function flushSchedulerQueue () { // as we run existing watchers for (index = 0; index < queue.length; index++) { watcher = queue[index] + if (watcher.before) { + watcher.before() + } id = watcher.id has[id] = null watcher.run() diff --git a/src/core/observer/watcher.js b/src/core/observer/watcher.js index 48a2e612e28..3c4e533c08b 100644 --- a/src/core/observer/watcher.js +++ b/src/core/observer/watcher.js @@ -37,6 +37,7 @@ export default class Watcher { newDeps: Array; depIds: SimpleSet; newDepIds: SimpleSet; + before: ?Function; getter: Function; value: any; @@ -58,6 +59,7 @@ export default class Watcher { this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync + this.before = options.before } else { this.deep = this.user = this.lazy = this.sync = false } diff --git a/src/platforms/web/runtime/components/transition-group.js b/src/platforms/web/runtime/components/transition-group.js index 219ff01cf54..81d033d5ad5 100644 --- a/src/platforms/web/runtime/components/transition-group.js +++ b/src/platforms/web/runtime/components/transition-group.js @@ -33,6 +33,21 @@ delete props.mode export default { props, + beforeMount () { + const update = this._update + this._update = (vnode, hydrating) => { + // force removing pass + this.__patch__( + this._vnode, + this.kept, + false, // hydrating + true // removeOnly (!important, avoids unnecessary moves) + ) + this._vnode = this.kept + update.call(this, vnode, hydrating) + } + }, + render (h: Function) { const tag: string = this.tag || this.$vnode.data.tag || 'span' const map: Object = Object.create(null) @@ -76,17 +91,6 @@ export default { return h(tag, null, children) }, - beforeUpdate () { - // force removing pass - this.__patch__( - this._vnode, - this.kept, - false, // hydrating - true // removeOnly (!important, avoids unnecessary moves) - ) - this._vnode = this.kept - }, - updated () { const children: Array = this.prevChildren const moveClass: string = this.moveClass || ((this.name || 'v') + '-move') diff --git a/test/unit/features/options/lifecycle.spec.js b/test/unit/features/options/lifecycle.spec.js index 2b649aa98e4..3fa152e70c2 100644 --- a/test/unit/features/options/lifecycle.spec.js +++ b/test/unit/features/options/lifecycle.spec.js @@ -137,6 +137,21 @@ describe('Options lifecycle hooks', () => { expect(spy).toHaveBeenCalled() }).then(done) }) + + it('should be called before render and allow mutating state', done => { + const vm = new Vue({ + template: '
{{ msg }}
', + data: { msg: 'foo' }, + beforeUpdate () { + this.msg += '!' + } + }).$mount() + expect(vm.$el.textContent).toBe('foo') + vm.msg = 'bar' + waitForUpdate(() => { + expect(vm.$el.textContent).toBe('bar!') + }).then(done) + }) }) describe('updated', () => {