vue3-yuanma/packages/runtime-core/src/vnode.ts

246 lines
6.6 KiB
TypeScript
Raw Normal View History

2019-06-01 02:14:49 +08:00
import { isArray, isFunction, isString, isObject, EMPTY_ARR } from '@vue/shared'
2019-05-28 19:36:15 +08:00
import { ComponentInstance } from './component'
2019-05-28 17:19:47 +08:00
import { HostNode } from './createRenderer'
2019-05-31 18:07:43 +08:00
import { RawSlots } from './componentSlots'
2019-06-01 17:44:06 +08:00
import { CLASS } from './patchFlags'
import {
ELEMENT,
FUNCTIONAL_COMPONENT,
STATEFUL_COMPONENT,
TEXT_CHILDREN,
ARRAY_CHILDREN,
SLOTS_CHILDREN
} from './shapeFlags'
2019-05-28 10:28:25 +08:00
2019-05-25 23:51:20 +08:00
export const Fragment = Symbol('Fragment')
export const Text = Symbol('Text')
export const Empty = Symbol('Empty')
2019-05-28 17:19:47 +08:00
export const Portal = Symbol('Portal')
2018-09-19 23:35:38 +08:00
2019-05-25 23:51:20 +08:00
type VNodeTypes =
| string
| Function
2019-05-28 17:19:47 +08:00
| Object
2019-05-25 23:51:20 +08:00
| typeof Fragment
| typeof Text
| typeof Empty
2018-09-19 23:35:38 +08:00
2019-05-28 17:19:47 +08:00
type VNodeChildAtom = VNode | string | number | null | void
export interface VNodeChildren extends Array<VNodeChildren | VNodeChildAtom> {}
export type VNodeChild = VNodeChildAtom | VNodeChildren
2018-10-13 07:49:41 +08:00
2019-05-31 18:07:43 +08:00
export type NormalizedChildren = string | VNodeChildren | RawSlots | null
2019-05-25 23:51:20 +08:00
export interface VNode {
type: VNodeTypes
props: { [key: string]: any } | null
key: string | number | null
2019-05-31 18:07:43 +08:00
children: NormalizedChildren
2019-05-28 19:36:15 +08:00
component: ComponentInstance | null
2019-05-28 13:27:31 +08:00
// DOM
2019-05-28 17:19:47 +08:00
el: HostNode | null
anchor: HostNode | null // fragment anchor
2019-05-29 16:10:25 +08:00
target: HostNode | null // portal target
2019-05-28 13:27:31 +08:00
// optimization only
2019-06-02 16:35:19 +08:00
shapeFlag: number
2019-06-01 17:43:41 +08:00
patchFlag: number
2019-05-25 23:51:20 +08:00
dynamicProps: string[] | null
dynamicChildren: VNode[] | null
2018-10-13 07:49:41 +08:00
}
2019-05-30 21:24:40 +08:00
// Since v-if and v-for are the two possible ways node structure can dynamically
// change, once we consider v-if branches and each v-for fragment a block, we
// can divide a template into nested blocks, and within each block the node
// structure would be stable. This allows us to skip most children diffing
// and only worry about the dynamic nodes (indicated by patch flags).
2019-05-27 13:48:40 +08:00
const blockStack: (VNode[] | null)[] = []
2018-09-19 23:35:38 +08:00
2019-05-30 21:24:40 +08:00
// Open a block.
// This must be called before `createBlock`. It cannot be part of `createBlock`
// because the children of the block are evaluated before `createBlock` itself
// is called. The generated code typically looks like this:
//
// function render() {
// return (openBlock(),createBlock('div', null, [...]))
// }
2019-05-27 13:48:40 +08:00
export function openBlock(disableTrackng?: boolean) {
blockStack.push(disableTrackng ? null : [])
2019-05-25 23:51:20 +08:00
}
2018-10-13 07:49:41 +08:00
2019-05-25 23:51:20 +08:00
let shouldTrack = true
2018-10-13 07:49:41 +08:00
2019-05-30 21:24:40 +08:00
// Create a block root vnode. Takes the same exact arguments as `createVNode`.
// A block root keeps track of dynamic nodes within the block in the
// `dynamicChildren` array.
2019-05-25 23:51:20 +08:00
export function createBlock(
type: VNodeTypes,
props?: { [key: string]: any } | null,
children?: any,
patchFlag?: number,
dynamicProps?: string[]
): VNode {
// avoid a block with optFlag tracking itself
shouldTrack = false
const vnode = createVNode(type, props, children, patchFlag, dynamicProps)
shouldTrack = true
2019-05-27 13:48:40 +08:00
const trackedNodes = blockStack.pop()
vnode.dynamicChildren =
2019-05-29 16:10:25 +08:00
trackedNodes && trackedNodes.length ? trackedNodes : EMPTY_ARR
2019-05-25 23:51:20 +08:00
// a block is always going to be patched
trackDynamicNode(vnode)
return vnode
2018-10-13 01:42:19 +08:00
}
2019-05-25 23:51:20 +08:00
export function createVNode(
type: VNodeTypes,
2019-06-01 02:14:49 +08:00
props: { [key: string]: any } | null | 0 = null,
2019-05-25 23:51:20 +08:00
children: any = null,
2019-06-01 17:43:41 +08:00
patchFlag: number = 0,
2019-05-25 23:51:20 +08:00
dynamicProps: string[] | null = null
): VNode {
2019-06-01 17:43:41 +08:00
// Allow passing 0 for props, this can save bytes on generated code.
props = props || null
// normalize children
children = normalizeChildren(children) as NormalizedChildren
2019-06-02 16:35:19 +08:00
const typeFlag = isString(type)
2019-06-02 16:35:19 +08:00
? ELEMENT
: isFunction(type)
? FUNCTIONAL_COMPONENT
: isObject(type)
? STATEFUL_COMPONENT
: 0
const childFlag = isString(children)
? TEXT_CHILDREN
: isArray(children)
? ARRAY_CHILDREN
: isObject(children)
? SLOTS_CHILDREN
: 0
2019-05-25 23:51:20 +08:00
const vnode: VNode = {
type,
2019-06-01 17:43:41 +08:00
props,
2019-05-25 23:51:20 +08:00
key: props && props.key,
children,
2019-05-28 13:27:31 +08:00
component: null,
2019-05-27 13:48:40 +08:00
el: null,
anchor: null,
2019-05-29 16:10:25 +08:00
target: null,
shapeFlag: typeFlag | childFlag,
2019-05-25 23:51:20 +08:00
patchFlag,
dynamicProps,
dynamicChildren: null
2018-09-19 23:35:38 +08:00
}
2019-06-01 17:43:41 +08:00
// class & style normalization.
if (props !== null) {
// class normalization only needed if the vnode isn't generated by
// compiler-optimized code
if (props.class != null && !(patchFlag & CLASS)) {
props.class = normalizeClass(props.class)
}
if (props.style != null) {
props.style = normalizeStyle(props.style)
}
}
2019-05-30 21:24:40 +08:00
// presence of a patch flag indicates this node is dynamic
2019-06-01 02:14:49 +08:00
// component nodes also should always be tracked, because even if the
// component doesn't need to update, it needs to persist the instance on to
// the next vnode so that it can be properly unmounted later.
2019-06-02 16:35:19 +08:00
if (
shouldTrack &&
(patchFlag ||
typeFlag & STATEFUL_COMPONENT ||
typeFlag & FUNCTIONAL_COMPONENT)
2019-06-02 16:35:19 +08:00
) {
2019-05-25 23:51:20 +08:00
trackDynamicNode(vnode)
2018-09-25 07:11:14 +08:00
}
2019-06-01 17:43:41 +08:00
2019-05-25 23:51:20 +08:00
return vnode
}
2018-09-19 23:35:38 +08:00
2019-05-25 23:51:20 +08:00
function trackDynamicNode(vnode: VNode) {
const currentBlockDynamicNodes = blockStack[blockStack.length - 1]
2019-05-27 13:48:40 +08:00
if (currentBlockDynamicNodes != null) {
2019-05-25 23:51:20 +08:00
currentBlockDynamicNodes.push(vnode)
2018-09-19 23:35:38 +08:00
}
2019-05-25 23:51:20 +08:00
}
2018-09-19 23:35:38 +08:00
2019-05-25 23:51:20 +08:00
export function cloneVNode(vnode: VNode): VNode {
// TODO
return vnode
2019-05-25 23:51:20 +08:00
}
2019-05-28 13:27:31 +08:00
2019-05-28 17:19:47 +08:00
export function normalizeVNode(child: VNodeChild): VNode {
2019-05-28 13:27:31 +08:00
if (child == null) {
// empty placeholder
return createVNode(Empty)
} else if (isArray(child)) {
// fragment
return createVNode(Fragment, null, child)
} else if (typeof child === 'object') {
2019-05-31 18:07:43 +08:00
// already vnode, this should be the most common since compiled templates
// always produce all-vnode children arrays
2019-05-28 13:27:31 +08:00
return child as VNode
} else {
// primitive types
return createVNode(Text, null, child + '')
}
}
2019-05-31 18:07:43 +08:00
export function normalizeChildren(children: unknown): NormalizedChildren {
if (children == null) {
return null
} else if (isArray(children)) {
return children
} else if (typeof children === 'object') {
return children as RawSlots
} else if (isFunction(children)) {
return { default: children }
} else {
return isString(children) ? children : children + ''
}
}
2019-06-01 17:43:41 +08:00
function normalizeStyle(
value: unknown
): Record<string, string | number> | void {
if (isArray(value)) {
const res: Record<string, string | number> = {}
for (let i = 0; i < value.length; i++) {
const normalized = normalizeStyle(value[i])
if (normalized) {
for (const key in normalized) {
res[key] = normalized[key]
}
}
}
return res
} else if (isObject(value)) {
return value
}
}
2019-06-01 17:47:19 +08:00
export function normalizeClass(value: unknown): string {
2019-06-01 17:43:41 +08:00
let res = ''
if (isString(value)) {
res = value
} else if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
res += normalizeClass(value[i]) + ' '
}
} else if (isObject(value)) {
for (const name in value) {
if (value[name]) {
res += name + ' '
}
}
}
return res.trim()
}