wip: minimal component implementation
This commit is contained in:
parent
28a0c50357
commit
b69ea00f5c
@ -1 +1,46 @@
|
||||
import { VNode, normalizeVNode } from './vnode'
|
||||
|
||||
export class Component {}
|
||||
|
||||
export function renderComponentRoot(instance: any): VNode {
|
||||
return normalizeVNode(instance.render(instance.vnode.props))
|
||||
}
|
||||
|
||||
export function shouldUpdateComponent(
|
||||
prevVNode: VNode,
|
||||
nextVNode: VNode
|
||||
): boolean {
|
||||
const { props: prevProps } = prevVNode
|
||||
const { props: nextProps } = nextVNode
|
||||
|
||||
// TODO handle slots
|
||||
// If has different slots content, or has non-compiled slots,
|
||||
// the child needs to be force updated.
|
||||
// if (
|
||||
// prevChildFlags !== nextChildFlags ||
|
||||
// (nextChildFlags & ChildrenFlags.DYNAMIC_SLOTS) > 0
|
||||
// ) {
|
||||
// return true
|
||||
// }
|
||||
|
||||
if (prevProps === nextProps) {
|
||||
return false
|
||||
}
|
||||
if (prevProps === null) {
|
||||
return nextProps !== null
|
||||
}
|
||||
if (nextProps === null) {
|
||||
return prevProps !== null
|
||||
}
|
||||
const nextKeys = Object.keys(nextProps)
|
||||
if (nextKeys.length !== Object.keys(prevProps).length) {
|
||||
return true
|
||||
}
|
||||
for (let i = 0; i < nextKeys.length; i++) {
|
||||
const key = nextKeys[i]
|
||||
if (nextProps[key] !== prevProps[key]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -13,12 +13,14 @@ import {
|
||||
Text,
|
||||
Fragment,
|
||||
Empty,
|
||||
createVNode,
|
||||
normalizeVNode,
|
||||
VNode,
|
||||
VNodeChildren
|
||||
} from './vnode.js'
|
||||
import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
|
||||
import { effect } from '@vue/observer'
|
||||
import { isString, isFunction, isArray } from '@vue/shared'
|
||||
import { renderComponentRoot, shouldUpdateComponent } from './component.js'
|
||||
|
||||
const emptyArr: any[] = []
|
||||
const emptyObj: { [key: string]: any } = {}
|
||||
@ -46,6 +48,7 @@ export interface RendererOptions {
|
||||
createComment(text: string): HostNode
|
||||
setText(node: HostNode, text: string): void
|
||||
setElementText(node: HostNode, text: string): void
|
||||
parentNode(node: HostNode): HostNode | null
|
||||
nextSibling(node: HostNode): HostNode | null
|
||||
}
|
||||
|
||||
@ -59,6 +62,7 @@ export function createRenderer(options: RendererOptions) {
|
||||
createComment: hostCreateComment,
|
||||
setText: hostSetText,
|
||||
setElementText: hostSetElementText,
|
||||
parentNode: hostParentNode,
|
||||
nextSibling: hostNextSibling
|
||||
} = options
|
||||
|
||||
@ -71,7 +75,7 @@ export function createRenderer(options: RendererOptions) {
|
||||
) {
|
||||
// patching & not same type, unmount old tree
|
||||
if (n1 != null && !isSameType(n1, n2)) {
|
||||
anchor = hostNextSibling(n1.anchor || n1.el)
|
||||
anchor = getNextHostNode(n1)
|
||||
unmount(n1, true)
|
||||
n1 = null
|
||||
}
|
||||
@ -160,27 +164,11 @@ export function createRenderer(options: RendererOptions) {
|
||||
start: number = 0
|
||||
) {
|
||||
for (let i = start; i < children.length; i++) {
|
||||
const child = (children[i] = normalizeChild(children[i]))
|
||||
const child = (children[i] = normalizeVNode(children[i]))
|
||||
patch(null, child, container, anchor)
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeChild(child: any): VNode {
|
||||
if (child == null) {
|
||||
// empty placeholder
|
||||
return createVNode(Empty)
|
||||
} else if (isArray(child)) {
|
||||
// fragment
|
||||
return createVNode(Fragment, null, child)
|
||||
} else if (typeof child === 'object') {
|
||||
// already vnode
|
||||
return child as VNode
|
||||
} else {
|
||||
// primitive types
|
||||
return createVNode(Text, null, child + '')
|
||||
}
|
||||
}
|
||||
|
||||
function patchElement(n1: VNode, n2: VNode, optimized?: boolean) {
|
||||
const el = (n2.el = n1.el)
|
||||
const { patchFlag, dynamicChildren } = n2
|
||||
@ -328,7 +316,65 @@ export function createRenderer(options: RendererOptions) {
|
||||
n2: VNode,
|
||||
container: HostNode,
|
||||
anchor?: HostNode
|
||||
) {}
|
||||
) {
|
||||
if (n1 == null) {
|
||||
mountComponent(n2, container, anchor)
|
||||
} else {
|
||||
updateComponent(n1.component, n2, container, anchor)
|
||||
}
|
||||
}
|
||||
|
||||
function mountComponent(
|
||||
vnode: VNode,
|
||||
container: HostNode,
|
||||
anchor?: HostNode
|
||||
) {
|
||||
const instance = (vnode.component = {
|
||||
vnode: null,
|
||||
subTree: null,
|
||||
updateHandle: null,
|
||||
render: vnode.type
|
||||
} as any)
|
||||
|
||||
instance.updateHandle = effect(
|
||||
() => {
|
||||
if (!instance.vnode) {
|
||||
// initial mount
|
||||
instance.vnode = vnode
|
||||
const subTree = (instance.subTree = renderComponentRoot(instance))
|
||||
patch(null, subTree, container, anchor)
|
||||
vnode.el = subTree.el
|
||||
} else {
|
||||
updateComponent(instance, vnode)
|
||||
}
|
||||
},
|
||||
{
|
||||
scheduler: e => e() // TODO use proper scheduler
|
||||
}
|
||||
) as any
|
||||
}
|
||||
|
||||
function updateComponent(
|
||||
instance: any,
|
||||
next: VNode,
|
||||
container?: HostNode,
|
||||
anchor?: HostNode
|
||||
) {
|
||||
const prev = instance.vnode
|
||||
instance.vnode = next
|
||||
next.component = instance
|
||||
if (shouldUpdateComponent(prev, next)) {
|
||||
const prevTree = instance.subTree
|
||||
const nextTree = (instance.subTree = renderComponentRoot(instance))
|
||||
patch(
|
||||
prevTree,
|
||||
nextTree,
|
||||
container || hostParentNode(prevTree.el),
|
||||
anchor || getNextHostNode(prevTree)
|
||||
)
|
||||
next.el = nextTree.el
|
||||
}
|
||||
}
|
||||
|
||||
function patchChildren(
|
||||
n1: VNode | null,
|
||||
@ -405,7 +451,7 @@ export function createRenderer(options: RendererOptions) {
|
||||
const commonLength = Math.min(oldLength, newLength)
|
||||
let i
|
||||
for (i = 0; i < commonLength; i++) {
|
||||
const nextChild = (c2[i] = normalizeChild(c2[i]))
|
||||
const nextChild = (c2[i] = normalizeVNode(c2[i]))
|
||||
patch(c1[i], nextChild, container, null, optimized)
|
||||
}
|
||||
if (oldLength > newLength) {
|
||||
@ -435,7 +481,7 @@ export function createRenderer(options: RendererOptions) {
|
||||
// (a b) d e
|
||||
while (i <= e1 && i <= e2) {
|
||||
const n1 = c1[i]
|
||||
const n2 = (c2[i] = normalizeChild(c2[i]))
|
||||
const n2 = (c2[i] = normalizeVNode(c2[i]))
|
||||
if (isSameType(n1, n2)) {
|
||||
patch(n1, n2, container, parentAnchor, optimized)
|
||||
} else {
|
||||
@ -449,7 +495,7 @@ export function createRenderer(options: RendererOptions) {
|
||||
// d e (b c)
|
||||
while (i <= e1 && i <= e2) {
|
||||
const n1 = c1[e1]
|
||||
const n2 = (c2[e2] = normalizeChild(c2[e2]))
|
||||
const n2 = (c2[e2] = normalizeVNode(c2[e2]))
|
||||
if (isSameType(n1, n2)) {
|
||||
patch(n1, n2, container, parentAnchor, optimized)
|
||||
} else {
|
||||
@ -471,7 +517,7 @@ export function createRenderer(options: RendererOptions) {
|
||||
const nextPos = e2 + 1
|
||||
const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
|
||||
while (i <= e2) {
|
||||
patch(null, (c2[i] = normalizeChild(c2[i])), container, anchor)
|
||||
patch(null, (c2[i] = normalizeVNode(c2[i])), container, anchor)
|
||||
i++
|
||||
}
|
||||
}
|
||||
@ -502,7 +548,7 @@ export function createRenderer(options: RendererOptions) {
|
||||
// 5.1 build key:index map for newChildren
|
||||
const keyToNewIndexMap: Map<any, number> = new Map()
|
||||
for (i = s2; i <= e2; i++) {
|
||||
const nextChild = (c2[i] = normalizeChild(c2[i]))
|
||||
const nextChild = (c2[i] = normalizeVNode(c2[i]))
|
||||
if (nextChild.key != null) {
|
||||
// TODO warn duplicate keys
|
||||
keyToNewIndexMap.set(nextChild.key, i)
|
||||
@ -588,6 +634,10 @@ export function createRenderer(options: RendererOptions) {
|
||||
}
|
||||
|
||||
function move(vnode: VNode, container: HostNode, anchor: HostNode) {
|
||||
if (vnode.component != null) {
|
||||
move(vnode.component.subTree, container, anchor)
|
||||
return
|
||||
}
|
||||
if (vnode.type === Fragment) {
|
||||
hostInsert(vnode.el, container, anchor)
|
||||
const children = vnode.children as VNode[]
|
||||
@ -601,6 +651,11 @@ export function createRenderer(options: RendererOptions) {
|
||||
}
|
||||
|
||||
function unmount(vnode: VNode, doRemove?: boolean) {
|
||||
if (vnode.component != null) {
|
||||
// TODO teardown component
|
||||
unmount(vnode.component.subTree, doRemove)
|
||||
return
|
||||
}
|
||||
const shouldRemoveChildren = vnode.type === Fragment && doRemove
|
||||
if (vnode.dynamicChildren != null) {
|
||||
unmountChildren(vnode.dynamicChildren, shouldRemoveChildren)
|
||||
@ -623,6 +678,12 @@ export function createRenderer(options: RendererOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
function getNextHostNode(vnode: VNode): HostNode {
|
||||
return vnode.component === null
|
||||
? hostNextSibling(vnode.anchor || vnode.el)
|
||||
: getNextHostNode(vnode.component.subTree)
|
||||
}
|
||||
|
||||
return function render(vnode: VNode, dom: HostNode): VNode {
|
||||
patch(dom._vnode, vnode, dom)
|
||||
return (dom._vnode = vnode)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { isFunction } from '@vue/shared'
|
||||
import { isArray, isFunction } from '@vue/shared'
|
||||
|
||||
export const Fragment = Symbol('Fragment')
|
||||
export const Text = Symbol('Text')
|
||||
@ -15,12 +15,17 @@ export type VNodeChild = VNode | string | number | null
|
||||
export interface VNodeChildren extends Array<VNodeChildren | VNodeChild> {}
|
||||
|
||||
export interface VNode {
|
||||
el: any
|
||||
anchor: any // fragment anchor
|
||||
type: VNodeTypes
|
||||
props: { [key: string]: any } | null
|
||||
key: string | number | null
|
||||
children: string | VNodeChildren | null
|
||||
component: any
|
||||
|
||||
// DOM
|
||||
el: any
|
||||
anchor: any // fragment anchor
|
||||
|
||||
// optimization only
|
||||
patchFlag: number | null
|
||||
dynamicProps: string[] | null
|
||||
dynamicChildren: VNode[] | null
|
||||
@ -68,6 +73,7 @@ export function createVNode(
|
||||
props,
|
||||
key: props && props.key,
|
||||
children,
|
||||
component: null,
|
||||
el: null,
|
||||
anchor: null,
|
||||
patchFlag,
|
||||
@ -91,3 +97,19 @@ export function cloneVNode(vnode: VNode): VNode {
|
||||
// TODO
|
||||
return vnode
|
||||
}
|
||||
|
||||
export function normalizeVNode(child: any): VNode {
|
||||
if (child == null) {
|
||||
// empty placeholder
|
||||
return createVNode(Empty)
|
||||
} else if (isArray(child)) {
|
||||
// fragment
|
||||
return createVNode(Fragment, null, child)
|
||||
} else if (typeof child === 'object') {
|
||||
// already vnode
|
||||
return child as VNode
|
||||
} else {
|
||||
// primitive types
|
||||
return createVNode(Text, null, child + '')
|
||||
}
|
||||
}
|
||||
|
@ -37,5 +37,7 @@ export const DOMRendererOptions: RendererOptions = {
|
||||
el.textContent = text
|
||||
},
|
||||
|
||||
parentNode: (node: Node): Node | null => node.parentNode,
|
||||
|
||||
nextSibling: (node: Node): Node | null => node.nextSibling
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user