From 1eb6067a8598730c67b3b3a4ac459d2723aa858c Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 19 Aug 2020 17:57:51 -0400 Subject: [PATCH] fix(runtime-core): fix beforeUpdate call timing to allow state mutation fix #1899 --- .../__tests__/apiLifecycle.spec.ts | 30 ++++++++++++++ packages/runtime-core/src/renderer.ts | 40 +++++++++++-------- 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/packages/runtime-core/__tests__/apiLifecycle.spec.ts b/packages/runtime-core/__tests__/apiLifecycle.spec.ts index 003a3d0a8dd..a4c89ff95cb 100644 --- a/packages/runtime-core/__tests__/apiLifecycle.spec.ts +++ b/packages/runtime-core/__tests__/apiLifecycle.spec.ts @@ -74,6 +74,36 @@ describe('api: lifecycle hooks', () => { count.value++ await nextTick() expect(fn).toHaveBeenCalledTimes(1) + expect(serializeInner(root)).toBe(`
1
`) + }) + + it('state mutation in onBeforeUpdate', async () => { + const count = ref(0) + const root = nodeOps.createElement('div') + const fn = jest.fn(() => { + // should be called before inner div is updated + expect(serializeInner(root)).toBe(`
0
`) + count.value++ + }) + const renderSpy = jest.fn() + + const Comp = { + setup() { + onBeforeUpdate(fn) + return () => { + renderSpy() + return h('div', count.value) + } + } + } + render(h(Comp), root) + expect(renderSpy).toHaveBeenCalledTimes(1) + + count.value++ + await nextTick() + expect(fn).toHaveBeenCalledTimes(1) + expect(renderSpy).toHaveBeenCalledTimes(2) + expect(serializeInner(root)).toBe(`
2
`) }) it('onUpdated', async () => { diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 97e4bf06c3a..6497e88edcc 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1281,13 +1281,7 @@ function baseCreateRenderer( let vnodeHook: VNodeHook | null | undefined const { el, props } = initialVNode const { bm, m, parent } = instance - if (__DEV__) { - startMeasure(instance, `render`) - } - const subTree = (instance.subTree = renderComponentRoot(instance)) - if (__DEV__) { - endMeasure(instance, `render`) - } + // beforeMount hook if (bm) { invokeArrayFns(bm) @@ -1296,6 +1290,16 @@ function baseCreateRenderer( if ((vnodeHook = props && props.onVnodeBeforeMount)) { invokeVNodeHook(vnodeHook, parent, initialVNode) } + + // render + if (__DEV__) { + startMeasure(instance, `render`) + } + const subTree = (instance.subTree = renderComponentRoot(instance)) + if (__DEV__) { + endMeasure(instance, `render`) + } + if (el && hydrateNode) { if (__DEV__) { startMeasure(instance, `hydrate`) @@ -1365,16 +1369,8 @@ function baseCreateRenderer( } else { next = vnode } - if (__DEV__) { - startMeasure(instance, `render`) - } - const nextTree = renderComponentRoot(instance) - if (__DEV__) { - endMeasure(instance, `render`) - } - const prevTree = instance.subTree - instance.subTree = nextTree next.el = vnode.el + // beforeUpdate hook if (bu) { invokeArrayFns(bu) @@ -1383,6 +1379,18 @@ function baseCreateRenderer( if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) { invokeVNodeHook(vnodeHook, parent, next, vnode) } + + // render + if (__DEV__) { + startMeasure(instance, `render`) + } + const nextTree = renderComponentRoot(instance) + if (__DEV__) { + endMeasure(instance, `render`) + } + const prevTree = instance.subTree + instance.subTree = nextTree + // reset refs // only needed if previous patch had refs if (instance.refs !== EMPTY_OBJ) {