diff --git a/libs/horizon/src/renderer/vnode/ProcessingVNode.ts b/libs/horizon/src/renderer/vnode/ProcessingVNode.ts new file mode 100644 index 00000000..1110242c --- /dev/null +++ b/libs/horizon/src/renderer/vnode/ProcessingVNode.ts @@ -0,0 +1,8 @@ +import type {VNode} from '../Types'; + +// 当前所有者是应拥有当前正在构建的任何组件的组件。 +const ProcessingVNode: { val: VNode | null } = { + val: null, +}; + +export default ProcessingVNode; diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts new file mode 100644 index 00000000..f726c4a4 --- /dev/null +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -0,0 +1,98 @@ +/** + * 虚拟DOM结构体 + */ +import {TreeRoot} from './VNodeTags'; +import type {VNodeTag} from './VNodeTags'; +import type {RefType, ContextType} from '../Types'; +import type {Hook} from '../hooks/HookType'; + +export class VNode { + tag: VNodeTag; + key: string | null; // 唯一标识符 + type: any = null; + realNode: any = null; // 如果是类,则存放实例;如果是div这种,则存放真实DOM; + + // 关系结构 + parent: VNode | null = null; // 父节点 + children: Array | null = null; // 子节点 + cIndex: number = 0; // 节点在children数组中的位置 + eIndex: number = 0; // HorizonElement在jsx中的位置,例如:jsx中的null不会生成vNode,所以eIndex和cIndex不一致 + + ref: RefType | ((handle: any) => void) | null = null; // 包裹一个函数,submit阶段使用,比如将外部useRef生成的对象赋值到ref上 + props: any; // 传给组件的props的值,类组件包含defaultProps,Lazy组件不包含 + oldProps: any = null; + + suspensePromises: any = null; // suspense组件的promise列表 + changeList: any = null; // DOM的变更列表 + effectList: any[] = []; // useEffect 的更新数组 + updates: any[] = null; // TreeRoot和ClassComponent使用的更新数组 + stateCallbacks: any[] = []; // 存放存在setState的第二个参数和HorizonDOM.render的第三个参数所在的node数组 + isForceUpdate: boolean = false; // 是否使用强制更新 + + state: any = null; // ClassComponent和TreeRoot的状态 + hooks: Array> = []; // 保存hook + suspenseChildStatus: string = ''; // Suspense的Children是否显示 + depContexts: Array> = []; // FunctionComponent和ClassComponent对context的依赖列表 + isDepContextChange: boolean = false; // context是否变更 + dirtyNodes: Array = []; // 需要改动的节点数组 + shouldUpdate: boolean = false; + childShouldUpdate: boolean = false; + outerDom: any; + task: any; + + // 使用这个变量来记录修改前的值,用于恢复。 + contexts = {}; + // 因为LazyComponent会修改tag和type属性,为了能识别,增加一个属性 + isLazyComponent: boolean = false; + + // 因为LazyComponent会修改type属性,为了在diff中判断是否可以复用,需要增加一个lazyType + lazyType: any = null; + flags: { + Addition?: boolean, + Update?: boolean, + Deletion?: boolean, + ResetText?: boolean, + Callback?: boolean, + DidCapture?: boolean, + Ref?: boolean, + Snapshot?: boolean, + Interrupted?: boolean, + ShouldCapture?: boolean, + ForceUpdate?: boolean, + } = {}; + + // one tree相关属性 + isCreated: boolean = true; + oldHooks: Array> = []; // 保存上一次执行的hook + oldState: any = null; + oldRef: RefType | ((handle: any) => void) | null = null; + suspenseChildThrow = false; + oldSuspenseChildStatus: string = ''; // 上一次Suspense的Children是否显示 + oldChildren: Array | null = null; + suspenseDidCapture: boolean = false; // suspense是否捕获了异常 + promiseResolve: boolean = false; // suspense的promise是否resolve + + path: Array = []; // 保存从根到本节点的路径 + toUpdateNodes: Set | null = null; // 保存要更新的节点 + + constructor(tag: VNodeTag, props: any, key: null | string, outerDom) { + this.tag = tag; // 对应组件的类型,比如ClassComponent等 + this.key = key; + + this.props = props; + + // 根节点 + if (tag === TreeRoot) { + this.outerDom = outerDom; + this.task = null; + this.toUpdateNodes = new Set(); + } + } + + setContext(contextName, value) { + this.contexts[contextName] = value; + } + getContext(contextName) { + return this.contexts[contextName]; + } +} diff --git a/libs/horizon/src/renderer/vnode/VNodeCreator.ts b/libs/horizon/src/renderer/vnode/VNodeCreator.ts new file mode 100644 index 00000000..e648e3b1 --- /dev/null +++ b/libs/horizon/src/renderer/vnode/VNodeCreator.ts @@ -0,0 +1,191 @@ +import type { VNodeTag } from './VNodeTags'; +import { FlagUtils } from './VNodeFlags'; +import { + ClassComponent, + ContextConsumer, + ContextProvider, + ForwardRef, + Fragment, + FunctionComponent, + DomComponent, + DomPortal, + TreeRoot, + DomText, + ClsOrFunComponent, + LazyComponent, + MemoComponent, + SuspenseComponent, +} from './VNodeTags'; +import { createUpdateArray } from '../UpdateHandler'; +import { + TYPE_CONTEXT, + TYPE_FORWARD_REF, TYPE_FRAGMENT, + TYPE_LAZY, + TYPE_MEMO, TYPE_PROFILER, + TYPE_PROVIDER, TYPE_STRICT_MODE, + TYPE_SUSPENSE, +} from '../utils/elementType'; +import { VNode } from './VNode'; +import {HorizonElement} from '../Types'; + +const typeLazyMap = { + [TYPE_FORWARD_REF]: ForwardRef, + [TYPE_MEMO]: MemoComponent, +}; +const typeMap = { + ...typeLazyMap, + [TYPE_PROVIDER]: ContextProvider, + [TYPE_CONTEXT]: ContextConsumer, + [TYPE_LAZY]: LazyComponent, +}; + +const newVirtualNode = function(tag: VNodeTag, key?: null | string, vNodeProps?: any, outerDom?: any): VNode { + return new VNode(tag, vNodeProps, key, outerDom); +}; + +function isClassComponent(comp: Function) { + // 如果使用 getPrototypeOf 方法获取构造函数,不能兼容业务组组件继承组件的使用方式,会误认为是函数组件 + // 如果使用静态属性,部分函数高阶组件会将类组件的静态属性复制到自身,导致误判为类组件 + // 既然已经兼容使用了该标识符,那么继续使用 + return comp.prototype?.isReactComponent === true; +} + +// 解析懒组件的tag +export function getLazyVNodeTag(lazyComp: any): string { + let vNodeTag = ClsOrFunComponent; + if (typeof lazyComp === 'function') { + vNodeTag = isClassComponent(lazyComp) ? ClassComponent : FunctionComponent; + } else if (lazyComp !== undefined && lazyComp !== null && typeLazyMap[lazyComp.vtype]) { + vNodeTag = typeLazyMap[lazyComp.vtype]; + } + return vNodeTag; +} + +// 创建processing +export function updateVNode(vNode: VNode, vNodeProps?: any): VNode { + if (vNode.tag === ClassComponent) { + vNode.oldState = vNode.state; + } + + if (vNode.tag === SuspenseComponent) { + vNode.oldSuspenseChildStatus = vNode.suspenseChildStatus; + vNode.oldChildren = vNode.children; + } + + vNode.oldProps = vNode.props; + vNode.props = vNodeProps; + + vNode.oldRef = vNode.ref; + + FlagUtils.setNoFlags(vNode); + vNode.dirtyNodes = []; + vNode.isCreated = false; + + return vNode; +} + +function getVNodeTag(type: any) { + let vNodeTag = ClsOrFunComponent; + let isLazy = false; + + if (typeof type === 'function') { + if (isClassComponent(type)) { + vNodeTag = ClassComponent; + } + } else if (typeof type === 'string') { + vNodeTag = DomComponent; + } else if (type === TYPE_SUSPENSE) { + vNodeTag = SuspenseComponent; + } else if (typeof type === 'object' && type !== null && typeMap[type.vtype]) { + vNodeTag = typeMap[type.vtype]; + isLazy = type.vtype === TYPE_LAZY; + } else { + throw Error(`Component type is invalid, got: ${type == null ? type : typeof type}`); + } + return { vNodeTag, isLazy }; +} + +export function createVNode(tag: VNodeTag | string, ...secondArg) { + let vNode = null; + switch (tag) { + case Fragment: + const [fragmentKey, fragmentProps] = secondArg; + vNode = newVirtualNode(Fragment, fragmentKey, fragmentProps); + vNode.shouldUpdate = true; + break; + case DomText: + const content = secondArg[0]; + vNode = newVirtualNode(DomText, null, content); + vNode.shouldUpdate = true; + break; + case DomPortal: + const portal = secondArg[0]; + const children = portal.children ?? []; + vNode = newVirtualNode(DomPortal, portal.key, children); + vNode.shouldUpdate = true; + vNode.outerDom = portal.outerDom; + break; + case 'props': + const [type, key, props] = secondArg; + + const { vNodeTag, isLazy } = getVNodeTag(type); + + vNode = newVirtualNode(vNodeTag, key, props); + vNode.type = type; + vNode.shouldUpdate = true; + + // lazy类型的特殊处理 + vNode.isLazyComponent = isLazy; + if (isLazy) { + vNode.lazyType = type; + } + break; + case TreeRoot: + // 创建treeRoot + vNode = newVirtualNode(TreeRoot, null, null, secondArg[0]); + vNode.path.push(0); + + createUpdateArray(vNode); + break; + } + + return vNode; +} + +export function updateVNodePath(vNode: VNode) { + vNode.path = [...vNode.parent.path, vNode.cIndex]; +} + +export function createVNodeFromElement(element: HorizonElement): VNode { + const type = element.type; + const key = element.key; + const props = element.props; + + if (type === TYPE_STRICT_MODE || type === TYPE_FRAGMENT || type === TYPE_PROFILER) { + return createVNode(Fragment, key, props.children); + } else { + return createVNode('props', type, key, props); + } +} + +// 直接更新子节点属性即可,不需要diff +export function onlyUpdateChildVNodes(processing: VNode): Array | null { + // 检查子树是否需要更新 + if (processing.childShouldUpdate) { + // 此vNode无需更新,但是子树需要 + if (!processing.isCreated && processing.children && processing.children.length) { + // 更新子节点 + processing.children.forEach(child => { + updateVNode(child, child.props); + updateVNodePath(child); + }); + } + + // 返回子节点,继续遍历 + return processing.children; + } + + // 子树无需工作 + return null; +} + diff --git a/libs/horizon/src/renderer/vnode/VNodeFlags.ts b/libs/horizon/src/renderer/vnode/VNodeFlags.ts new file mode 100644 index 00000000..7259beb7 --- /dev/null +++ b/libs/horizon/src/renderer/vnode/VNodeFlags.ts @@ -0,0 +1,92 @@ +/** + * vNode结构的变化标志 + */ + +import type { VNode } from '../Types'; + +// vNode节点的flags +export const Addition = 'Addition'; +export const Update = 'Update'; +export const Deletion = 'Deletion'; +export const ResetText = 'ResetText'; +export const Callback = 'Callback'; +export const DidCapture = 'DidCapture'; +export const Ref = 'Ref'; +export const Snapshot = 'Snapshot'; +// 被中断了,抛出错误的vNode以及它的父vNode +export const Interrupted = 'Interrupted'; +export const ShouldCapture = 'ShouldCapture'; +// For suspense +export const ForceUpdate = 'ForceUpdate'; + +const flagArr = [Addition, Update, Deletion, ResetText, Callback, DidCapture, Ref, Snapshot, Interrupted, ShouldCapture, ForceUpdate]; + +const LifecycleEffectArr = [Update, Callback, Ref, Snapshot]; + +function resetFlag(node) { + node.flags = {}; +} + +export class FlagUtils { + static removeFlag(node: VNode, flag: string) { + node.flags[flag] = false; + } + static removeLifecycleEffectFlags(node) { + LifecycleEffectArr.forEach(key => { + node.flags[key] = false; + }); + } + static hasAnyFlag(node: VNode) { // 有标志位 + let keyFlag = false; + flagArr.forEach(key => { + if (node.flags[key]) { + keyFlag = true; + } + }); + return keyFlag; + } + + static setNoFlags(node: VNode) { + resetFlag(node); + } + + static markAddition(node: VNode) { + node.flags.Addition = true; + } + static setAddition(node: VNode) { + resetFlag(node); + node.flags.Addition = true; + } + static markUpdate(node: VNode) { + node.flags.Update = true; + } + static setDeletion(node: VNode) { + resetFlag(node); + node.flags.Deletion = true; + } + static markContentReset(node: VNode) { + node.flags.ResetText = true; + } + static markCallback(node: VNode) { + node.flags.Callback = true; + } + static markDidCapture(node: VNode) { + node.flags.DidCapture = true; + } + static markShouldCapture(node: VNode) { + node.flags.ShouldCapture = true; + } + static markRef(node: VNode) { + node.flags.Ref = true; + } + static markSnapshot(node: VNode) { + node.flags.Snapshot = true; + } + static markInterrupted(node: VNode) { + node.flags.Interrupted = true; + } + static markForceUpdate(node: VNode) { + node.flags.ForceUpdate = true; + } +} + diff --git a/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts b/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts new file mode 100644 index 00000000..00ebe158 --- /dev/null +++ b/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts @@ -0,0 +1,66 @@ +// 从当前节点向上遍历,更新shouldUpdate和childShouldUpdate +import {VNode} from './VNode'; +import {TreeRoot} from './VNodeTags'; + +export function updateShouldUpdateOfTree(vNode: VNode): VNode | null { + vNode.shouldUpdate = true; + + // 一直向上遍历,修改childShouldUpdate + let node = vNode; + let parent = vNode.parent; + while (parent !== null) { + parent.childShouldUpdate = true; + node = parent; + parent = parent.parent; + } + + if (node.tag === TreeRoot) { + node.shouldUpdate = true; + // 返回根节点 + return node; + } + + return null; +} + +// 设置节点的childShouldUpdate +export function updateChildShouldUpdate(vNode: VNode) { + const children = vNode.children || []; + + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if (child && (child.shouldUpdate || child.childShouldUpdate)) { + vNode.childShouldUpdate = true; + return; + } + } + vNode.childShouldUpdate = false; +} + +// 设置节点的所有父节点的childShouldUpdate +export function updateParentsChildShouldUpdate(vNode: VNode) { + let node = vNode.parent; + let isShouldUpdate = vNode.shouldUpdate || vNode.childShouldUpdate; + + if (isShouldUpdate) { // 开始节点是shouldUpdate或childShouldUpdate + setParentsChildShouldUpdate(node); + } else { + while (node !== null) { + updateChildShouldUpdate(node); + node = node.parent; + } + } +} + +// 更新从当前节点到根节点的childShouldUpdate为true +export function setParentsChildShouldUpdate(parent: VNode | null) { + let node = parent; + while (node !== null) { + if (node.childShouldUpdate) { + break; + } + node.childShouldUpdate = true; + + node = node.parent; + } +} diff --git a/libs/horizon/src/renderer/vnode/VNodeTags.ts b/libs/horizon/src/renderer/vnode/VNodeTags.ts new file mode 100644 index 00000000..2ec1db13 --- /dev/null +++ b/libs/horizon/src/renderer/vnode/VNodeTags.ts @@ -0,0 +1,21 @@ +/** + * 定义vNode的类型 + */ +export type VNodeTag = string; + +export const TreeRoot = 'TreeRoot'; // tree的根节点,用于存放一些tree级的变量 +export const FunctionComponent = 'FunctionComponent'; +export const ClassComponent = 'ClassComponent'; +export const ClsOrFunComponent = 'ClsOrFunComponent'; +export const DomPortal = 'DomPortal'; +export const DomComponent = 'DomComponent'; +export const DomText = 'DomText'; +export const Fragment = 'Fragment'; +export const ContextConsumer = 'ContextConsumer'; +export const ContextProvider = 'ContextProvider'; +export const ForwardRef = 'ForwardRef'; +export const Profiler = 'Profiler'; +export const SuspenseComponent = 'SuspenseComponent'; +export const MemoComponent = 'MemoComponent'; +export const LazyComponent = 'LazyComponent'; +export const IncompleteClassComponent = 'IncompleteClassComponent'; diff --git a/libs/horizon/src/renderer/vnode/VNodeUtils.ts b/libs/horizon/src/renderer/vnode/VNodeUtils.ts new file mode 100644 index 00000000..3b921738 --- /dev/null +++ b/libs/horizon/src/renderer/vnode/VNodeUtils.ts @@ -0,0 +1,278 @@ +/** + * 提供:vNode的“遍历”,“查找”,“判断”的相关工具方法 + */ + +import type {VNode} from '../Types'; + +import {DomComponent, DomPortal, DomText, TreeRoot} from './VNodeTags'; +import {isComment} from '../../dom/utils/Common'; +import {getNearestVNode} from '../../dom/DOMInternalKeys'; + +export function getSiblingVNode(node) { + let siblingVNode = null; + const index = node.cIndex; + if (node && node.parent && node.parent.children && node.parent.children.length > index + 1) { + siblingVNode = node.parent.children[index + 1]; + } + return siblingVNode; +} + +export function getFirstChild(vNode: VNode) { + return (vNode.children && vNode.children.length) ? vNode.children[0] : null; +} + +// 从beginVNode开始深度遍历vNode树,对每个vNode调用handleVNode方法 +export function travelVNodeTree( + beginVNode: VNode, + handleVNode: Function, + childFilter: Function = () => false, // 返回true不处理child + finishVNode?: VNode, // 结束遍历节点,有时候和beginVNode不相同 + handleWhenToParent?: Function +): VNode | null { + const overVNode = finishVNode || beginVNode; + let node = beginVNode; + + while (true) { + const ret = handleVNode(node); + // 如果处理一个vNode时有返回值,则中断遍历 + if (ret) { + return ret; + } + + // 找子节点 + const childVNode = getFirstChild(node); + if (childVNode !== null && !childFilter(node)) { + childVNode.parent = node; + node = childVNode; + continue; + } + + // 回到开始节点 + if (node === overVNode) { + return null; + } + + // 找兄弟,没有就往上再找兄弟 + while (getSiblingVNode(node) === null) { + if (node.parent === null || node.parent === overVNode) { + return null; + } + node = node.parent; + + if (typeof handleWhenToParent === 'function') { + handleWhenToParent(node); + } + } + // 找到兄弟 + const siblingVNode = getSiblingVNode(node); + siblingVNode.parent = node.parent; + node = siblingVNode; + } +} + +// 置空vNode +export function clearVNode(vNode: VNode) { + clearOneVNode(vNode); +} + +function clearOneVNode(vNode: VNode) { + vNode.children = []; + vNode.depContexts = []; + vNode.dirtyNodes = []; + vNode.oldProps = null; + vNode.state = null; + vNode.hooks = []; + vNode.suspenseChildStatus = ''; + vNode.props = null; + vNode.parent = null; + vNode.suspensePromises = null; + vNode.changeList = null; + vNode.effectList = []; + vNode.updates = null; + + vNode.oldHooks = []; + vNode.oldState = null; + vNode.oldRef = null; + vNode.suspenseChildThrow = false; + vNode.oldSuspenseChildStatus = ''; + vNode.oldChildren = null; + + vNode.path = []; + vNode.toUpdateNodes = null; +} + +// 是dom类型的vNode +export function isDomVNode(node: VNode) { + return node.tag === DomComponent || node.tag === DomText; +} + +// 是容器类型的vNode +function isDomContainer(vNode: VNode): boolean { + return ( + vNode.tag === DomComponent || + vNode.tag === TreeRoot || + vNode.tag === DomPortal + ); +} + +// 找到DOM类型的父 +export function findDomParent(vNode: VNode) { + let parent = vNode.parent; + + while (parent !== null) { + switch (parent.tag) { + case DomComponent: + return {parent, parentDom: parent.realNode}; + case TreeRoot: + case DomPortal: + return {parent, parentDom: parent.outerDom}; + } + parent = parent.parent; + } + + return null; +} + +export function findDomVNode(vNode: VNode): VNode | null { + return travelVNodeTree(vNode, (node) => { + if (node.tag === DomComponent || node.tag === DomText) { + return node; + } + }); +} + +export function findDOMByClassInst(inst) { + const vNode = inst._vNode; + if (vNode === undefined) { + throw new Error('Unable to find the vNode by class instance.'); + } + + const domVNode = findDomVNode(vNode); + + return domVNode !== null ? domVNode.realNode : null; +} + +// 判断dom树是否已经挂载 +export function isMounted(vNode: VNode) { + const rootNode = getTreeRootVNode(vNode); + // 如果根节点是 Dom 类型节点,表示已经挂载 + return rootNode.tag === TreeRoot; +} + +function getTreeRootVNode(vNode) { + let node = vNode; + while (node.parent) { + node = node.parent; + } + return node; +} + +// 找到相邻的DOM +export function getSiblingDom(vNode: VNode): Element | null { + let node: VNode = vNode; + + findSibling: while (true) { + // 没有兄弟节点,找父节点 + while (getSiblingVNode(node) === null) { + // 没父节点,或父节点已经是根节点,则返回 + if (node.parent === null || isDomContainer(node.parent)) { + return null; + } + node = node.parent; + } + + const siblingVNode = getSiblingVNode(node); + siblingVNode.parent = node.parent; + node = siblingVNode; + + // 如果不是dom节点,往下找 + while (!isDomVNode(node)) { + // 如果节点也是Addition + if (node.flags.Addition) { + continue findSibling; + } + + // 没有子节点,或是DomPortal + if (!node.children || !node.children.length || node.tag === DomPortal) { + continue findSibling; + } else { + const childVNode = getFirstChild(node); + childVNode.parent = node; + node = childVNode; + } + } + + if (!node.flags.Addition) { + // 找到 + return node.realNode; + } + } +} + +function isSameContainer( + container: Element, + targetContainer: EventTarget, +): boolean { + if (container === targetContainer) { + return true; + } + // 注释类型的节点 + if (isComment(container) && container.parentNode === targetContainer) { + return true + } + return false; +} + +function isPortalRoot(vNode, targetContainer) { + if (vNode.tag === DomPortal) { + let topVNode = vNode.parent; + while (topVNode !== null) { + const grandTag = topVNode.tag; + if (grandTag === TreeRoot || grandTag === DomPortal) { + const topContainer = topVNode.outerDom; + // 如果topContainer是targetContainer,不需要在这里处理 + if (isSameContainer(topContainer, targetContainer)) { + return true; + } + } + topVNode = topVNode.parent; + } + return false; + } + return false; +} + +// 获取根vNode节点 +export function getExactNode(targetVNode, targetContainer) { + // 确认vNode节点是否准确,portal场景下可能祖先节点不准确 + let vNode = targetVNode; + while (vNode !== null) { + if (vNode.tag === TreeRoot || vNode.tag === DomPortal) { + let container = vNode.outerDom; + if (isSameContainer(container, targetContainer)) { + break; + } + if (isPortalRoot(vNode, targetContainer)) { + return null; + } + while (container !== null) { + const parentNode = getNearestVNode(container); + if (parentNode === null) { + return null; + } + if (parentNode.tag === DomComponent || parentNode.tag === DomText) { + vNode = parentNode; + return getExactNode(vNode, targetContainer); + } + container = container.parentNode; + } + } + vNode = vNode.parent; + } + if (vNode === null) { + return null; + } + return targetVNode; +} + +