From 35777afb6dd91579f00f677cfd6ea7554d0ccbe3 Mon Sep 17 00:00:00 2001 From: * <8> Date: Wed, 22 Dec 2021 20:42:14 +0800 Subject: [PATCH] Match-id-93334d78c8b399ade8afbf64ec34ab13e1965ee4 --- .../src/renderer/render/DomComponent.ts | 131 ++++++++++ libs/horizon/src/renderer/render/DomPortal.ts | 29 +++ libs/horizon/src/renderer/render/DomText.ts | 35 +++ .../horizon/src/renderer/render/ForwardRef.ts | 9 + libs/horizon/src/renderer/render/Fragment.ts | 14 + .../src/renderer/render/FunctionComponent.ts | 100 ++++++++ .../render/IncompleteClassComponent.ts | 41 +++ .../src/renderer/render/LazyComponent.ts | 82 ++++++ .../src/renderer/render/MemoComponent.ts | 61 +++++ .../src/renderer/render/SuspenseComponent.ts | 239 ++++++++++++++++++ libs/horizon/src/renderer/render/TreeRoot.ts | 42 +++ libs/horizon/src/renderer/render/index.ts | 56 ++++ 12 files changed, 839 insertions(+) create mode 100644 libs/horizon/src/renderer/render/DomComponent.ts create mode 100644 libs/horizon/src/renderer/render/DomPortal.ts create mode 100644 libs/horizon/src/renderer/render/DomText.ts create mode 100644 libs/horizon/src/renderer/render/ForwardRef.ts create mode 100644 libs/horizon/src/renderer/render/Fragment.ts create mode 100644 libs/horizon/src/renderer/render/FunctionComponent.ts create mode 100644 libs/horizon/src/renderer/render/IncompleteClassComponent.ts create mode 100644 libs/horizon/src/renderer/render/LazyComponent.ts create mode 100644 libs/horizon/src/renderer/render/MemoComponent.ts create mode 100644 libs/horizon/src/renderer/render/SuspenseComponent.ts create mode 100644 libs/horizon/src/renderer/render/TreeRoot.ts create mode 100644 libs/horizon/src/renderer/render/index.ts diff --git a/libs/horizon/src/renderer/render/DomComponent.ts b/libs/horizon/src/renderer/render/DomComponent.ts new file mode 100644 index 00000000..dc8917b3 --- /dev/null +++ b/libs/horizon/src/renderer/render/DomComponent.ts @@ -0,0 +1,131 @@ +import type {VNode} from '../Types'; +import type {Props} from '../../dom/DOMOperator'; + +import { + getNamespaceCtx, + setNamespaceCtx, + resetNamespaceCtx, +} from '../ContextSaver'; +import { + appendChildElement, + newDom, + initDomProps, getPropChangeList, + isTextChild, +} from '../../dom/DOMOperator'; +import {FlagUtils} from '../vnode/VNodeFlags'; +import {createVNodeChildren, markRef} from './BaseComponent'; +import {DomComponent, DomPortal, DomText} from '../vnode/VNodeTags'; +import {getFirstChild, travelVNodeTree} from '../vnode/VNodeUtils'; + +export function captureRender(processing: VNode): Array | null { + return captureDomComponent(processing); +} + +export function bubbleRender(processing: VNode) { + resetNamespaceCtx(processing); + + const type = processing.type; + const newProps = processing.props; + if (!processing.isCreated && processing.realNode != null) { + // 更新dom属性 + updateDom( + processing, + type, + newProps, + ); + + if (processing.oldRef !== processing.ref) { + FlagUtils.markRef(processing); + } + } else { + const parentNamespace = getNamespaceCtx(); + + // 创建dom + const dom = newDom( + type, + newProps, + parentNamespace, + processing, + ); + + appendAllChildren(dom, processing); + + processing.realNode = dom; + + if (initDomProps(dom, type, newProps)) { + FlagUtils.markUpdate(processing); + } + + // 处理ref导致的更新 + if (processing.ref !== null) { + FlagUtils.markRef(processing); + } + } +} + +function captureDomComponent(processing: VNode): Array | null { + setNamespaceCtx(processing); + + const type = processing.type; + const newProps = processing.props; + const oldProps = !processing.isCreated ? processing.oldProps : null; + + let nextChildren = newProps.children; + const isDirectTextChild = isTextChild(type, newProps); + + if (isDirectTextChild) { + // 如果为文本节点,则认为没有子节点 + nextChildren = null; + } else if (oldProps !== null && isTextChild(type, oldProps)) { + // 将纯文本的子节点改为vNode节点 + FlagUtils.markContentReset(processing); + } + + markRef(processing); + processing.children = createVNodeChildren(processing, nextChildren); + return processing.children; +} + +// 把dom类型的子节点append到parent dom中 +function appendAllChildren(parent: Element, processing: VNode) { + const vNode = getFirstChild(processing); + if (vNode === null) { + return; + } + + // 向下递归它的子节点,查找所有终端节点。 + travelVNodeTree(vNode, (node) => { + if (node.tag === DomComponent || node.tag === DomText) { + appendChildElement(parent, node.realNode); + } + }, (node) => { + // 已经append到父节点,或者是DomPortal都不需要处理child了 + return node.tag === DomComponent || node.tag === DomText || node.tag === DomPortal; + }, processing); +} + +function updateDom( + processing: VNode, + type: any, + newProps: Props, +) { + // 如果oldProps !== newProps,意味着存在更新,并且需要处理其相关的副作用 + const oldProps = processing.oldProps; + if (oldProps === newProps) { + // 如果props没有发生变化,即使它的children发生了变化,我们也不会改变它 + return; + } + + const dom: Element = processing.realNode; + + const changeList = getPropChangeList( + dom, + type, + oldProps, + newProps, + ); + processing.changeList = changeList; + + // 标记为更新 + FlagUtils.markUpdate(processing); +} diff --git a/libs/horizon/src/renderer/render/DomPortal.ts b/libs/horizon/src/renderer/render/DomPortal.ts new file mode 100644 index 00000000..d1bd60ac --- /dev/null +++ b/libs/horizon/src/renderer/render/DomPortal.ts @@ -0,0 +1,29 @@ +import type { VNode } from '../Types'; +import { resetNamespaceCtx, setNamespaceCtx } from '../ContextSaver'; +import { createChildrenByDiff } from '../diff/nodeDiffComparator'; +import { createVNodeChildren } from './BaseComponent'; +import { prePortal } from '../../dom/DOMOperator'; + +export function captureRender(processing: VNode): Array | null { + return capturePortalComponent(processing); +} + +export function bubbleRender(processing: VNode) { + resetNamespaceCtx(processing); + + if (processing.isCreated) { + prePortal(processing.outerDom); + } +} + +function capturePortalComponent(processing: VNode) { + setNamespaceCtx(processing, processing.outerDom); + + const newElements = processing.props; + if (processing.isCreated) { + processing.children = createChildrenByDiff(processing, [], newElements); + } else { + processing.children = createVNodeChildren(processing, newElements); + } + return processing.children; +} diff --git a/libs/horizon/src/renderer/render/DomText.ts b/libs/horizon/src/renderer/render/DomText.ts new file mode 100644 index 00000000..525d8a04 --- /dev/null +++ b/libs/horizon/src/renderer/render/DomText.ts @@ -0,0 +1,35 @@ +import type {VNode} from '../Types'; + +import {throwIfTrue} from '../utils/throwIfTrue'; +import {newTextDom} from '../../dom/DOMOperator'; +import {FlagUtils} from '../vnode/VNodeFlags'; + +export function captureRender(): VNode | null { + return null; +} + +export function bubbleRender(processing: VNode) { + const newText = processing.props; + + if (!processing.isCreated && processing.realNode != null) { // 更新 + const oldText = processing.oldProps; + // 如果文本不同,将其标记为更新 + if (oldText !== newText) { + FlagUtils.markUpdate(processing); + } + } else { // 初始化 + if (typeof newText !== 'string') { + // 如果存在bug,可能出现这种情况 + throwIfTrue( + processing.realNode === null, + 'We must have new text for new mounted node. This error is likely ' + + 'caused by a bug in Horizon. Please file an issue.', + ); + } + // 获得对应节点 + processing.realNode = newTextDom( + newText, + processing, + ); + } +} diff --git a/libs/horizon/src/renderer/render/ForwardRef.ts b/libs/horizon/src/renderer/render/ForwardRef.ts new file mode 100644 index 00000000..6ae2fb64 --- /dev/null +++ b/libs/horizon/src/renderer/render/ForwardRef.ts @@ -0,0 +1,9 @@ +import type {VNode} from '../Types'; +import {captureRender as funCaptureRender} from './FunctionComponent'; + +export function captureRender(processing: VNode, shouldUpdate?: boolean): Array | null { + return funCaptureRender(processing, shouldUpdate); +} + +export function bubbleRender() {} + diff --git a/libs/horizon/src/renderer/render/Fragment.ts b/libs/horizon/src/renderer/render/Fragment.ts new file mode 100644 index 00000000..286335f9 --- /dev/null +++ b/libs/horizon/src/renderer/render/Fragment.ts @@ -0,0 +1,14 @@ +import type {VNode} from '../Types'; +import {createVNodeChildren} from './BaseComponent'; + +export function captureRender(processing: VNode): Array | null { + return captureFragment(processing); +} + +export function bubbleRender() {} + +function captureFragment(processing: VNode) { + const newElement = processing.props; + processing.children = createVNodeChildren(processing, newElement); + return processing.children; +} diff --git a/libs/horizon/src/renderer/render/FunctionComponent.ts b/libs/horizon/src/renderer/render/FunctionComponent.ts new file mode 100644 index 00000000..862e62c0 --- /dev/null +++ b/libs/horizon/src/renderer/render/FunctionComponent.ts @@ -0,0 +1,100 @@ +import type {VNode} from '../Types'; + +import {mergeDefaultProps} from './LazyComponent'; +import {getOldContext} from '../components/context/CompatibleContext'; +import {resetDepContexts} from '../components/context/Context'; +import {exeFunctionHook} from '../hooks/HookMain'; +import {createVNodeChildren} from './BaseComponent'; +import {ForwardRef} from '../vnode/VNodeTags'; +import {FlagUtils, Update} from '../vnode/VNodeFlags'; +import {getContextChangeCtx} from '../ContextSaver'; +import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; + +// 在useState, useReducer的时候,会触发state变化 +let stateChange = false; + +export function captureRender(processing: VNode, shouldUpdate?: boolean): Array | null { + const Component = processing.type; + const unresolvedProps = processing.props; + const resolvedProps = + processing.isLazyComponent + ? mergeDefaultProps(Component, unresolvedProps) + : unresolvedProps; + + return captureFunctionComponent( + processing, + Component, + resolvedProps, + shouldUpdate + ); +} + +export function bubbleRender() {} + +export function captureFunctionComponent( + processing: VNode, + funcComp: any, + nextProps: any, + shouldUpdate?: boolean +) { + let context; + if (processing.tag !== ForwardRef) { + context = getOldContext(processing, funcComp, true); + } + + resetDepContexts(processing); + + const isCanReuse = checkIfCanReuseChildren(processing, shouldUpdate); + // 在执行exeFunctionHook前先设置stateChange为false + setStateChange(false); + + const newElements = exeFunctionHook( + processing.tag === ForwardRef ? funcComp.render : funcComp, + nextProps, + processing.tag === ForwardRef ? processing.ref : context, + processing, + ); + + // 这里需要判断是否可以复用,因为函数组件比起其他组价,多了context和stateChange两个因素 + if (isCanReuse && !isStateChange()) { + FlagUtils.removeFlag(processing, Update); + + return onlyUpdateChildVNodes(processing); + } + + processing.children = createVNodeChildren(processing, newElements); + return processing.children; +} + +// 判断children是否可以复用 +function checkIfCanReuseChildren(processing: VNode, shouldUpdate?: boolean) { + let isCanReuse = true; + + if (!processing.isCreated) { + const oldProps = processing.oldProps; + const newProps = processing.props; + + // 如果props或者context改变了 + if (oldProps !== newProps || getContextChangeCtx() || processing.isDepContextChange) { + isCanReuse = false; + } else { + if (shouldUpdate && processing.suspenseChildThrow) { + // 使用完后恢复 + processing.suspenseChildThrow = false; + isCanReuse = false; + } + } + } else { + isCanReuse = false; + } + + return isCanReuse; +} + +export function setStateChange(isUpdate) { + stateChange = isUpdate; +} + +export function isStateChange() { + return stateChange; +} diff --git a/libs/horizon/src/renderer/render/IncompleteClassComponent.ts b/libs/horizon/src/renderer/render/IncompleteClassComponent.ts new file mode 100644 index 00000000..7de50b1a --- /dev/null +++ b/libs/horizon/src/renderer/render/IncompleteClassComponent.ts @@ -0,0 +1,41 @@ +import type {VNode} from '../Types'; + +import {mergeDefaultProps} from './LazyComponent'; +import {ClassComponent} from '../vnode/VNodeTags'; +import {resetDepContexts} from '../components/context/Context'; +import {getIncompleteClassComponent} from './ClassComponent'; +import { + isOldProvider, + resetOldCtx, + cacheOldCtx, +} from '../components/context/CompatibleContext'; + +export function captureRender(processing: VNode): Array | null { + const Component = processing.type; + const unresolvedProps = processing.props; + const resolvedProps = + processing.isLazyComponent + ? mergeDefaultProps(Component, unresolvedProps) + : unresolvedProps; + + return captureIncompleteClassComponent(processing, Component, resolvedProps); +} + +export function bubbleRender(processing: VNode) { + // 处理与类组件相同。 + const Component = processing.type; + if (isOldProvider(Component)) { + resetOldCtx(processing); + } +} + +function captureIncompleteClassComponent(processing, Component, nextProps) { + processing.tag = ClassComponent; + + const hasOldContext = isOldProvider(Component); + cacheOldCtx(processing, hasOldContext); + + resetDepContexts(processing); + + return getIncompleteClassComponent(Component, processing, nextProps); +} diff --git a/libs/horizon/src/renderer/render/LazyComponent.ts b/libs/horizon/src/renderer/render/LazyComponent.ts new file mode 100644 index 00000000..65f7285c --- /dev/null +++ b/libs/horizon/src/renderer/render/LazyComponent.ts @@ -0,0 +1,82 @@ +import type {VNode} from '../Types'; + +import {FlagUtils} from '../vnode/VNodeFlags'; +import {getLazyVNodeTag} from '../vnode/VNodeCreator'; +import { + ClassComponent, + ForwardRef, + FunctionComponent, + MemoComponent, +} from '../vnode/VNodeTags'; +import {throwIfTrue} from '../utils/throwIfTrue'; +import {captureFunctionComponent} from './FunctionComponent'; +import {captureClassComponent} from './ClassComponent'; +import {captureMemoComponent} from './MemoComponent'; + +export function captureRender(processing: VNode, shouldUpdate: boolean): VNode | null { + return captureLazyComponent(processing, processing.type, shouldUpdate); +} + +export function bubbleRender() {} + +const LazyRendererMap = { + [FunctionComponent]: captureFunctionComponent, + [ClassComponent]: captureClassComponent, + [ForwardRef]: captureFunctionComponent, + [MemoComponent]: captureMemoComponent, +}; + +function captureLazyComponent( + processing, + lazyComponent, + shouldUpdate, +) { + if (!processing.isCreated) { + // 每次加载lazy都当作mount来处理 + processing.isCreated = true; + FlagUtils.markAddition(processing); + } + + const Component = lazyComponent._load(lazyComponent._content); + + // ======================loaded=============================== + // 加载得到的Component存在type中 + processing.type = Component; + + const lazyVNodeTag = processing.tag = getLazyVNodeTag(Component); + const lazyVNodeProps = mergeDefaultProps(Component, processing.props); + + const lazyRender = LazyRendererMap[lazyVNodeTag]; + if (lazyRender) { + if (lazyVNodeTag === MemoComponent) { + // Memo要特殊处理 + const memoVNodeProps = mergeDefaultProps(Component.type, lazyVNodeProps); // 需要整合defaultProps + return lazyRender(processing, Component, memoVNodeProps, shouldUpdate); + } else { + return lazyRender(processing, Component, lazyVNodeProps, false); + } + } else { + // lazy加载的组件类型未受支持 + throwIfTrue( + true, + 'Element type is invalid. Received a promise that resolves to: %s. ' + + 'Lazy element type must resolve to a class or function.%s', + Component, + '', + ); + } +} + +export function mergeDefaultProps(Component: any, props: object): object { + if (Component && Component.defaultProps) { + const clonedProps = {...props}; + const defaultProps = Component.defaultProps; + Object.keys(defaultProps).forEach(key => { + if (clonedProps[key] === undefined) { + clonedProps[key] = defaultProps[key]; + } + }); + return clonedProps; + } + return props; +} diff --git a/libs/horizon/src/renderer/render/MemoComponent.ts b/libs/horizon/src/renderer/render/MemoComponent.ts new file mode 100644 index 00000000..4be5060c --- /dev/null +++ b/libs/horizon/src/renderer/render/MemoComponent.ts @@ -0,0 +1,61 @@ +import type {VNode} from '../Types'; + +import {mergeDefaultProps} from './LazyComponent'; +import {updateVNode, createVNode, onlyUpdateChildVNodes, updateVNodePath} from '../vnode/VNodeCreator'; +import {shallowCompare} from '../utils/compare'; +import { + TYPE_FRAGMENT, + TYPE_PROFILER, + TYPE_STRICT_MODE, +} from '../utils/elementType'; +import {Fragment} from '../vnode/VNodeTags'; + +export function captureRender(processing: VNode, shouldUpdate: boolean): Array | null { + return captureMemoComponent(processing, shouldUpdate); +} + +export function bubbleRender() {} + +export function captureMemoComponent( + processing: VNode, + shouldUpdate: boolean, +): Array | null { + const Component = processing.type; + // 合并 函数组件或类组件 的defaultProps + const newProps = mergeDefaultProps(Component, processing.props); + + if (processing.isCreated) { + let newChild = null; + const type = Component.type; + if (type === TYPE_STRICT_MODE || type === TYPE_FRAGMENT || type === TYPE_PROFILER) { + newChild = createVNode(Fragment, null, newProps.children); + } else { + newChild = createVNode('props', type, null, newProps, processing); + } + newChild.parent = processing; + newChild.ref = processing.ref; + updateVNodePath(newChild); + processing.children = [newChild]; + + return processing.children; + } + + const firstChild = processing.children.length ? processing.children[0] : null; // Memo只有一个child + if (!shouldUpdate) { + const oldProps = firstChild.props; + // 默认是浅对比 + const compare = Component.compare ? Component.compare : shallowCompare; + if (compare(oldProps, newProps) && processing.oldRef === processing.ref) { + return onlyUpdateChildVNodes(processing); + } + } + + const newChild = updateVNode(firstChild, newProps); + newChild.parent = processing; + newChild.cIndex = 0; + updateVNodePath(newChild); + newChild.ref = processing.ref; + processing.children = [newChild]; + + return processing.children; +} diff --git a/libs/horizon/src/renderer/render/SuspenseComponent.ts b/libs/horizon/src/renderer/render/SuspenseComponent.ts new file mode 100644 index 00000000..119c1f9c --- /dev/null +++ b/libs/horizon/src/renderer/render/SuspenseComponent.ts @@ -0,0 +1,239 @@ +import type {VNode, PromiseType} from '../Types'; + +import {FlagUtils, Interrupted} from '../vnode/VNodeFlags'; +import {createVNode, onlyUpdateChildVNodes, updateVNode, updateVNodePath} from '../vnode/VNodeCreator'; +import { + ClassComponent, + IncompleteClassComponent, + SuspenseComponent, + Fragment, +} from '../vnode/VNodeTags'; +import {pushForceUpdate} from '../UpdateHandler'; +import {launchUpdateFromVNode, tryRenderRoot} from '../TreeBuilder'; +import {updateShouldUpdateOfTree} from '../vnode/VNodeShouldUpdate'; +import {getContextChangeCtx} from '../ContextSaver'; + +export enum SuspenseChildStatus { + Init = '', + ShowChild = 'showChild', + ShowFallback = 'showFallback', +} + +export function captureRender(processing: VNode, shouldUpdate: boolean): Array | VNode | null { + if ( + !processing.isCreated && + processing.oldProps === processing.props && + !getContextChangeCtx() && + !shouldUpdate + ) { + if (processing.suspenseChildStatus === SuspenseChildStatus.ShowFallback) { + // 当显示fallback时,suspense的子组件要更新 + return updateFallback(processing); + } + return onlyUpdateChildVNodes(processing); + } + + return captureSuspenseComponent(processing); +} + +function updateFallback(processing: VNode): Array | VNode | null { + const childFragment: VNode = processing.children[0]; + + if (childFragment.childShouldUpdate) { + if (processing.promiseResolve) { + // promise已完成,展示promise返回的新节点 + return captureSuspenseComponent(processing); + } else { + // promise未完成,继续显示fallback,不需要继续刷新子节点 + const fallbackFragment: VNode = processing.children[1]; + childFragment.childShouldUpdate = false; + fallbackFragment.childShouldUpdate = false; + return null; + } + } else { + const children = onlyUpdateChildVNodes(processing); + + if (children !== null) { + // child不需要更新,跳过child处理fallback + return children[1]; + } else { + return null; + } + } +} + +export function bubbleRender(processing: VNode) { + if (processing.suspenseChildStatus === SuspenseChildStatus.ShowFallback + || (!processing.isCreated && processing.oldSuspenseChildStatus === SuspenseChildStatus.ShowFallback) + ) { + FlagUtils.markUpdate(processing); + } + + return null; +} + +export function captureSuspenseComponent(processing: VNode) { + const nextProps = processing.props; + + // suspense被捕获后需要展示fallback + const showFallback = processing.suspenseDidCapture; + + if (showFallback) { + processing.suspenseDidCapture = false; + const nextFallbackChildren = nextProps.fallback; + return createFallback(processing, nextFallbackChildren); + } else { + const newChildren = nextProps.children; + return createSuspenseChildren(processing, newChildren); + } +} + +// 创建子节点 +function createSuspenseChildren(processing: VNode, newChildren) { + let childFragment: VNode; + if (!processing.isCreated) { + const oldChildFragment: VNode = processing.children[0]; + const oldFallbackFragment: VNode | null = processing.children.length > 1 ? processing.children[1] : null; + + childFragment = updateVNode(oldChildFragment); + // 将Suspense新的子参数传给子Fragment + childFragment.props = processing.props.children; + childFragment.shouldUpdate = true; + + // 删除fallback + if (oldFallbackFragment !== null) { + FlagUtils.setDeletion(oldFallbackFragment); + processing.dirtyNodes = [oldFallbackFragment]; + } + // SuspenseComponent 中使用 + processing.suspenseChildStatus = SuspenseChildStatus.ShowChild; + } else { + childFragment = createVNode(Fragment, null, newChildren); + } + + childFragment.parent = processing; + childFragment.cIndex = 0; + updateVNodePath(childFragment); + processing.children = [childFragment]; + processing.promiseResolve = false; + return processing.children; +} + +// 创建fallback子节点 +function createFallback(processing: VNode, fallbackChildren) { + const childFragment: VNode = processing.children[0]; + let fallbackFragment; + childFragment.childShouldUpdate = false; + + if (!processing.isCreated) { + const oldFallbackFragment: VNode | null = processing.oldChildren.length > 1 ? processing.oldChildren[1] : null; + + if (oldFallbackFragment !== null) { + fallbackFragment = updateVNode(oldFallbackFragment, fallbackChildren); + } else { + fallbackFragment = createVNode(Fragment, null, fallbackChildren); + FlagUtils.markAddition(fallbackFragment); + } + } else { + // 创建 + fallbackFragment = createVNode(Fragment, null, fallbackChildren); + } + + processing.children = [childFragment, fallbackFragment]; + childFragment.parent = processing; + fallbackFragment.parent = processing; + fallbackFragment.eIndex = 1; + fallbackFragment.cIndex = 1; + updateVNodePath(fallbackFragment); + processing.suspenseChildStatus = SuspenseChildStatus.ShowFallback; + + return [fallbackFragment]; +} + +// 处理Suspense子组件抛出的promise +export function handleSuspenseChildThrowError(parent: VNode, processing: VNode, error: any): boolean { + let vNode = parent; + + // 向上找到最近的不在fallback状态的Suspense,并触发重新渲染 + do { + if (vNode.tag === SuspenseComponent && canCapturePromise(vNode)) { + if (vNode.suspensePromises === null) { + vNode.suspensePromises = new Set(); + } + vNode.suspensePromises.add(error); + + processing.suspenseChildThrow = true; + + // 移除生命周期flag 和 中断flag + FlagUtils.removeLifecycleEffectFlags(processing); + FlagUtils.removeFlag(processing, Interrupted); + + if (processing.tag === ClassComponent) { + if (processing.isCreated) { + // 渲染类组件场景,要标志未完成(否则会触发componentWillUnmount) + processing.tag = IncompleteClassComponent; + } else { + // 类组件更新,标记强制更新,否则被memo等优化跳过 + pushForceUpdate(processing); + launchUpdateFromVNode(processing); + } + } + + // 应该抛出promise未完成更新,标志待更新 + processing.shouldUpdate = true; + + vNode.suspenseDidCapture = true; + launchUpdateFromVNode(vNode); + + return true; + } + + vNode = vNode.parent; + } while (vNode !== null); + + return false; +} + +function canCapturePromise(vNode: VNode): boolean { + return vNode.suspenseChildStatus !== SuspenseChildStatus.ShowFallback && vNode.props.fallback !== undefined; +} + +const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set; + +// 对于每个promise,添加一个侦听器,以便当它resolve时,重新渲染 +export function listenToPromise(suspenseVNode: VNode) { + const promises: Set> | null = suspenseVNode.suspensePromises; + if (promises !== null) { + suspenseVNode.suspensePromises = null; + + // 记录已经监听的 promise + let promiseCache = suspenseVNode.realNode; + if (promiseCache === null) { + // @ts-ignore + promiseCache = suspenseVNode.realNode = new PossiblyWeakSet(); + } + + promises.forEach(promise => { + const resole = resolvePromise.bind(null, suspenseVNode, promise); + if (!promiseCache.has(promise)) { + promiseCache.add(promise); + // 监听promise + promise.then(resole, resole); + } + }); + } +} + +function resolvePromise(suspenseVNode: VNode, promise: PromiseType) { + const promiseCache = suspenseVNode.realNode; + if (promiseCache !== null) { + promiseCache.delete(promise); + } + suspenseVNode.promiseResolve = true; + const root = updateShouldUpdateOfTree(suspenseVNode); + if (root !== null) { + tryRenderRoot(root); + } +} + + diff --git a/libs/horizon/src/renderer/render/TreeRoot.ts b/libs/horizon/src/renderer/render/TreeRoot.ts new file mode 100644 index 00000000..075e3984 --- /dev/null +++ b/libs/horizon/src/renderer/render/TreeRoot.ts @@ -0,0 +1,42 @@ +import type {VNode} from '../Types'; +import {throwIfTrue} from '../utils/throwIfTrue'; +import {processUpdates} from '../UpdateHandler'; +import {createVNodeChildren} from './BaseComponent'; +import {resetNamespaceCtx, setNamespaceCtx} from '../ContextSaver'; +import {resetOldCtx} from '../components/context/CompatibleContext'; +import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; + +export function captureRender(processing: VNode): VNode | null { + return updateTreeRoot(processing); +} + +export function bubbleRender(processing: VNode) { + resetNamespaceCtx(processing); + resetOldCtx(processing); +} + +function updateTreeRoot(processing) { + setNamespaceCtx(processing, processing.outerDom); + + const updates = processing.updates; + throwIfTrue( + processing.isCreated || updates === null, + 'If the root does not have an updates, we should have already ' + + 'bailed out. This error is likely caused by a bug. Please ' + + 'file an issue.', + ); + + const newProps = processing.props; + const oldState = processing.state; + const oldElement = oldState !== null ? oldState.element : null; + processUpdates(processing, null, newProps); + + const newState = processing.state; + // 为了保持对Dev Tools的兼容,这里还是使用element + const newElement = newState.element; + if (newElement === oldElement) { + return onlyUpdateChildVNodes(processing); + } + processing.children = createVNodeChildren(processing, newElement); + return processing.children; +} diff --git a/libs/horizon/src/renderer/render/index.ts b/libs/horizon/src/renderer/render/index.ts new file mode 100644 index 00000000..29637aa3 --- /dev/null +++ b/libs/horizon/src/renderer/render/index.ts @@ -0,0 +1,56 @@ +import * as BaseComponentRender from './BaseComponent'; +import * as ClassComponentRender from './ClassComponent'; +import * as ContextConsumerRender from './ContextConsumer'; +import * as ContextProviderRender from './ContextProvider'; +import * as ForwardRefRender from './ForwardRef'; +import * as FragmentRender from './Fragment'; +import * as FunctionComponentRender from './FunctionComponent'; +import * as DomComponentRender from './DomComponent'; +import * as DomPortalRender from './DomPortal'; +import * as TreeRootRender from './TreeRoot'; +import * as DomTextRender from './DomText'; +import * as IncompleteClassComponentRender from './IncompleteClassComponent'; +import * as ClsOrFunComponentRender from './ClsOrFunComponent'; +import * as LazyComponentRender from './LazyComponent'; +import * as MemoComponentRender from './MemoComponent'; +import * as SuspenseComponentRender from './SuspenseComponent'; + +import { + ClassComponent, + ContextConsumer, + ContextProvider, + ForwardRef, + Fragment, + FunctionComponent, + DomComponent, + DomPortal, + TreeRoot, + DomText, + IncompleteClassComponent, + ClsOrFunComponent, + LazyComponent, + MemoComponent, + SuspenseComponent, +} from '../vnode/VNodeTags'; + +export { + BaseComponentRender +} + +export default { + [ClassComponent]: ClassComponentRender, + [ContextConsumer]: ContextConsumerRender, + [ContextProvider]: ContextProviderRender, + [ForwardRef]: ForwardRefRender, + [Fragment]: FragmentRender, + [FunctionComponent]: FunctionComponentRender, + [DomComponent]: DomComponentRender, + [DomPortal]: DomPortalRender, + [TreeRoot]: TreeRootRender, + [DomText]: DomTextRender, + [IncompleteClassComponent]: IncompleteClassComponentRender, + [ClsOrFunComponent]: ClsOrFunComponentRender, + [LazyComponent]: LazyComponentRender, + [MemoComponent]: MemoComponentRender, + [SuspenseComponent]: SuspenseComponentRender, +}