From a1167c57e5514be57505f4bce8d163aa1f92cf14 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 10 Dec 2021 17:17:08 +0800 Subject: [PATCH] fix(runtime-core): disallow recurse in vnode/directive beforeUpdate hooks --- packages/runtime-core/src/component.ts | 8 +++++++- packages/runtime-core/src/renderer.ts | 26 +++++++++++++++++--------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index cda15d8bd07..eb80e3f9f7f 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -7,7 +7,8 @@ import { EffectScope, markRaw, track, - TrackOpTypes + TrackOpTypes, + ReactiveEffect } from '@vue/reactivity' import { ComponentPublicInstance, @@ -224,6 +225,10 @@ export interface ComponentInternalInstance { * Root vnode of this component's own vdom tree */ subTree: VNode + /** + * Render effect instance + */ + effect: ReactiveEffect /** * Bound effect runner to be passed to schedulers */ @@ -460,6 +465,7 @@ export function createComponentInstance( root: null!, // to be immediately set next: null, subTree: null!, // will be set synchronously right after creation + effect: null!, update: null!, // will be set synchronously right after creation scope: new EffectScope(true /* detached */), render: null, diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 7cf8b49521f..3892eebee8d 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -826,12 +826,15 @@ function baseCreateRenderer( const newProps = n2.props || EMPTY_OBJ let vnodeHook: VNodeHook | undefined | null + // disable recurse in beforeUpdate hooks + parentComponent && toggleRecurse(parentComponent, false) if ((vnodeHook = newProps.onVnodeBeforeUpdate)) { invokeVNodeHook(vnodeHook, parentComponent, n2, n1) } if (dirs) { invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate') } + parentComponent && toggleRecurse(parentComponent, true) if (__DEV__ && isHmrUpdating) { // HMR updated, force full diff @@ -1318,7 +1321,7 @@ function baseCreateRenderer( const { bm, m, parent } = instance const isAsyncWrapperVNode = isAsyncWrapper(initialVNode) - effect.allowRecurse = false + toggleRecurse(instance, false) // beforeMount hook if (bm) { invokeArrayFns(bm) @@ -1336,7 +1339,7 @@ function baseCreateRenderer( ) { instance.emit('hook:beforeMount') } - effect.allowRecurse = true + toggleRecurse(instance, true) if (el && hydrateNode) { // vnode has adopted host node - perform hydration instead of mount. @@ -1459,8 +1462,7 @@ function baseCreateRenderer( } // Disallow component effect recursion during pre-lifecycle hooks. - effect.allowRecurse = false - + toggleRecurse(instance, false) if (next) { next.el = vnode.el updateComponentPreRender(instance, next, optimized) @@ -1482,8 +1484,7 @@ function baseCreateRenderer( ) { instance.emit('hook:beforeUpdate') } - - effect.allowRecurse = true + toggleRecurse(instance, true) // render if (__DEV__) { @@ -1552,17 +1553,17 @@ function baseCreateRenderer( } // create reactive effect for rendering - const effect = new ReactiveEffect( + const effect = (instance.effect = new ReactiveEffect( componentUpdateFn, () => queueJob(instance.update), instance.scope // track it in component's effect scope - ) + )) const update = (instance.update = effect.run.bind(effect) as SchedulerJob) update.id = instance.uid // allowRecurse // #1801, #2043 component render effects should allow recursive updates - effect.allowRecurse = update.allowRecurse = true + toggleRecurse(instance, true) if (__DEV__) { effect.onTrack = instance.rtc @@ -2455,6 +2456,13 @@ export function invokeVNodeHook( ]) } +function toggleRecurse( + { effect, update }: ComponentInternalInstance, + allowed: boolean +) { + effect.allowRecurse = update.allowRecurse = allowed +} + /** * #1156 * When a component is HMR-enabled, we need to make sure that all static nodes