refactor!: drop custom directives (#274)

This commit is contained in:
Kevin Deng 三咲智子 2024-09-19 01:15:17 +08:00 committed by GitHub
parent 6791c887da
commit e1bedb8ad0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 53 additions and 484 deletions

View File

@ -19,6 +19,7 @@
"paths": { "paths": {
"vue": ["../packages/vue/src"], "vue": ["../packages/vue/src"],
"@vue/vapor": ["../packages/vue-vapor/src"], "@vue/vapor": ["../packages/vue-vapor/src"],
"vue/vapor": ["../packages/vue-vapor/src"],
"@vue/*": ["../packages/*/src"] "@vue/*": ["../packages/*/src"]
} }
}, },

View File

@ -153,7 +153,7 @@ describe('api: createVaporApp', () => {
expect(host.innerHTML).toBe(`foobar!barbaz!`) expect(host.innerHTML).toBe(`foobar!barbaz!`)
}) })
test('directive', () => { test.todo('directive', () => {
const spy1 = vi.fn() const spy1 = vi.fn()
const spy2 = vi.fn() const spy2 = vi.fn()

View File

@ -19,7 +19,7 @@ import { makeRender } from './_utils'
const define = makeRender() const define = makeRender()
describe('directives', () => { describe.todo('directives', () => {
it('should work', async () => { it('should work', async () => {
const count = ref(0) const count = ref(0)

View File

@ -15,7 +15,7 @@ import { makeRender } from '../_utils'
const define = makeRender() const define = makeRender()
describe('directives', () => { describe.todo('directives', () => {
it('should work', async () => { it('should work', async () => {
const count = ref(0) const count = ref(0)

View File

@ -27,7 +27,7 @@ const setDOMProps = (el: any, props: Array<[key: string, value: any]>) => {
}) })
} }
describe('directive: v-model', () => { describe.todo('directive: v-model', () => {
test('should work with text input', async () => { test('should work with text input', async () => {
const spy = vi.fn() const spy = vi.fn()

View File

@ -28,7 +28,7 @@ const createDemo = (defaultValue: boolean) =>
on(n1 as HTMLElement, 'click', () => handleClick) on(n1 as HTMLElement, 'click', () => handleClick)
return n0 return n0
}) })
describe('directive: v-show', () => { describe.todo('directive: v-show', () => {
test('basic', async () => { test('basic', async () => {
const { host } = createDemo(true).render() const { host } = createDemo(true).render()
const btn = host.querySelector('button') const btn = host.querySelector('button')

View File

@ -194,7 +194,7 @@ describe('createFor', () => {
expect(host.innerHTML).toBe('<!--for-->') expect(host.innerHTML).toBe('<!--for-->')
}) })
test('should work with directive hooks', async () => { test.fails('should work with directive hooks', async () => {
const calls: string[] = [] const calls: string[] = []
const list = ref([0]) const list = ref([0])
const update = ref(0) const update = ref(0)

View File

@ -134,7 +134,7 @@ describe('createIf', () => {
expect(host.innerHTML).toBe('<!--if-->') expect(host.innerHTML).toBe('<!--if-->')
}) })
test('should work with directive hooks', async () => { test.todo('should work with directive hooks', async () => {
const calls: string[] = [] const calls: string[] = []
const show1 = ref(true) const show1 = ref(true)
const show2 = ref(true) const show2 = ref(true)

View File

@ -1,10 +1,10 @@
import { import {
type EffectScope,
type ShallowRef, type ShallowRef,
getCurrentScope, effectScope,
isReactive, isReactive,
proxyRefs, proxyRefs,
shallowRef, shallowRef,
traverse,
triggerRef, triggerRef,
} from '@vue/reactivity' } from '@vue/reactivity'
import { isArray, isObject, isString } from '@vue/shared' import { isArray, isObject, isString } from '@vue/shared'
@ -18,17 +18,11 @@ import { type Block, type Fragment, fragmentKey } from './apiRender'
import { warn } from './warning' import { warn } from './warning'
import { currentInstance } from './component' import { currentInstance } from './component'
import { componentKey } from './component' import { componentKey } from './component'
import { BlockEffectScope, isRenderEffectScope } from './blockEffectScope'
import {
createChildFragmentDirectives,
invokeWithMount,
invokeWithUnmount,
invokeWithUpdate,
} from './directivesChildFragment'
import type { DynamicSlot } from './componentSlots' import type { DynamicSlot } from './componentSlots'
import { renderEffect } from './renderEffect'
interface ForBlock extends Fragment { interface ForBlock extends Fragment {
scope: BlockEffectScope scope: EffectScope
state: [ state: [
item: ShallowRef<any>, item: ShallowRef<any>,
key: ShallowRef<any>, key: ShallowRef<any>,
@ -53,8 +47,6 @@ export const createFor = (
let oldBlocks: ForBlock[] = [] let oldBlocks: ForBlock[] = []
let newBlocks: ForBlock[] let newBlocks: ForBlock[]
let parent: ParentNode | undefined | null let parent: ParentNode | undefined | null
const update = getMemo ? updateWithMemo : updateWithoutMemo
const parentScope = getCurrentScope()!
const parentAnchor = __DEV__ ? createComment('for') : createTextNode() const parentAnchor = __DEV__ ? createComment('for') : createTextNode()
const ref: Fragment = { const ref: Fragment = {
nodes: oldBlocks, nodes: oldBlocks,
@ -62,25 +54,17 @@ export const createFor = (
} }
const instance = currentInstance! const instance = currentInstance!
if (__DEV__ && (!instance || !isRenderEffectScope(parentScope))) { if (__DEV__ && !instance) {
warn('createFor() can only be used inside setup()') warn('createFor() can only be used inside setup()')
} }
createChildFragmentDirectives( const update = getMemo ? updateWithMemo : updateWithoutMemo
parentAnchor, once ? renderList() : renderEffect(renderList)
() => oldBlocks.map(b => b.scope),
// source getter
() => traverse(src(), 1),
// init cb
getValue => doFor(getValue()),
// effect cb
getValue => doFor(getValue()),
once,
)
return ref return ref
function doFor(source: any) { function renderList() {
const source = src()
const newLength = getLength(source) const newLength = getLength(source)
const oldLength = oldBlocks.length const oldLength = oldBlocks.length
newBlocks = new Array(newLength) newBlocks = new Array(newLength)
@ -265,7 +249,7 @@ export const createFor = (
idx: number, idx: number,
anchor: Node = parentAnchor, anchor: Node = parentAnchor,
): ForBlock { ): ForBlock {
const scope = new BlockEffectScope(instance, parentScope) const scope = effectScope()
const [item, key, index] = getItem(source, idx) const [item, key, index] = getItem(source, idx)
const state = [ const state = [
@ -283,11 +267,9 @@ export const createFor = (
}) })
block.nodes = scope.run(() => renderItem(proxyRefs(state)))! block.nodes = scope.run(() => renderItem(proxyRefs(state)))!
invokeWithMount(scope, () => { // TODO v-memo
// TODO v-memo // if (getMemo) block.update()
// if (getMemo) block.update() if (parent) insert(block.nodes, parent, anchor)
if (parent) insert(block.nodes, parent, anchor)
})
return block return block
} }
@ -326,7 +308,6 @@ export const createFor = (
} }
if (needsUpdate) setState(block, newItem, newKey, newIndex) if (needsUpdate) setState(block, newItem, newKey, newIndex)
invokeWithUpdate(block.scope)
} }
function updateWithoutMemo( function updateWithoutMemo(
@ -344,13 +325,11 @@ export const createFor = (
(!isReactive(newItem) && isObject(newItem)) (!isReactive(newItem) && isObject(newItem))
if (needsUpdate) setState(block, newItem, newKey, newIndex) if (needsUpdate) setState(block, newItem, newKey, newIndex)
invokeWithUpdate(block.scope)
} }
function unmount({ nodes, scope }: ForBlock) { function unmount({ nodes, scope }: ForBlock) {
invokeWithUnmount(scope, () => { removeBlock(nodes, parent!)
removeBlock(nodes, parent!) scope.stop()
})
} }
} }

View File

@ -1,15 +1,7 @@
import { renderEffect } from './renderEffect'
import { type Block, type Fragment, fragmentKey } from './apiRender' import { type Block, type Fragment, fragmentKey } from './apiRender'
import { getCurrentScope } from '@vue/reactivity' import { type EffectScope, effectScope } from '@vue/reactivity'
import { createComment, createTextNode, insert, remove } from './dom/element' import { createComment, createTextNode, insert, remove } from './dom/element'
import { currentInstance } from './component'
import { warn } from './warning'
import { BlockEffectScope, isRenderEffectScope } from './blockEffectScope'
import {
createChildFragmentDirectives,
invokeWithMount,
invokeWithUnmount,
invokeWithUpdate,
} from './directivesChildFragment'
type BlockFn = () => Block type BlockFn = () => Block
@ -26,8 +18,7 @@ export const createIf = (
let branch: BlockFn | undefined let branch: BlockFn | undefined
let parent: ParentNode | undefined | null let parent: ParentNode | undefined | null
let block: Block | undefined let block: Block | undefined
let scope: BlockEffectScope | undefined let scope: EffectScope | undefined
const parentScope = getCurrentScope()!
const anchor = __DEV__ ? createComment('if') : createTextNode() const anchor = __DEV__ ? createComment('if') : createTextNode()
const fragment: Fragment = { const fragment: Fragment = {
nodes: [], nodes: [],
@ -35,37 +26,17 @@ export const createIf = (
[fragmentKey]: true, [fragmentKey]: true,
} }
const instance = currentInstance!
if (__DEV__ && (!instance || !isRenderEffectScope(parentScope))) {
warn('createIf() can only be used inside setup()')
}
// TODO: SSR // TODO: SSR
// if (isHydrating) { // if (isHydrating) {
// parent = hydrationNode!.parentNode // parent = hydrationNode!.parentNode
// setCurrentHydrationNode(hydrationNode!) // setCurrentHydrationNode(hydrationNode!)
// } // }
createChildFragmentDirectives( if (once) {
anchor, doIf()
() => (scope ? [scope] : []), } else {
// source getter renderEffect(() => doIf())
condition, }
// init cb
getValue => {
newValue = !!getValue()
doIf()
},
// effect cb
getValue => {
if ((newValue = !!getValue()) !== oldValue) {
doIf()
} else if (scope) {
invokeWithUpdate(scope)
}
},
once,
)
// TODO: SSR // TODO: SSR
// if (isHydrating) { // if (isHydrating) {
@ -75,17 +46,20 @@ export const createIf = (
return fragment return fragment
function doIf() { function doIf() {
parent ||= anchor.parentNode if ((newValue = !!condition()) !== oldValue) {
if (block) { parent ||= anchor.parentNode
invokeWithUnmount(scope!, () => remove(block!, parent!)) if (block) {
} scope!.stop()
if ((branch = (oldValue = newValue) ? b1 : b2)) { remove(block, parent!)
scope = new BlockEffectScope(instance, parentScope) }
fragment.nodes = block = scope.run(branch)! if ((branch = (oldValue = newValue) ? b1 : b2)) {
invokeWithMount(scope, () => parent && insert(block!, parent, anchor)) scope = effectScope()
} else { fragment.nodes = block = scope.run(branch)!
scope = block = undefined parent && insert(block, parent, anchor)
fragment.nodes = [] } else {
scope = block = undefined
fragment.nodes = []
}
} }
} }
} }

View File

@ -1,36 +0,0 @@
import { EffectScope } from '@vue/reactivity'
import type { ComponentInternalInstance } from './component'
import type { DirectiveBindingsMap } from './directives'
export class BlockEffectScope extends EffectScope {
/**
* instance
* @internal
*/
it: ComponentInternalInstance
/**
* isMounted
* @internal
*/
im: boolean
/**
* directives
* @internal
*/
dirs?: DirectiveBindingsMap
constructor(
instance: ComponentInternalInstance,
parentScope: EffectScope | null,
) {
super(false, parentScope || undefined)
this.im = false
this.it = instance
}
}
export function isRenderEffectScope(
scope: EffectScope | undefined,
): scope is BlockEffectScope {
return scope instanceof BlockEffectScope
}

View File

@ -1,4 +1,4 @@
import { isRef } from '@vue/reactivity' import { EffectScope, isRef } from '@vue/reactivity'
import { import {
EMPTY_OBJ, EMPTY_OBJ,
hasOwn, hasOwn,
@ -31,7 +31,6 @@ import {
createAppContext, createAppContext,
} from './apiCreateVaporApp' } from './apiCreateVaporApp'
import type { Data } from '@vue/runtime-shared' import type { Data } from '@vue/runtime-shared'
import { BlockEffectScope } from './blockEffectScope'
export type Component = FunctionalComponent | ObjectComponent export type Component = FunctionalComponent | ObjectComponent
@ -161,7 +160,7 @@ export interface ComponentInternalInstance {
root: ComponentInternalInstance root: ComponentInternalInstance
provides: Data provides: Data
scope: BlockEffectScope scope: EffectScope
comps: Set<ComponentInternalInstance> comps: Set<ComponentInternalInstance>
rawProps: NormalizedRawProps rawProps: NormalizedRawProps
@ -283,7 +282,7 @@ export function createComponentInstance(
parent, parent,
root: null!, // set later root: null!, // set later
scope: null!, scope: new EffectScope(true /* detached */)!,
provides: parent ? parent.provides : Object.create(_appContext.provides), provides: parent ? parent.provides : Object.create(_appContext.provides),
type: component, type: component,
comps: new Set(), comps: new Set(),
@ -358,7 +357,6 @@ export function createComponentInstance(
// [VaporLifecycleHooks.SERVER_PREFETCH]: null, // [VaporLifecycleHooks.SERVER_PREFETCH]: null,
} }
instance.root = parent ? parent.root : instance instance.root = parent ? parent.root : instance
instance.scope = new BlockEffectScope(instance, parent && parent.scope)
initProps(instance, rawProps, !isFunction(component), once) initProps(instance, rawProps, !isFunction(component), once)
initSlots(instance, slots) initSlots(instance, slots)
instance.emit = emit.bind(null, instance) instance.emit = emit.bind(null, instance)

View File

@ -2,7 +2,7 @@ import { invokeArrayFns } from '@vue/shared'
import type { VaporLifecycleHooks } from './enums' import type { VaporLifecycleHooks } from './enums'
import { type ComponentInternalInstance, setCurrentInstance } from './component' import { type ComponentInternalInstance, setCurrentInstance } from './component'
import { queuePostFlushCb } from './scheduler' import { queuePostFlushCb } from './scheduler'
import { type DirectiveHookName, invokeDirectiveHook } from './directives' import type { DirectiveHookName } from './directives'
export function invokeLifecycle( export function invokeLifecycle(
instance: ComponentInternalInstance, instance: ComponentInternalInstance,
@ -24,8 +24,6 @@ export function invokeLifecycle(
} }
post ? queuePostFlushCb(fn) : fn() post ? queuePostFlushCb(fn) : fn()
} }
invokeDirectiveHook(instance, directive, instance.scope)
} }
function invokeSub() { function invokeSub() {

View File

@ -1,27 +1,6 @@
import { invokeArrayFns, isBuiltInDirective, isFunction } from '@vue/shared' import { isBuiltInDirective } from '@vue/shared'
import { import { type ComponentInternalInstance, currentInstance } from './component'
type ComponentInternalInstance,
currentInstance,
isVaporComponent,
setCurrentInstance,
} from './component'
import {
EffectFlags,
ReactiveEffect,
getCurrentScope,
pauseTracking,
resetTracking,
traverse,
} from '@vue/reactivity'
import {
VaporErrorCodes,
callWithAsyncErrorHandling,
callWithErrorHandling,
} from './errorHandling'
import { type SchedulerJob, queueJob, queuePostFlushCb } from './scheduler'
import { warn } from './warning' import { warn } from './warning'
import { type BlockEffectScope, isRenderEffectScope } from './blockEffectScope'
import { normalizeBlock } from './dom/element'
export type DirectiveModifiers<M extends string = string> = Record<M, boolean> export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
@ -98,168 +77,7 @@ export function withDirectives<T extends ComponentInternalInstance | Node>(
return nodeOrComponent return nodeOrComponent
} }
let node: Node // NOOP
if (isVaporComponent(nodeOrComponent)) {
const root = getComponentNode(nodeOrComponent)
if (!root) return nodeOrComponent
node = root
} else {
node = nodeOrComponent
}
let bindings: DirectiveBinding[]
const instance = currentInstance!
const parentScope = getCurrentScope() as BlockEffectScope
if (__DEV__ && !isRenderEffectScope(parentScope)) {
warn(`Directives should be used inside of RenderEffectScope.`)
}
const directivesMap = (parentScope.dirs ||= new Map())
if (!(bindings = directivesMap.get(node))) {
directivesMap.set(node, (bindings = []))
}
for (const directive of directives) {
let [dir, source, arg, modifiers] = directive
if (!dir) continue
if (isFunction(dir)) {
dir = {
mounted: dir,
updated: dir,
} satisfies ObjectDirective
}
const binding: DirectiveBinding = {
dir,
instance,
value: null, // set later
oldValue: undefined,
arg,
modifiers,
}
if (source) {
if (dir.deep) {
const deep = dir.deep === true ? undefined : dir.deep
const baseSource = source
source = () => traverse(baseSource(), deep)
}
const effect = new ReactiveEffect(() =>
callWithErrorHandling(
source!,
instance,
VaporErrorCodes.RENDER_FUNCTION,
),
)
const triggerRenderingUpdate = createRenderingUpdateTrigger(
instance,
effect,
)
effect.scheduler = () => queueJob(triggerRenderingUpdate)
binding.source = effect.run.bind(effect)
}
bindings.push(binding)
callDirectiveHook(node, binding, instance, 'created')
}
return nodeOrComponent return nodeOrComponent
} }
function getComponentNode(component: ComponentInternalInstance) {
if (!component.block) return
const nodes = normalizeBlock(component.block)
if (nodes.length !== 1) {
warn(
`Runtime directive used on component with non-element root node. ` +
`The directives will not function as intended.`,
)
return
}
return nodes[0]
}
export function invokeDirectiveHook(
instance: ComponentInternalInstance | null,
name: DirectiveHookName,
scope: BlockEffectScope,
): void {
const { dirs } = scope
if (name === 'mounted') scope.im = true
if (!dirs) return
const iterator = dirs.entries()
for (const [node, bindings] of iterator) {
for (const binding of bindings) {
callDirectiveHook(node, binding, instance, name)
}
}
}
function callDirectiveHook(
node: Node,
binding: DirectiveBinding,
instance: ComponentInternalInstance | null,
name: DirectiveHookName,
) {
if (name === 'beforeUpdate') binding.oldValue = binding.value
const { dir } = binding
const hook = dir[name]
if (!hook) return
const newValue = binding.source ? binding.source() : undefined
binding.value = newValue
// disable tracking inside all lifecycle hooks
// since they can potentially be called inside effects.
pauseTracking()
callWithAsyncErrorHandling(hook, instance, VaporErrorCodes.DIRECTIVE_HOOK, [
node,
binding,
])
resetTracking()
}
export function createRenderingUpdateTrigger(
instance: ComponentInternalInstance,
effect: ReactiveEffect,
): SchedulerJob {
job.id = instance.uid
return job
function job() {
if (!(effect.flags & EffectFlags.ACTIVE) || !effect.dirty) {
return
}
if (instance.isMounted && !instance.isUpdating) {
instance.isUpdating = true
const reset = setCurrentInstance(instance)
const { bu, u, scope } = instance
const { dirs } = scope
// beforeUpdate hook
if (bu) {
invokeArrayFns(bu)
}
invokeDirectiveHook(instance, 'beforeUpdate', scope)
queuePostFlushCb(() => {
instance.isUpdating = false
const reset = setCurrentInstance(instance)
if (dirs) {
invokeDirectiveHook(instance, 'updated', scope)
}
// updated hook
if (u) {
queuePostFlushCb(u)
}
reset()
})
reset()
}
}
}

View File

@ -1,155 +0,0 @@
import { ReactiveEffect, getCurrentScope } from '@vue/reactivity'
import {
type Directive,
type DirectiveHookName,
createRenderingUpdateTrigger,
invokeDirectiveHook,
} from './directives'
import { warn } from './warning'
import { type BlockEffectScope, isRenderEffectScope } from './blockEffectScope'
import { currentInstance } from './component'
import { VaporErrorCodes, callWithErrorHandling } from './errorHandling'
import { queueJob, queuePostFlushCb } from './scheduler'
/**
* used in createIf and createFor
* manage directives of child fragments in components.
*/
export function createChildFragmentDirectives(
anchor: Node,
getScopes: () => BlockEffectScope[],
source: () => any,
initCallback: (getValue: () => any) => void,
effectCallback: (getValue: () => any) => void,
once?: boolean,
): void {
let isTriggered = false
const instance = currentInstance!
const parentScope = getCurrentScope() as BlockEffectScope
if (__DEV__) {
if (!isRenderEffectScope(parentScope)) {
warn('child directives can only be added to a render effect scope')
}
if (!instance) {
warn('child directives can only be added in a component')
}
}
const callSourceWithErrorHandling = () =>
callWithErrorHandling(source, instance, VaporErrorCodes.RENDER_FUNCTION)
if (once) {
initCallback(callSourceWithErrorHandling)
return
}
const directiveBindingsMap = (parentScope.dirs ||= new Map())
const dir: Directive = {
beforeUpdate: onDirectiveBeforeUpdate,
beforeMount: () => invokeChildrenDirectives('beforeMount'),
mounted: () => invokeChildrenDirectives('mounted'),
beforeUnmount: () => invokeChildrenDirectives('beforeUnmount'),
unmounted: () => invokeChildrenDirectives('unmounted'),
}
directiveBindingsMap.set(anchor, [
{
dir,
instance,
value: null,
oldValue: undefined,
},
])
const effect = new ReactiveEffect(callSourceWithErrorHandling)
const triggerRenderingUpdate = createRenderingUpdateTrigger(instance, effect)
effect.scheduler = () => {
isTriggered = true
queueJob(triggerRenderingUpdate)
}
const getValue = () => effect.run()
initCallback(getValue)
function onDirectiveBeforeUpdate() {
if (isTriggered) {
isTriggered = false
effectCallback(getValue)
} else {
const scopes = getScopes()
for (const scope of scopes) {
invokeWithUpdate(scope)
}
return
}
}
function invokeChildrenDirectives(name: DirectiveHookName) {
const scopes = getScopes()
for (const scope of scopes) {
invokeDirectiveHook(instance, name, scope)
}
}
}
export function invokeWithMount(
scope: BlockEffectScope,
handler?: () => any,
): any {
if (isRenderEffectScope(scope.parent) && !scope.parent.im) {
return handler && handler()
}
return invokeWithDirsHooks(scope, 'mount', handler)
}
export function invokeWithUnmount(
scope: BlockEffectScope,
handler?: () => void,
): any {
try {
return invokeWithDirsHooks(scope, 'unmount', handler)
} finally {
scope.stop()
}
}
export function invokeWithUpdate(
scope: BlockEffectScope,
handler?: () => void,
): any {
return invokeWithDirsHooks(scope, 'update', handler)
}
const lifecycleMap = {
mount: ['beforeMount', 'mounted'],
update: ['beforeUpdate', 'updated'],
unmount: ['beforeUnmount', 'unmounted'],
} as const
function invokeWithDirsHooks(
scope: BlockEffectScope,
name: keyof typeof lifecycleMap,
handler?: () => any,
) {
const { dirs, it: instance } = scope
const [before, after] = lifecycleMap[name]
if (!dirs) {
const res = handler && handler()
if (name === 'mount') {
queuePostFlushCb(() => (scope.im = true))
}
return res
}
invokeDirectiveHook(instance, before, scope)
try {
if (handler) {
return handler()
}
} finally {
queuePostFlushCb(() => {
invokeDirectiveHook(instance, after, scope)
})
}
}

View File

@ -12,7 +12,6 @@ import {
queuePostFlushCb, queuePostFlushCb,
} from './scheduler' } from './scheduler'
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling' import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
import { invokeDirectiveHook } from './directives'
export function renderEffect(cb: () => void): void { export function renderEffect(cb: () => void): void {
const instance = getCurrentInstance() const instance = getCurrentInstance()
@ -58,24 +57,17 @@ export function renderEffect(cb: () => void): void {
if (instance && instance.isMounted && !instance.isUpdating) { if (instance && instance.isMounted && !instance.isUpdating) {
instance.isUpdating = true instance.isUpdating = true
const { bu, u, scope } = instance const { bu, u } = instance
const { dirs } = scope
// beforeUpdate hook // beforeUpdate hook
if (bu) { if (bu) {
invokeArrayFns(bu) invokeArrayFns(bu)
} }
if (dirs) {
invokeDirectiveHook(instance, 'beforeUpdate', scope)
}
effect.run() effect.run()
queuePostFlushCb(() => { queuePostFlushCb(() => {
instance.isUpdating = false instance.isUpdating = false
const reset = setCurrentInstance(instance) const reset = setCurrentInstance(instance)
if (dirs) {
invokeDirectiveHook(instance, 'updated', scope)
}
// updated hook // updated hook
if (u) { if (u) {
queuePostFlushCb(u) queuePostFlushCb(u)