Skip to content

Commit

Permalink
fix: beforeUpdate should be called before render and allow state muta…
Browse files Browse the repository at this point in the history
…tion (vuejs#7822)

fix vuejs#7481
  • Loading branch information
yyx990803 authored and aJean committed Aug 19, 2020
1 parent 3e1a188 commit dbc0e4a
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 15 deletions.
11 changes: 7 additions & 4 deletions src/core/instance/lifecycle.js
Expand Up @@ -50,9 +50,6 @@ export function initLifecycle (vm: Component) {
export function lifecycleMixin (Vue: Class<Component>) {
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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/core/observer/scheduler.js
Expand Up @@ -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()
Expand Down
2 changes: 2 additions & 0 deletions src/core/observer/watcher.js
Expand Up @@ -37,6 +37,7 @@ export default class Watcher {
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;

Expand All @@ -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
}
Expand Down
26 changes: 15 additions & 11 deletions src/platforms/web/runtime/components/transition-group.js
Expand Up @@ -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)
Expand Down Expand Up @@ -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<VNode> = this.prevChildren
const moveClass: string = this.moveClass || ((this.name || 'v') + '-move')
Expand Down
15 changes: 15 additions & 0 deletions test/unit/features/options/lifecycle.spec.js
Expand Up @@ -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: '<div>{{ msg }}</div>',
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', () => {
Expand Down

0 comments on commit dbc0e4a

Please sign in to comment.