fix(runtime-core): disallow recurse in vnode/directive beforeUpdate hooks

This commit is contained in:
Evan You 2021-12-10 17:17:08 +08:00
parent 4b0ca8709a
commit a1167c57e5
2 changed files with 24 additions and 10 deletions

View File

@ -7,7 +7,8 @@ import {
EffectScope, EffectScope,
markRaw, markRaw,
track, track,
TrackOpTypes TrackOpTypes,
ReactiveEffect
} from '@vue/reactivity' } from '@vue/reactivity'
import { import {
ComponentPublicInstance, ComponentPublicInstance,
@ -224,6 +225,10 @@ export interface ComponentInternalInstance {
* Root vnode of this component's own vdom tree * Root vnode of this component's own vdom tree
*/ */
subTree: VNode subTree: VNode
/**
* Render effect instance
*/
effect: ReactiveEffect
/** /**
* Bound effect runner to be passed to schedulers * Bound effect runner to be passed to schedulers
*/ */
@ -460,6 +465,7 @@ export function createComponentInstance(
root: null!, // to be immediately set root: null!, // to be immediately set
next: null, next: null,
subTree: null!, // will be set synchronously right after creation subTree: null!, // will be set synchronously right after creation
effect: null!,
update: null!, // will be set synchronously right after creation update: null!, // will be set synchronously right after creation
scope: new EffectScope(true /* detached */), scope: new EffectScope(true /* detached */),
render: null, render: null,

View File

@ -826,12 +826,15 @@ function baseCreateRenderer(
const newProps = n2.props || EMPTY_OBJ const newProps = n2.props || EMPTY_OBJ
let vnodeHook: VNodeHook | undefined | null let vnodeHook: VNodeHook | undefined | null
// disable recurse in beforeUpdate hooks
parentComponent && toggleRecurse(parentComponent, false)
if ((vnodeHook = newProps.onVnodeBeforeUpdate)) { if ((vnodeHook = newProps.onVnodeBeforeUpdate)) {
invokeVNodeHook(vnodeHook, parentComponent, n2, n1) invokeVNodeHook(vnodeHook, parentComponent, n2, n1)
} }
if (dirs) { if (dirs) {
invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate') invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate')
} }
parentComponent && toggleRecurse(parentComponent, true)
if (__DEV__ && isHmrUpdating) { if (__DEV__ && isHmrUpdating) {
// HMR updated, force full diff // HMR updated, force full diff
@ -1318,7 +1321,7 @@ function baseCreateRenderer(
const { bm, m, parent } = instance const { bm, m, parent } = instance
const isAsyncWrapperVNode = isAsyncWrapper(initialVNode) const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)
effect.allowRecurse = false toggleRecurse(instance, false)
// beforeMount hook // beforeMount hook
if (bm) { if (bm) {
invokeArrayFns(bm) invokeArrayFns(bm)
@ -1336,7 +1339,7 @@ function baseCreateRenderer(
) { ) {
instance.emit('hook:beforeMount') instance.emit('hook:beforeMount')
} }
effect.allowRecurse = true toggleRecurse(instance, true)
if (el && hydrateNode) { if (el && hydrateNode) {
// vnode has adopted host node - perform hydration instead of mount. // vnode has adopted host node - perform hydration instead of mount.
@ -1459,8 +1462,7 @@ function baseCreateRenderer(
} }
// Disallow component effect recursion during pre-lifecycle hooks. // Disallow component effect recursion during pre-lifecycle hooks.
effect.allowRecurse = false toggleRecurse(instance, false)
if (next) { if (next) {
next.el = vnode.el next.el = vnode.el
updateComponentPreRender(instance, next, optimized) updateComponentPreRender(instance, next, optimized)
@ -1482,8 +1484,7 @@ function baseCreateRenderer(
) { ) {
instance.emit('hook:beforeUpdate') instance.emit('hook:beforeUpdate')
} }
toggleRecurse(instance, true)
effect.allowRecurse = true
// render // render
if (__DEV__) { if (__DEV__) {
@ -1552,17 +1553,17 @@ function baseCreateRenderer(
} }
// create reactive effect for rendering // create reactive effect for rendering
const effect = new ReactiveEffect( const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn, componentUpdateFn,
() => queueJob(instance.update), () => queueJob(instance.update),
instance.scope // track it in component's effect scope instance.scope // track it in component's effect scope
) ))
const update = (instance.update = effect.run.bind(effect) as SchedulerJob) const update = (instance.update = effect.run.bind(effect) as SchedulerJob)
update.id = instance.uid update.id = instance.uid
// allowRecurse // allowRecurse
// #1801, #2043 component render effects should allow recursive updates // #1801, #2043 component render effects should allow recursive updates
effect.allowRecurse = update.allowRecurse = true toggleRecurse(instance, true)
if (__DEV__) { if (__DEV__) {
effect.onTrack = instance.rtc effect.onTrack = instance.rtc
@ -2455,6 +2456,13 @@ export function invokeVNodeHook(
]) ])
} }
function toggleRecurse(
{ effect, update }: ComponentInternalInstance,
allowed: boolean
) {
effect.allowRecurse = update.allowRecurse = allowed
}
/** /**
* #1156 * #1156
* When a component is HMR-enabled, we need to make sure that all static nodes * When a component is HMR-enabled, we need to make sure that all static nodes