Skip to content

Commit 5155ed2

Browse files
committed
fix(runtime-core): should run vnode hook with component
fix vuejs#684
1 parent eb9e089 commit 5155ed2

File tree

3 files changed

+141
-2
lines changed

3 files changed

+141
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { h } from '../src/h'
2+
import { nextTick, nodeOps, ref, render } from '@vue/runtime-test'
3+
4+
describe('renderer: vnode hook', () => {
5+
test('element', async () => {
6+
const onVnodeBeforeMount = jest.fn()
7+
const onVnodeMounted = jest.fn()
8+
const onVnodeBeforeUpdate = jest.fn()
9+
const onVnodeUpdated = jest.fn()
10+
const onVnodeBeforeUnmount = jest.fn()
11+
const onVnodeUnmounted = jest.fn()
12+
const root = nodeOps.createElement('div')
13+
const count = ref(0)
14+
15+
const App = () =>
16+
h('div', {
17+
onVnodeBeforeMount,
18+
onVnodeMounted,
19+
onVnodeBeforeUpdate,
20+
onVnodeUpdated,
21+
onVnodeBeforeUnmount,
22+
onVnodeUnmounted,
23+
count: count.value
24+
})
25+
26+
render(h(App), root)
27+
await nextTick()
28+
expect(onVnodeBeforeMount).toBeCalled()
29+
expect(onVnodeMounted).toBeCalled()
30+
31+
count.value++
32+
await nextTick()
33+
expect(onVnodeBeforeUpdate).toBeCalled()
34+
expect(onVnodeUpdated).toBeCalled()
35+
36+
render(null, root)
37+
await nextTick()
38+
expect(onVnodeBeforeUnmount).toBeCalled()
39+
expect(onVnodeUnmounted).toBeCalled()
40+
})
41+
42+
test('component', async () => {
43+
const Comp = () => h('div')
44+
const onVnodeBeforeMount = jest.fn()
45+
const onVnodeMounted = jest.fn()
46+
const onVnodeBeforeUpdate = jest.fn()
47+
const onVnodeUpdated = jest.fn()
48+
const onVnodeBeforeUnmount = jest.fn()
49+
const onVnodeUnmounted = jest.fn()
50+
const root = nodeOps.createElement('div')
51+
const count = ref(0)
52+
53+
const App = () =>
54+
h(Comp, {
55+
onVnodeBeforeMount,
56+
onVnodeMounted,
57+
onVnodeBeforeUpdate,
58+
onVnodeUpdated,
59+
onVnodeBeforeUnmount,
60+
onVnodeUnmounted,
61+
count: count.value
62+
})
63+
64+
render(h(App), root)
65+
await nextTick()
66+
expect(onVnodeBeforeMount).toBeCalled()
67+
expect(onVnodeMounted).toBeCalled()
68+
69+
count.value++
70+
await nextTick()
71+
expect(onVnodeBeforeUpdate).toBeCalled()
72+
expect(onVnodeUpdated).toBeCalled()
73+
74+
render(null, root)
75+
await nextTick()
76+
expect(onVnodeBeforeUnmount).toBeCalled()
77+
expect(onVnodeUnmounted).toBeCalled()
78+
})
79+
})

packages/runtime-core/src/components/Suspense.ts

+2
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ export interface SuspenseBoundary<
220220
setupRenderEffect: (
221221
instance: ComponentInternalInstance,
222222
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
223+
parentComponent: ComponentInternalInstance | null,
223224
initialVNode: VNode<HostNode, HostElement>,
224225
container: HostElement,
225226
anchor: HostNode | null,
@@ -420,6 +421,7 @@ function createSuspenseBoundary<HostNode, HostElement>(
420421
setupRenderEffect(
421422
instance,
422423
suspense,
424+
parentComponent,
423425
vnode,
424426
// component may have been moved before resolve
425427
parentNode(instance.subTree.el)!,

packages/runtime-core/src/renderer.ts

+60-2
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,7 @@ export function createRenderer<
970970
setupRenderEffect(
971971
instance,
972972
parentSuspense,
973+
parentComponent,
973974
initialVNode,
974975
container,
975976
anchor,
@@ -984,6 +985,7 @@ export function createRenderer<
984985
function setupRenderEffect(
985986
instance: ComponentInternalInstance,
986987
parentSuspense: HostSuspenseBoundary | null,
988+
parentComponent: ComponentInternalInstance | null,
987989
initialVNode: HostVNode,
988990
container: HostElement,
989991
anchor: HostNode | null,
@@ -993,16 +995,29 @@ export function createRenderer<
993995
instance.update = effect(function componentEffect() {
994996
if (!instance.isMounted) {
995997
const subTree = (instance.subTree = renderComponentRoot(instance))
998+
const { props } = instance.vnode
996999
// beforeMount hook
9971000
if (instance.bm !== null) {
9981001
invokeHooks(instance.bm)
9991002
}
1003+
if (props && props.onVnodeBeforeMount != null) {
1004+
invokeDirectiveHook(
1005+
props.onVnodeBeforeMount,
1006+
parentComponent,
1007+
subTree
1008+
)
1009+
}
10001010
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)
10011011
initialVNode.el = subTree.el
10021012
// mounted hook
10031013
if (instance.m !== null) {
10041014
queuePostRenderEffect(instance.m, parentSuspense)
10051015
}
1016+
if (props && props.onVnodeMounted != null) {
1017+
queuePostRenderEffect(() => {
1018+
invokeDirectiveHook(props.onVnodeMounted!, parentComponent, subTree)
1019+
}, parentSuspense)
1020+
}
10061021
// activated hook for keep-alive roots.
10071022
if (
10081023
instance.a !== null &&
@@ -1016,6 +1031,7 @@ export function createRenderer<
10161031
// This is triggered by mutation of component's own state (next: null)
10171032
// OR parent calling processComponent (next: HostVNode)
10181033
const { next } = instance
1034+
const { props } = instance.vnode
10191035

10201036
if (__DEV__) {
10211037
pushWarningContext(next || instance.vnode)
@@ -1031,6 +1047,14 @@ export function createRenderer<
10311047
if (instance.bu !== null) {
10321048
invokeHooks(instance.bu)
10331049
}
1050+
if (props && props.onVnodeBeforeUpdate != null) {
1051+
invokeDirectiveHook(
1052+
props.onVnodeBeforeUpdate,
1053+
parentComponent,
1054+
nextTree,
1055+
prevTree
1056+
)
1057+
}
10341058
// reset refs
10351059
// only needed if previous patch had refs
10361060
if (instance.refs !== EMPTY_OBJ) {
@@ -1058,6 +1082,16 @@ export function createRenderer<
10581082
if (instance.u !== null) {
10591083
queuePostRenderEffect(instance.u, parentSuspense)
10601084
}
1085+
if (props && props.onVnodeUpdated != null) {
1086+
queuePostRenderEffect(() => {
1087+
invokeDirectiveHook(
1088+
props.onVnodeUpdated!,
1089+
parentComponent,
1090+
nextTree,
1091+
prevTree
1092+
)
1093+
}, parentSuspense)
1094+
}
10611095

10621096
if (__DEV__) {
10631097
popWarningContext()
@@ -1539,7 +1573,12 @@ export function createRenderer<
15391573
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
15401574
;(parentComponent!.sink as KeepAliveSink).deactivate(vnode)
15411575
} else {
1542-
unmountComponent(vnode.component!, parentSuspense, doRemove)
1576+
unmountComponent(
1577+
vnode.component!,
1578+
parentSuspense,
1579+
parentComponent,
1580+
doRemove
1581+
)
15431582
}
15441583
return
15451584
}
@@ -1621,17 +1660,31 @@ export function createRenderer<
16211660
function unmountComponent(
16221661
instance: ComponentInternalInstance,
16231662
parentSuspense: HostSuspenseBoundary | null,
1663+
parentComponent: ComponentInternalInstance | null,
16241664
doRemove?: boolean
16251665
) {
16261666
if (__HMR__ && instance.type.__hmrId != null) {
16271667
unregisterHMR(instance)
16281668
}
16291669

1630-
const { bum, effects, update, subTree, um, da, isDeactivated } = instance
1670+
const {
1671+
bum,
1672+
effects,
1673+
update,
1674+
subTree,
1675+
um,
1676+
da,
1677+
isDeactivated,
1678+
vnode
1679+
} = instance
1680+
const { props } = vnode
16311681
// beforeUnmount hook
16321682
if (bum !== null) {
16331683
invokeHooks(bum)
16341684
}
1685+
if (props && props.onVnodeBeforeUnmount != null) {
1686+
invokeDirectiveHook(props.onVnodeBeforeUnmount, parentComponent, subTree)
1687+
}
16351688
if (effects !== null) {
16361689
for (let i = 0; i < effects.length; i++) {
16371690
stop(effects[i])
@@ -1647,6 +1700,11 @@ export function createRenderer<
16471700
if (um !== null) {
16481701
queuePostRenderEffect(um, parentSuspense)
16491702
}
1703+
if (props && props.onVnodeUnmounted != null) {
1704+
queuePostRenderEffect(() => {
1705+
invokeDirectiveHook(props.onVnodeUnmounted!, parentComponent, subTree)
1706+
}, parentSuspense)
1707+
}
16501708
// deactivated hook
16511709
if (
16521710
da !== null &&

0 commit comments

Comments
 (0)