feat: vnode hooks

This commit is contained in:
Evan You 2018-09-25 13:39:19 -04:00
parent 8cafad1fd3
commit 1106e2208d
4 changed files with 57 additions and 17 deletions

View File

@ -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))
) {

View File

@ -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) {

View File

@ -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

View File

@ -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])