From f85feae6e1a4fe3d169df92b820a91b96abc652e Mon Sep 17 00:00:00 2001 From: kleinfreund Date: Sat, 3 Oct 2020 15:19:15 +0200 Subject: [PATCH] fix(runtime-core): vnode.el is null in watcher after rerendering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An instance’s `el` property can be `null` inside a watcher after a component was rerendered (e.g. as a result of a changed prop). In an instance’s `update` method, the branch for handling mounted instances doesn’t set the `el` property of what will become the instance’s `vnode` property soon enough. Upon processing of the `updateComponentPreRender` function, the `el` property is then `null` which causes issues in watcher routines that rely on an instance’s `$el` property. --- .../__tests__/rendererComponent.spec.ts | 79 +++++++++++++++++++ packages/runtime-core/src/renderer.ts | 2 +- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/__tests__/rendererComponent.spec.ts b/packages/runtime-core/__tests__/rendererComponent.spec.ts index 4253779d4cf..d178c21e82c 100644 --- a/packages/runtime-core/__tests__/rendererComponent.spec.ts +++ b/packages/runtime-core/__tests__/rendererComponent.spec.ts @@ -137,4 +137,83 @@ describe('renderer: component', () => { await nextTick() expect(serializeInner(root)).toBe(`
1
1
`) }) + + // #2170 + test('should have access to instance’s “$el” property in watcher when setting instance data', async () => { + function returnThis(this: any) { + return this + } + const dataWatchSpy = jest.fn(returnThis) + let instance: any + const Comp = { + data() { + return { + testData: undefined + } + }, + + watch: { + testData() { + // @ts-ignore + dataWatchSpy(this.$el) + } + }, + + created() { + instance = this + }, + + render() { + return h('div') + } + } + + const root = nodeOps.createElement('div') + render(h(Comp), root) + + expect(dataWatchSpy).not.toHaveBeenCalled() + instance.testData = 'data' + + await nextTick() + expect(dataWatchSpy).toHaveBeenCalledWith(instance.$el) + }) + + // #2170 + test('should have access to instance’s “$el” property in watcher when rendereing with watched prop', async () => { + function returnThis(this: any) { + return this + } + const propWatchSpy = jest.fn(returnThis) + let instance: any + const Comp = { + props: { + testProp: String + }, + + watch: { + testProp() { + // @ts-ignore + propWatchSpy(this.$el) + } + }, + + created() { + instance = this + }, + + render() { + return h('div') + } + } + + const root = nodeOps.createElement('div') + + render(h(Comp), root) + await nextTick() + expect(propWatchSpy).not.toHaveBeenCalled() + + render(h(Comp, { testProp: 'prop ' }), root) + await nextTick() + expect(propWatchSpy).toHaveBeenCalledWith(instance.$el) + }) }) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index bb3129bd7af..217e9a4c209 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1425,11 +1425,11 @@ function baseCreateRenderer( } if (next) { + next.el = vnode.el updateComponentPreRender(instance, next, optimized) } else { next = vnode } - next.el = vnode.el // beforeUpdate hook if (bu) {