wip: portal

This commit is contained in:
Evan You 2019-05-29 16:10:25 +08:00
parent 178c7c827e
commit 453cdcd600
4 changed files with 85 additions and 13 deletions

View File

@ -14,7 +14,14 @@ import {
createComponentInstance,
setupStatefulComponent
} from './component'
import { isString, isArray, EMPTY_OBJ, EMPTY_ARR } from '@vue/shared'
import {
isString,
isArray,
isFunction,
isObject,
EMPTY_OBJ,
EMPTY_ARR
} from '@vue/shared'
import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
import { effect, stop, ReactiveEffectOptions } from '@vue/observer'
@ -69,6 +76,7 @@ export interface RendererOptions {
setElementText(node: HostNode, text: string): void
parentNode(node: HostNode): HostNode | null
nextSibling(node: HostNode): HostNode | null
querySelector(selector: string): HostNode | null
}
export function createRenderer(options: RendererOptions) {
@ -82,7 +90,8 @@ export function createRenderer(options: RendererOptions) {
setText: hostSetText,
setElementText: hostSetElementText,
parentNode: hostParentNode,
nextSibling: hostNextSibling
nextSibling: hostNextSibling,
querySelector: hostQuerySelector
} = options
function patch(
@ -111,12 +120,15 @@ export function createRenderer(options: RendererOptions) {
processFragment(n1, n2, container, anchor, optimized)
break
case Portal:
// TODO
processPortal(n1, n2, container, anchor, optimized)
break
default:
if (isString(type)) {
processElement(n1, n2, container, anchor, optimized)
} else {
if (__DEV__ && !isFunction(type) && !isObject(type)) {
// TODO warn invalid node type
}
processComponent(n1, n2, container, anchor)
}
break
@ -340,6 +352,61 @@ export function createRenderer(options: RendererOptions) {
}
}
function processPortal(
n1: VNode | null,
n2: VNode,
container: HostNode,
anchor?: HostNode,
optimized?: boolean
) {
const targetSelector = n2.props && n2.props.target
if (n1 == null) {
const children = n2.children
const target = (n2.target = isString(targetSelector)
? hostQuerySelector(targetSelector)
: null)
if (target != null) {
if (isString(children)) {
hostSetElementText(target, children)
} else if (children != null) {
mountChildren(children, target)
}
} else {
// TODO warn missing or invalid target
}
} else {
// update content
const target = (n2.target = n1.target)
if (n2.patchFlag === TEXT) {
hostSetElementText(target, n2.children as string)
} else if (!optimized) {
patchChildren(n1, n2, target)
}
// target changed
if (targetSelector !== (n1.props && n1.props.target)) {
const nextTarget = (n2.target = isString(targetSelector)
? hostQuerySelector(targetSelector)
: null)
if (nextTarget != null) {
// move content
const children = n2.children
if (isString(children)) {
hostSetElementText(target, '')
hostSetElementText(nextTarget, children)
} else if (children != null) {
for (let i = 0; i < children.length; i++) {
move(children[i] as VNode, nextTarget, null)
}
}
} else {
// TODO warn missing or invalid target
}
}
}
// insert an empty node as the placeholder for the portal
processEmptyNode(n1, n2, container, anchor)
}
function processComponent(
n1: VNode | null,
n2: VNode,
@ -409,8 +476,9 @@ export function createRenderer(options: RendererOptions) {
patch(
prevTree,
nextTree,
container || hostParentNode(prevTree.el),
anchor || getNextHostNode(prevTree)
// may have moved
hostParentNode(prevTree.el),
getNextHostNode(prevTree)
)
if (next != null) {
next.el = nextTree.el

View File

@ -4,8 +4,7 @@ export {
createBlock,
createVNode,
Fragment,
Text,
Empty
Portal
} from './vnode'
export {

View File

@ -1,4 +1,4 @@
import { isArray, isFunction } from '@vue/shared'
import { isArray, isFunction, EMPTY_ARR } from '@vue/shared'
import { ComponentInstance } from './component'
import { HostNode } from './createRenderer'
@ -29,6 +29,7 @@ export interface VNode {
// DOM
el: HostNode | null
anchor: HostNode | null // fragment anchor
target: HostNode | null // portal target
// optimization only
patchFlag: number | null
@ -59,7 +60,7 @@ export function createBlock(
shouldTrack = true
const trackedNodes = blockStack.pop()
vnode.dynamicChildren =
trackedNodes && trackedNodes.length ? trackedNodes : null
trackedNodes && trackedNodes.length ? trackedNodes : EMPTY_ARR
// a block is always going to be patched
trackDynamicNode(vnode)
return vnode
@ -81,6 +82,7 @@ export function createVNode(
component: null,
el: null,
anchor: null,
target: null,
patchFlag,
dynamicProps,
dynamicChildren: null

View File

@ -1,6 +1,7 @@
import { RendererOptions } from '@vue/runtime-core'
import { patchProp } from './patchProp'
const doc = document
const svgNS = 'http://www.w3.org/2000/svg'
export const DOMRendererOptions: RendererOptions = {
@ -23,11 +24,11 @@ export const DOMRendererOptions: RendererOptions = {
},
createElement: (tag: string, isSVG?: boolean): Element =>
isSVG ? document.createElementNS(svgNS, tag) : document.createElement(tag),
isSVG ? doc.createElementNS(svgNS, tag) : doc.createElement(tag),
createText: (text: string): Text => document.createTextNode(text),
createText: (text: string): Text => doc.createTextNode(text),
createComment: (text: string): Comment => document.createComment(text),
createComment: (text: string): Comment => doc.createComment(text),
setText: (node: Text, text: string) => {
node.nodeValue = text
@ -39,5 +40,7 @@ export const DOMRendererOptions: RendererOptions = {
parentNode: (node: Node): Node | null => node.parentNode,
nextSibling: (node: Node): Node | null => node.nextSibling
nextSibling: (node: Node): Node | null => node.nextSibling,
querySelector: (selector: string): Node | null => doc.querySelector(selector)
}