feat: vnode hooks
This commit is contained in:
parent
8cafad1fd3
commit
1106e2208d
@ -1,4 +1,4 @@
|
||||
import { EMPTY_OBJ, nativeOnRE } from './utils'
|
||||
import { EMPTY_OBJ, nativeOnRE, vnodeHookRE } from './utils'
|
||||
import {
|
||||
Component,
|
||||
ComponentClass,
|
||||
@ -92,14 +92,15 @@ export function resolveProps(
|
||||
if (key === 'key' || key === 'ref' || key === 'slots') {
|
||||
continue
|
||||
}
|
||||
// class, style & nativeOn are always extracted into a separate `attrs`
|
||||
// object, which can then be merged onto child component root.
|
||||
// in addition, if the component has explicitly declared props, then
|
||||
// class, style, nativeOn & directive hooks are always extracted into a
|
||||
// separate `attrs` object, which can then be merged onto child component
|
||||
// root. in addition, if the component has explicitly declared props, then
|
||||
// any non-matching props are extracted into `attrs` as well.
|
||||
let isNativeOn
|
||||
if (
|
||||
key === 'class' ||
|
||||
key === 'style' ||
|
||||
vnodeHookRE.test(key) ||
|
||||
(isNativeOn = nativeOnRE.test(key)) ||
|
||||
(hasDeclaredProps && !options.hasOwnProperty(key))
|
||||
) {
|
||||
|
@ -156,13 +156,14 @@ export function createRenderer(options: RendererOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
// lifecycle hooks -----------------------------------------------------------
|
||||
// lifecycle lifecycleHooks -----------------------------------------------------------
|
||||
|
||||
const hooks: Function[] = []
|
||||
const lifecycleHooks: Function[] = []
|
||||
const vnodeUpdatedHooks: Function[] = []
|
||||
|
||||
function flushHooks() {
|
||||
let fn
|
||||
while ((fn = hooks.shift())) {
|
||||
while ((fn = lifecycleHooks.shift())) {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
@ -222,6 +223,9 @@ export function createRenderer(options: RendererOptions) {
|
||||
for (const key in data) {
|
||||
patchData(el, key, null, data[key], null, vnode, isSVG)
|
||||
}
|
||||
if (data.vnodeBeforeMount) {
|
||||
data.vnodeBeforeMount(vnode)
|
||||
}
|
||||
}
|
||||
if (childFlags !== ChildrenFlags.NO_CHILDREN) {
|
||||
const hasSVGChildren = isSVG && tag !== 'foreignObject'
|
||||
@ -243,11 +247,16 @@ export function createRenderer(options: RendererOptions) {
|
||||
if (ref) {
|
||||
mountRef(ref, el)
|
||||
}
|
||||
if (data != null && data.vnodeMounted) {
|
||||
lifecycleHooks.push(() => {
|
||||
data.vnodeMounted(vnode)
|
||||
})
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
function mountRef(ref: Ref, el: RenderNode | MountedComponent) {
|
||||
hooks.push(() => {
|
||||
lifecycleHooks.push(() => {
|
||||
ref(el)
|
||||
})
|
||||
}
|
||||
@ -438,10 +447,14 @@ export function createRenderer(options: RendererOptions) {
|
||||
}
|
||||
|
||||
const el = (nextVNode.el = prevVNode.el) as RenderNode
|
||||
|
||||
// data
|
||||
const prevData = prevVNode.data
|
||||
const nextData = nextVNode.data
|
||||
|
||||
if (nextData != null && nextData.vnodeBeforeUpdate) {
|
||||
nextData.vnodeBeforeUpdate(nextVNode, prevVNode)
|
||||
}
|
||||
|
||||
// patch data
|
||||
if (prevData !== nextData) {
|
||||
const prevDataOrEmpty = prevData || EMPTY_OBJ
|
||||
const nextDataOrEmpty = nextData || EMPTY_OBJ
|
||||
@ -483,6 +496,12 @@ export function createRenderer(options: RendererOptions) {
|
||||
isSVG && nextVNode.tag !== 'foreignObject',
|
||||
null
|
||||
)
|
||||
|
||||
if (nextData != null && nextData.vnodeUpdated) {
|
||||
vnodeUpdatedHooks.push(() => {
|
||||
nextData.vnodeUpdated(nextVNode, prevVNode)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function patchComponent(
|
||||
@ -1064,12 +1083,19 @@ export function createRenderer(options: RendererOptions) {
|
||||
// unmounting ----------------------------------------------------------------
|
||||
|
||||
function unmount(vnode: VNode) {
|
||||
const { flags, children, childFlags, ref } = vnode
|
||||
if (flags & VNodeFlags.ELEMENT || flags & VNodeFlags.FRAGMENT) {
|
||||
const { flags, data, children, childFlags, ref } = vnode
|
||||
const isElement = flags & VNodeFlags.ELEMENT
|
||||
if (isElement || flags & VNodeFlags.FRAGMENT) {
|
||||
if (isElement && data != null && data.vnodeBeforeUnmount) {
|
||||
data.vnodeBeforeUnmount(vnode)
|
||||
}
|
||||
unmountChildren(children as VNodeChildren, childFlags)
|
||||
if (teardownVNode !== void 0) {
|
||||
teardownVNode(vnode)
|
||||
}
|
||||
if (isElement && data != null && data.vnodeUnmounted) {
|
||||
data.vnodeUnmounted(vnode)
|
||||
}
|
||||
} else if (flags & VNodeFlags.COMPONENT) {
|
||||
if (flags & VNodeFlags.COMPONENT_STATEFUL) {
|
||||
unmountComponentInstance(children as MountedComponent)
|
||||
@ -1187,7 +1213,7 @@ export function createRenderer(options: RendererOptions) {
|
||||
mountRef(ref, instance)
|
||||
}
|
||||
if (instance.mounted) {
|
||||
hooks.push(() => {
|
||||
lifecycleHooks.push(() => {
|
||||
;(instance as any).mounted.call(instance.$proxy)
|
||||
})
|
||||
}
|
||||
@ -1227,10 +1253,20 @@ export function createRenderer(options: RendererOptions) {
|
||||
// will be added to the queue AFTER the parent's, but they should be
|
||||
// invoked BEFORE the parent's. Therefore we add them to the head of the
|
||||
// queue instead.
|
||||
hooks.unshift(() => {
|
||||
lifecycleHooks.unshift(() => {
|
||||
;(instance as any).updated.call(instance.$proxy, nextVNode)
|
||||
})
|
||||
}
|
||||
|
||||
if (vnodeUpdatedHooks.length > 0) {
|
||||
const vnodeUpdatedHooksForCurrentInstance = vnodeUpdatedHooks.slice()
|
||||
vnodeUpdatedHooks.length = 0
|
||||
lifecycleHooks.unshift(() => {
|
||||
for (let i = 0; i < vnodeUpdatedHooksForCurrentInstance.length; i++) {
|
||||
vnodeUpdatedHooksForCurrentInstance[i]()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function unmountComponentInstance(instance: MountedComponent) {
|
||||
|
@ -4,7 +4,9 @@ export const NOOP = () => {}
|
||||
|
||||
export const onRE = /^on/
|
||||
export const nativeOnRE = /^nativeOn/
|
||||
export const reservedPropRE = /^(?:key|ref|slots)$|^nativeOn/
|
||||
export const vnodeHookRE = /^vnode/
|
||||
export const handlersRE = /^on|^nativeOn|^vnode/
|
||||
export const reservedPropRE = /^(?:key|ref|slots)$|^nativeOn|^vnode/
|
||||
|
||||
export function normalizeStyle(
|
||||
value: any
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
} from './component'
|
||||
import { VNodeFlags, ChildrenFlags } from './flags'
|
||||
import { createComponentClassFromOptions } from './componentUtils'
|
||||
import { normalizeClass, normalizeStyle, onRE, nativeOnRE } from './utils'
|
||||
import { normalizeClass, normalizeStyle, handlersRE } from './utils'
|
||||
|
||||
// Vue core is platform agnostic, so we are not using Element for "DOM" nodes.
|
||||
export interface RenderNode {
|
||||
@ -270,7 +270,8 @@ export function cloneVNode(vnode: VNode, extraData?: VNodeData): VNode {
|
||||
clonedData.class = normalizeClass([clonedData.class, extraData.class])
|
||||
} else if (key === 'style') {
|
||||
clonedData.style = normalizeStyle([clonedData.style, extraData.style])
|
||||
} else if (onRE.test(key) || nativeOnRE.test(key)) {
|
||||
} else if (handlersRE.test(key)) {
|
||||
// on*, nativeOn*, vnode*
|
||||
const existing = clonedData[key]
|
||||
clonedData[key] = existing
|
||||
? [].concat(existing, extraData[key])
|
||||
|
Loading…
Reference in New Issue
Block a user