feat: template ref handling + ref unmount

This commit is contained in:
Evan You 2019-08-19 18:06:20 -04:00
parent 10a2cf47ea
commit 8f9afdff64
3 changed files with 91 additions and 26 deletions

View File

@ -31,7 +31,14 @@ import {
FULL_PROPS FULL_PROPS
} from './patchFlags' } from './patchFlags'
import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler' import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
import { effect, stop, ReactiveEffectOptions } from '@vue/reactivity' import {
effect,
stop,
ReactiveEffectOptions,
isRef,
Ref,
toRaw
} from '@vue/reactivity'
import { resolveProps } from './componentProps' import { resolveProps } from './componentProps'
import { resolveSlots } from './componentSlots' import { resolveSlots } from './componentSlots'
import { import {
@ -80,7 +87,11 @@ export interface RendererOptions {
oldValue: any, oldValue: any,
isSVG: boolean, isSVG: boolean,
prevChildren?: VNode[], prevChildren?: VNode[],
unmountChildren?: (children: VNode[]) => void parentComponent?: ComponentInstance | null,
unmountChildren?: (
children: VNode[],
parentComponent: ComponentInstance | null
) => void
): void ): void
insert(el: HostNode, parent: HostNode, anchor?: HostNode): void insert(el: HostNode, parent: HostNode, anchor?: HostNode): void
remove(el: HostNode): void remove(el: HostNode): void
@ -121,7 +132,7 @@ export function createRenderer(options: RendererOptions) {
// patching & not same type, unmount old tree // patching & not same type, unmount old tree
if (n1 != null && !isSameType(n1, n2)) { if (n1 != null && !isSameType(n1, n2)) {
anchor = getNextHostNode(n1) anchor = getNextHostNode(n1)
unmount(n1, true) unmount(n1, parentComponent, true)
n1 = null n1 = null
} }
@ -237,7 +248,7 @@ export function createRenderer(options: RendererOptions) {
patchElement(n1, n2, parentComponent, isSVG, optimized) patchElement(n1, n2, parentComponent, isSVG, optimized)
} }
if (n2.ref !== null && parentComponent !== null) { if (n2.ref !== null && parentComponent !== null) {
setRef(n2.ref, parentComponent, n2.el) setRef(n2.ref, n1 && n1.ref, parentComponent, n2.el)
} }
} }
@ -306,7 +317,7 @@ export function createRenderer(options: RendererOptions) {
if (patchFlag & FULL_PROPS) { if (patchFlag & FULL_PROPS) {
// element props contain dynamic keys, full diff needed // element props contain dynamic keys, full diff needed
patchProps(el, n2, oldProps, newProps, isSVG) patchProps(el, n2, oldProps, newProps, parentComponent, isSVG)
} else { } else {
// class // class
// this flag is matched when the element has dynamic class bindings. // this flag is matched when the element has dynamic class bindings.
@ -343,6 +354,7 @@ export function createRenderer(options: RendererOptions) {
prev, prev,
isSVG, isSVG,
n1.children as VNode[], n1.children as VNode[],
parentComponent,
unmountChildren unmountChildren
) )
} }
@ -361,7 +373,7 @@ export function createRenderer(options: RendererOptions) {
} }
} else if (!optimized) { } else if (!optimized) {
// unoptimized, full diff // unoptimized, full diff
patchProps(el, n2, oldProps, newProps, isSVG) patchProps(el, n2, oldProps, newProps, parentComponent, isSVG)
} }
if (dynamicChildren != null) { if (dynamicChildren != null) {
@ -389,6 +401,7 @@ export function createRenderer(options: RendererOptions) {
vnode: VNode, vnode: VNode,
oldProps: any, oldProps: any,
newProps: any, newProps: any,
parentComponent: ComponentInstance | null,
isSVG: boolean isSVG: boolean
) { ) {
if (oldProps !== newProps) { if (oldProps !== newProps) {
@ -404,6 +417,7 @@ export function createRenderer(options: RendererOptions) {
prev, prev,
isSVG, isSVG,
vnode.children as VNode[], vnode.children as VNode[],
parentComponent,
unmountChildren unmountChildren
) )
} }
@ -419,6 +433,7 @@ export function createRenderer(options: RendererOptions) {
null, null,
isSVG, isSVG,
vnode.children as VNode[], vnode.children as VNode[],
parentComponent,
unmountChildren unmountChildren
) )
} }
@ -550,6 +565,7 @@ export function createRenderer(options: RendererOptions) {
if (n2.ref !== null && parentComponent !== null) { if (n2.ref !== null && parentComponent !== null) {
setRef( setRef(
n2.ref, n2.ref,
n1 && n1.ref,
parentComponent, parentComponent,
(n2.component as ComponentInstance).renderProxy (n2.component as ComponentInstance).renderProxy
) )
@ -690,7 +706,7 @@ export function createRenderer(options: RendererOptions) {
if (shapeFlag & TEXT_CHILDREN) { if (shapeFlag & TEXT_CHILDREN) {
// text children fast path // text children fast path
if (prevShapeFlag & ARRAY_CHILDREN) { if (prevShapeFlag & ARRAY_CHILDREN) {
unmountChildren(c1 as VNode[]) unmountChildren(c1 as VNode[], parentComponent)
} }
hostSetElementText(container, c2 as string) hostSetElementText(container, c2 as string)
} else { } else {
@ -719,7 +735,7 @@ export function createRenderer(options: RendererOptions) {
) )
} else { } else {
// c2 is null in this case // c2 is null in this case
unmountChildren(c1 as VNode[], true) unmountChildren(c1 as VNode[], parentComponent, true)
} }
} }
} }
@ -754,7 +770,7 @@ export function createRenderer(options: RendererOptions) {
} }
if (oldLength > newLength) { if (oldLength > newLength) {
// remove old // remove old
unmountChildren(c1, true, commonLength) unmountChildren(c1, parentComponent, true, commonLength)
} else { } else {
// mount new // mount new
mountChildren(c2, container, anchor, parentComponent, isSVG, commonLength) mountChildren(c2, container, anchor, parentComponent, isSVG, commonLength)
@ -855,7 +871,7 @@ export function createRenderer(options: RendererOptions) {
// i = 0, e1 = 0, e2 = -1 // i = 0, e1 = 0, e2 = -1
else if (i > e2) { else if (i > e2) {
while (i <= e1) { while (i <= e1) {
unmount(c1[i], true) unmount(c1[i], parentComponent, true)
i++ i++
} }
} }
@ -898,7 +914,7 @@ export function createRenderer(options: RendererOptions) {
const prevChild = c1[i] const prevChild = c1[i]
if (patched >= toBePatched) { if (patched >= toBePatched) {
// all new children have been patched so this can only be a removal // all new children have been patched so this can only be a removal
unmount(prevChild, true) unmount(prevChild, parentComponent, true)
continue continue
} }
let newIndex let newIndex
@ -914,7 +930,7 @@ export function createRenderer(options: RendererOptions) {
} }
} }
if (newIndex === undefined) { if (newIndex === undefined) {
unmount(prevChild, true) unmount(prevChild, parentComponent, true)
} else { } else {
newIndexToOldIndexMap[newIndex - s2] = i + 1 newIndexToOldIndexMap[newIndex - s2] = i + 1
if (newIndex >= maxNewIndexSoFar) { if (newIndex >= maxNewIndexSoFar) {
@ -981,28 +997,45 @@ export function createRenderer(options: RendererOptions) {
} }
} }
function unmount(vnode: VNode, doRemove?: boolean) { function unmount(
vnode: VNode,
parentComponent: ComponentInstance | null,
doRemove?: boolean
) {
// unset ref
if (vnode.ref !== null && parentComponent !== null) {
setRef(vnode.ref, null, parentComponent, null)
}
const instance = vnode.component const instance = vnode.component
if (instance != null) { if (instance != null) {
unmountComponent(instance, doRemove) unmountComponent(instance, doRemove)
return return
} }
const shouldRemoveChildren = vnode.type === Fragment && doRemove const shouldRemoveChildren = vnode.type === Fragment && doRemove
if (vnode.dynamicChildren != null) { if (vnode.dynamicChildren != null) {
unmountChildren(vnode.dynamicChildren, shouldRemoveChildren) unmountChildren(
vnode.dynamicChildren,
parentComponent,
shouldRemoveChildren
)
} else if (vnode.shapeFlag & ARRAY_CHILDREN) { } else if (vnode.shapeFlag & ARRAY_CHILDREN) {
unmountChildren(vnode.children as VNode[], shouldRemoveChildren) unmountChildren(
vnode.children as VNode[],
parentComponent,
shouldRemoveChildren
)
} }
if (doRemove) { if (doRemove) {
hostRemove(vnode.el) hostRemove(vnode.el)
if (vnode.anchor != null) hostRemove(vnode.anchor) if (vnode.anchor != null) hostRemove(vnode.anchor)
} }
} }
function unmountComponent( function unmountComponent(instance: ComponentInstance, doRemove?: boolean) {
{ bum, effects, update, subTree, um }: ComponentInstance, const { bum, effects, update, subTree, um } = instance
doRemove?: boolean
) {
// beforeUnmount hook // beforeUnmount hook
if (bum !== null) { if (bum !== null) {
invokeHooks(bum) invokeHooks(bum)
@ -1013,7 +1046,7 @@ export function createRenderer(options: RendererOptions) {
} }
} }
stop(update) stop(update)
unmount(subTree, doRemove) unmount(subTree, instance, doRemove)
// unmounted hook // unmounted hook
if (um !== null) { if (um !== null) {
queuePostFlushCb(um) queuePostFlushCb(um)
@ -1022,11 +1055,12 @@ export function createRenderer(options: RendererOptions) {
function unmountChildren( function unmountChildren(
children: VNode[], children: VNode[],
parentComponent: ComponentInstance | null,
doRemove?: boolean, doRemove?: boolean,
start: number = 0 start: number = 0
) { ) {
for (let i = start; i < children.length; i++) { for (let i = start; i < children.length; i++) {
unmount(children[i], doRemove) unmount(children[i], parentComponent, doRemove)
} }
} }
@ -1037,13 +1071,35 @@ export function createRenderer(options: RendererOptions) {
} }
function setRef( function setRef(
ref: string | Function, ref: string | Function | Ref<any>,
oldRef: string | Function | Ref<any> | null,
parent: ComponentInstance, parent: ComponentInstance,
value: HostNode | ComponentInstance value: HostNode | ComponentInstance | null
) { ) {
const refs = parent.refs === EMPTY_OBJ ? (parent.refs = {}) : parent.refs const refs = parent.refs === EMPTY_OBJ ? (parent.refs = {}) : parent.refs
const rawData = toRaw(parent.data)
// unset old ref
if (oldRef !== null && oldRef !== ref) {
if (isString(oldRef)) {
refs[oldRef] = null
const oldSetupRef = rawData[oldRef]
if (isRef(oldSetupRef)) {
oldSetupRef.value = null
}
} else if (isRef(oldRef)) {
oldRef.value = null
}
}
if (isString(ref)) { if (isString(ref)) {
const setupRef = rawData[ref]
if (isRef(setupRef)) {
setupRef.value = value
}
refs[ref] = value refs[ref] = value
} else if (isRef(ref)) {
ref.value = value
} else { } else {
if (__DEV__ && !isFunction(ref)) { if (__DEV__ && !isFunction(ref)) {
// TODO warn invalid ref type // TODO warn invalid ref type
@ -1055,7 +1111,7 @@ export function createRenderer(options: RendererOptions) {
return function render(vnode: VNode | null, dom: HostNode): VNode | null { return function render(vnode: VNode | null, dom: HostNode): VNode | null {
if (vnode == null) { if (vnode == null) {
if (dom._vnode) { if (dom._vnode) {
unmount(dom._vnode, true) unmount(dom._vnode, null, true)
} }
} else { } else {
patch(dom._vnode, vnode, dom) patch(dom._vnode, vnode, dom)

View File

@ -3,10 +3,11 @@ export function patchDOMProp(
key: string, key: string,
value: any, value: any,
prevChildren: any, prevChildren: any,
parentComponent: any,
unmountChildren: any unmountChildren: any
) { ) {
if ((key === 'innerHTML' || key === 'textContent') && prevChildren != null) { if ((key === 'innerHTML' || key === 'textContent') && prevChildren != null) {
unmountChildren(prevChildren) unmountChildren(prevChildren, parentComponent)
} }
el[key] = value el[key] = value
} }

View File

@ -13,6 +13,7 @@ export function patchProp(
prevValue: any, prevValue: any,
isSVG: boolean, isSVG: boolean,
prevChildren?: VNode[], prevChildren?: VNode[],
parentComponent?: any,
unmountChildren?: any unmountChildren?: any
) { ) {
switch (key) { switch (key) {
@ -27,7 +28,14 @@ export function patchProp(
if (isOn(key)) { if (isOn(key)) {
patchEvent(el, key.slice(2).toLowerCase(), prevValue, nextValue) patchEvent(el, key.slice(2).toLowerCase(), prevValue, nextValue)
} else if (!isSVG && key in el) { } else if (!isSVG && key in el) {
patchDOMProp(el, key, nextValue, prevChildren, unmountChildren) patchDOMProp(
el,
key,
nextValue,
prevChildren,
parentComponent,
unmountChildren
)
} else { } else {
patchAttr(el, key, nextValue, isSVG) patchAttr(el, key, nextValue, isSVG)
} }