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 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,
|
Text,
|
||||||
Fragment,
|
Fragment,
|
||||||
Empty,
|
Empty,
|
||||||
createVNode,
|
normalizeVNode,
|
||||||
VNode,
|
VNode,
|
||||||
VNodeChildren
|
VNodeChildren
|
||||||
} from './vnode.js'
|
} from './vnode.js'
|
||||||
import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
|
import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
|
||||||
|
import { effect } from '@vue/observer'
|
||||||
import { isString, isFunction, isArray } from '@vue/shared'
|
import { isString, isFunction, isArray } from '@vue/shared'
|
||||||
|
import { renderComponentRoot, shouldUpdateComponent } from './component.js'
|
||||||
|
|
||||||
const emptyArr: any[] = []
|
const emptyArr: any[] = []
|
||||||
const emptyObj: { [key: string]: any } = {}
|
const emptyObj: { [key: string]: any } = {}
|
||||||
@ -46,6 +48,7 @@ export interface RendererOptions {
|
|||||||
createComment(text: string): HostNode
|
createComment(text: string): HostNode
|
||||||
setText(node: HostNode, text: string): void
|
setText(node: HostNode, text: string): void
|
||||||
setElementText(node: HostNode, text: string): void
|
setElementText(node: HostNode, text: string): void
|
||||||
|
parentNode(node: HostNode): HostNode | null
|
||||||
nextSibling(node: HostNode): HostNode | null
|
nextSibling(node: HostNode): HostNode | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +62,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
createComment: hostCreateComment,
|
createComment: hostCreateComment,
|
||||||
setText: hostSetText,
|
setText: hostSetText,
|
||||||
setElementText: hostSetElementText,
|
setElementText: hostSetElementText,
|
||||||
|
parentNode: hostParentNode,
|
||||||
nextSibling: hostNextSibling
|
nextSibling: hostNextSibling
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
@ -71,7 +75,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 = hostNextSibling(n1.anchor || n1.el)
|
anchor = getNextHostNode(n1)
|
||||||
unmount(n1, true)
|
unmount(n1, true)
|
||||||
n1 = null
|
n1 = null
|
||||||
}
|
}
|
||||||
@ -160,27 +164,11 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
start: number = 0
|
start: number = 0
|
||||||
) {
|
) {
|
||||||
for (let i = start; i < children.length; i++) {
|
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)
|
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) {
|
function patchElement(n1: VNode, n2: VNode, optimized?: boolean) {
|
||||||
const el = (n2.el = n1.el)
|
const el = (n2.el = n1.el)
|
||||||
const { patchFlag, dynamicChildren } = n2
|
const { patchFlag, dynamicChildren } = n2
|
||||||
@ -328,7 +316,65 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
n2: VNode,
|
n2: VNode,
|
||||||
container: HostNode,
|
container: HostNode,
|
||||||
anchor?: 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(
|
function patchChildren(
|
||||||
n1: VNode | null,
|
n1: VNode | null,
|
||||||
@ -405,7 +451,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
const commonLength = Math.min(oldLength, newLength)
|
const commonLength = Math.min(oldLength, newLength)
|
||||||
let i
|
let i
|
||||||
for (i = 0; i < commonLength; 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)
|
patch(c1[i], nextChild, container, null, optimized)
|
||||||
}
|
}
|
||||||
if (oldLength > newLength) {
|
if (oldLength > newLength) {
|
||||||
@ -435,7 +481,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
// (a b) d e
|
// (a b) d e
|
||||||
while (i <= e1 && i <= e2) {
|
while (i <= e1 && i <= e2) {
|
||||||
const n1 = c1[i]
|
const n1 = c1[i]
|
||||||
const n2 = (c2[i] = normalizeChild(c2[i]))
|
const n2 = (c2[i] = normalizeVNode(c2[i]))
|
||||||
if (isSameType(n1, n2)) {
|
if (isSameType(n1, n2)) {
|
||||||
patch(n1, n2, container, parentAnchor, optimized)
|
patch(n1, n2, container, parentAnchor, optimized)
|
||||||
} else {
|
} else {
|
||||||
@ -449,7 +495,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
// d e (b c)
|
// d e (b c)
|
||||||
while (i <= e1 && i <= e2) {
|
while (i <= e1 && i <= e2) {
|
||||||
const n1 = c1[e1]
|
const n1 = c1[e1]
|
||||||
const n2 = (c2[e2] = normalizeChild(c2[e2]))
|
const n2 = (c2[e2] = normalizeVNode(c2[e2]))
|
||||||
if (isSameType(n1, n2)) {
|
if (isSameType(n1, n2)) {
|
||||||
patch(n1, n2, container, parentAnchor, optimized)
|
patch(n1, n2, container, parentAnchor, optimized)
|
||||||
} else {
|
} else {
|
||||||
@ -471,7 +517,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
const nextPos = e2 + 1
|
const nextPos = e2 + 1
|
||||||
const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
|
const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
|
||||||
while (i <= e2) {
|
while (i <= e2) {
|
||||||
patch(null, (c2[i] = normalizeChild(c2[i])), container, anchor)
|
patch(null, (c2[i] = normalizeVNode(c2[i])), container, anchor)
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -502,7 +548,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
// 5.1 build key:index map for newChildren
|
// 5.1 build key:index map for newChildren
|
||||||
const keyToNewIndexMap: Map<any, number> = new Map()
|
const keyToNewIndexMap: Map<any, number> = new Map()
|
||||||
for (i = s2; i <= e2; i++) {
|
for (i = s2; i <= e2; i++) {
|
||||||
const nextChild = (c2[i] = normalizeChild(c2[i]))
|
const nextChild = (c2[i] = normalizeVNode(c2[i]))
|
||||||
if (nextChild.key != null) {
|
if (nextChild.key != null) {
|
||||||
// TODO warn duplicate keys
|
// TODO warn duplicate keys
|
||||||
keyToNewIndexMap.set(nextChild.key, i)
|
keyToNewIndexMap.set(nextChild.key, i)
|
||||||
@ -588,6 +634,10 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function move(vnode: VNode, container: HostNode, anchor: HostNode) {
|
function move(vnode: VNode, container: HostNode, anchor: HostNode) {
|
||||||
|
if (vnode.component != null) {
|
||||||
|
move(vnode.component.subTree, container, anchor)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (vnode.type === Fragment) {
|
if (vnode.type === Fragment) {
|
||||||
hostInsert(vnode.el, container, anchor)
|
hostInsert(vnode.el, container, anchor)
|
||||||
const children = vnode.children as VNode[]
|
const children = vnode.children as VNode[]
|
||||||
@ -601,6 +651,11 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function unmount(vnode: VNode, doRemove?: boolean) {
|
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
|
const shouldRemoveChildren = vnode.type === Fragment && doRemove
|
||||||
if (vnode.dynamicChildren != null) {
|
if (vnode.dynamicChildren != null) {
|
||||||
unmountChildren(vnode.dynamicChildren, shouldRemoveChildren)
|
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 {
|
return function render(vnode: VNode, dom: HostNode): VNode {
|
||||||
patch(dom._vnode, vnode, dom)
|
patch(dom._vnode, vnode, dom)
|
||||||
return (dom._vnode = vnode)
|
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 Fragment = Symbol('Fragment')
|
||||||
export const Text = Symbol('Text')
|
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 VNodeChildren extends Array<VNodeChildren | VNodeChild> {}
|
||||||
|
|
||||||
export interface VNode {
|
export interface VNode {
|
||||||
el: any
|
|
||||||
anchor: any // fragment anchor
|
|
||||||
type: VNodeTypes
|
type: VNodeTypes
|
||||||
props: { [key: string]: any } | null
|
props: { [key: string]: any } | null
|
||||||
key: string | number | null
|
key: string | number | null
|
||||||
children: string | VNodeChildren | null
|
children: string | VNodeChildren | null
|
||||||
|
component: any
|
||||||
|
|
||||||
|
// DOM
|
||||||
|
el: any
|
||||||
|
anchor: any // fragment anchor
|
||||||
|
|
||||||
|
// optimization only
|
||||||
patchFlag: number | null
|
patchFlag: number | null
|
||||||
dynamicProps: string[] | null
|
dynamicProps: string[] | null
|
||||||
dynamicChildren: VNode[] | null
|
dynamicChildren: VNode[] | null
|
||||||
@ -68,6 +73,7 @@ export function createVNode(
|
|||||||
props,
|
props,
|
||||||
key: props && props.key,
|
key: props && props.key,
|
||||||
children,
|
children,
|
||||||
|
component: null,
|
||||||
el: null,
|
el: null,
|
||||||
anchor: null,
|
anchor: null,
|
||||||
patchFlag,
|
patchFlag,
|
||||||
@ -91,3 +97,19 @@ export function cloneVNode(vnode: VNode): VNode {
|
|||||||
// TODO
|
// TODO
|
||||||
return vnode
|
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
|
el.textContent = text
|
||||||
},
|
},
|
||||||
|
|
||||||
|
parentNode: (node: Node): Node | null => node.parentNode,
|
||||||
|
|
||||||
nextSibling: (node: Node): Node | null => node.nextSibling
|
nextSibling: (node: Node): Node | null => node.nextSibling
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user