diff --git a/libs/horizon/src/dom/DOMExternal.ts b/libs/horizon/src/dom/DOMExternal.ts index 237e8de2..02742120 100644 --- a/libs/horizon/src/dom/DOMExternal.ts +++ b/libs/horizon/src/dom/DOMExternal.ts @@ -10,30 +10,6 @@ import {findDOMByClassInst} from '../renderer/vnode/VNodeUtils'; import {TreeRoot} from '../renderer/vnode/VNodeTags'; import {Callback} from '../renderer/UpdateHandler'; -function executeRender( - children: any, - container: Container, - callback?: Callback, -) { - let treeRoot = container._treeRoot; - - if (!treeRoot) { - treeRoot = createRoot(children, container, callback); - } else { // container被render过 - if (typeof callback === 'function') { - const cb = callback; - callback = function () { - const instance = getFirstCustomDom(treeRoot); - cb.call(instance); - }; - } - // 执行更新操作 - startUpdate(children, treeRoot, callback); - } - - return getFirstCustomDom(treeRoot); -} - function createRoot(children: any, container: Container, callback?: Callback) { // 清空容器 let child = container.lastChild; @@ -66,8 +42,32 @@ function createRoot(children: any, container: Container, callback?: Callback) { return treeRoot; } +function executeRender( + children: any, + container: Container, + callback?: Callback, +) { + let treeRoot = container._treeRoot; + + if (!treeRoot) { + treeRoot = createRoot(children, container, callback); + } else { // container被render过 + if (typeof callback === 'function') { + const cb = callback; + callback = function () { + const instance = getFirstCustomDom(treeRoot); + cb.call(instance); + }; + } + // 执行更新操作 + startUpdate(children, treeRoot, callback); + } + + return getFirstCustomDom(treeRoot); +} + function findDOMNode(domOrEle: Element): null | Element | Text { - if (domOrEle == null) { + if (!domOrEle) { return null; } diff --git a/libs/horizon/src/dom/DOMInternalKeys.ts b/libs/horizon/src/dom/DOMInternalKeys.ts index dec6fed1..55621aae 100644 --- a/libs/horizon/src/dom/DOMInternalKeys.ts +++ b/libs/horizon/src/dom/DOMInternalKeys.ts @@ -72,7 +72,7 @@ export function getNearestVNode(dom: Node): null | VNode { } // 获取 vNode 上的属性相关信息 -export function getVNodeProps(dom: Element | Text): Props { +export function getVNodeProps(dom: Element | Text): Props | null{ return dom[internalKeys.props] || null; } @@ -93,7 +93,8 @@ export function getEventListeners(dom: EventTarget): Set { export function getEventToListenerMap(target: EventTarget): Map { let eventsMap = target[internalKeys.nonDelegatedEvents]; if (!eventsMap) { - eventsMap = target[internalKeys.nonDelegatedEvents] = new Map(); + eventsMap = new Map(); + target[internalKeys.nonDelegatedEvents] = new Map(); } return eventsMap; } diff --git a/libs/horizon/src/dom/DOMOperator.ts b/libs/horizon/src/dom/DOMOperator.ts index b475375a..9033a028 100644 --- a/libs/horizon/src/dom/DOMOperator.ts +++ b/libs/horizon/src/dom/DOMOperator.ts @@ -5,13 +5,13 @@ import { import { createDom, } from './utils/DomCreator'; -import {getSelectionInfo, resetSelectionRange, selectionData} from './SelectionRangeHandler'; -import {isElement, isComment, isDocument, isDocumentFragment, getDomTag, shouldAutoFocus} from './utils/Common'; -import {NSS} from './utils/DomCreator'; -import {adjustStyleValue} from './DOMPropertiesHandler/StyleHandler'; +import { getSelectionInfo, resetSelectionRange, selectionData } from './SelectionRangeHandler'; +import { isElement, isComment, isDocument, isDocumentFragment, getDomTag, shouldAutoFocus } from './utils/Common'; +import { NSS } from './utils/DomCreator'; +import { adjustStyleValue } from './DOMPropertiesHandler/StyleHandler'; -import {listenDelegatedEvents} from '../event/EventBinding'; -import type {VNode} from '../renderer/Types'; +import { listenDelegatedEvents } from '../event/EventBinding'; +import type { VNode } from '../renderer/Types'; import { setInitValue, getPropsWithoutValue, @@ -21,10 +21,10 @@ import { compareProps, setDomProps, updateDomProps } from './DOMPropertiesHandler/DOMPropertiesHandler'; -import {isNativeElement, validateProps} from './validators/ValidateProps'; -import {watchValueChange} from './valueHandler/ValueChangeHandler'; -import {DomComponent, DomText} from '../renderer/vnode/VNodeTags'; -import {updateCommonProp} from './DOMPropertiesHandler/UpdateCommonProp'; +import { isNativeElement, validateProps } from './validators/ValidateProps'; +import { watchValueChange } from './valueHandler/ValueChangeHandler'; +import { DomComponent, DomText } from '../renderer/vnode/VNodeTags'; +import { updateCommonProp } from './DOMPropertiesHandler/UpdateCommonProp'; export type Props = { autoFocus?: boolean; @@ -55,13 +55,7 @@ function getChildNS(parentNS: string | null, tagName: string): string { // 获取容器 export function getNSCtx(dom: Container, parentNS: string, type: string): string { - let namespace; - if (dom) { - namespace = getChildNS(dom.namespaceURI ?? null, dom.nodeName); - } else { - namespace = getChildNS(parentNS, type); - } - return namespace; + return dom ? getChildNS(dom.namespaceURI ?? null, dom.nodeName) : getChildNS(parentNS, type); } export function prepareForSubmit(): void { @@ -156,7 +150,7 @@ export function newTextDom( // 提交vNode的类型为Component或者Text的更新 export function submitDomUpdate(tag: string, vNode: VNode) { const newProps = vNode.props; - const element: Element = vNode.realNode; + const element: Element | null = vNode.realNode; if (tag === DomComponent) { // DomComponent类型 @@ -179,8 +173,10 @@ export function submitDomUpdate(tag: string, vNode: VNode) { } } } else if (tag === DomText) { - // text类型 - element.textContent = newProps; + if (element != null) { + // text类型 + element.textContent = newProps; + } } } diff --git a/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts b/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts index 3d5f7d36..5016d811 100644 --- a/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts +++ b/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts @@ -8,6 +8,27 @@ import { } from '../../event/EventBinding'; import { isEventProp, isNativeElement } from '../validators/ValidateProps'; +function updateOneProp(dom, propName, propVal, isNativeTag, isInit?: boolean) { + if (propName === 'style') { + setStyles(dom, propVal); + } else if (propName === 'dangerouslySetInnerHTML') { + dom.innerHTML = propVal.__html; + } else if (propName === 'children') { // 只处理纯文本子节点,其他children在VNode树中处理 + if (typeof propVal === 'string' || typeof propVal === 'number') { + dom.textContent = String(propVal); + } + } else if (isEventProp(propName)) { + // 事件监听属性处理 + if (!allDelegatedHorizonEvents.has(propName)) { + listenNonDelegatedEvent(propName, dom, propVal); + } + } else { + if (!isInit || (isInit && propVal != null)) { + updateCommonProp(dom, propName, propVal, isNativeTag); + } + } +} + // 初始化DOM属性 export function setDomProps( tagName: string, @@ -38,27 +59,6 @@ export function updateDomProps( } } -function updateOneProp(dom, propName, propVal, isNativeTag, isInit?: boolean) { - if (propName === 'style') { - setStyles(dom, propVal); - } else if (propName === 'dangerouslySetInnerHTML') { - dom.innerHTML = propVal.__html; - } else if (propName === 'children') { // 只处理纯文本子节点,其他children在VNode树中处理 - if (typeof propVal === 'string' || typeof propVal === 'number') { - dom.textContent = String(propVal); - } - } else if (isEventProp(propName)) { - // 事件监听属性处理 - if (!allDelegatedHorizonEvents.has(propName)) { - listenNonDelegatedEvent(propName, dom, propVal); - } - } else { - if (!isInit || (isInit && propVal != null)) { - updateCommonProp(dom, propName, propVal, isNativeTag); - } - } -} - // 找出两个 DOM 属性的差别,生成需要更新的属性集合 export function compareProps( oldProps: Object, diff --git a/libs/horizon/src/event/HorizonEventMain.ts b/libs/horizon/src/event/HorizonEventMain.ts index 55f34969..ad3dffda 100644 --- a/libs/horizon/src/event/HorizonEventMain.ts +++ b/libs/horizon/src/event/HorizonEventMain.ts @@ -122,7 +122,9 @@ function getProcessListenersFacade( )); } - if (nativeEvtName === 'compositionend' || nativeEvtName === 'compositionstart' || nativeEvtName === 'compositionupdate') { + if (nativeEvtName === 'compositionend' || + nativeEvtName === 'compositionstart' || + nativeEvtName === 'compositionupdate') { processingListenerList = processingListenerList.concat(getCompositionListeners( nativeEvtName, nativeEvent, @@ -183,13 +185,14 @@ export function handleEventMain( // 有事件正在执行,同步执行事件 if (isInEventsExecution) { - return triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, rootVNode); + triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, rootVNode); + return; } // 没有事件在执行,经过调度再执行事件 isInEventsExecution = true; try { - return asyncUpdates(() => + asyncUpdates(() => triggerHorizonEvents( nativeEvtName, isCapture, diff --git a/libs/horizon/src/event/WrapperListener.ts b/libs/horizon/src/event/WrapperListener.ts index 9c73299c..ba5d309b 100644 --- a/libs/horizon/src/event/WrapperListener.ts +++ b/libs/horizon/src/event/WrapperListener.ts @@ -5,15 +5,6 @@ import {handleEventMain} from './HorizonEventMain'; import {runDiscreteUpdates} from '../renderer/Renderer'; import {getEventTarget} from './utils'; -// 生成委托事件的监听方法 -export function createCustomEventListener( - target: EventTarget, - nativeEvtName: string, - isCapture: boolean, -): EventListener { - return triggerDelegatedEvent.bind(null, nativeEvtName, isCapture, target); -} - // 触发委托事件 function triggerDelegatedEvent( nativeEvtName: string, @@ -39,3 +30,12 @@ function triggerDelegatedEvent( } handleEventMain(nativeEvtName, isCapture, nativeEvent, targetVNode, targetDom); } + +// 生成委托事件的监听方法 +export function createCustomEventListener( + target: EventTarget, + nativeEvtName: string, + isCapture: boolean, +): EventListener { + return triggerDelegatedEvent.bind(null, nativeEvtName, isCapture, target); +} diff --git a/libs/horizon/src/event/customEvents/CustomBaseEvent.ts b/libs/horizon/src/event/customEvents/CustomBaseEvent.ts index d4595d31..5ea40d96 100644 --- a/libs/horizon/src/event/customEvents/CustomBaseEvent.ts +++ b/libs/horizon/src/event/customEvents/CustomBaseEvent.ts @@ -4,6 +4,48 @@ import {VNode} from '../../renderer/Types'; +// 从原生事件中复制属性到自定义事件中 +function extendAttribute(target, source) { + const attributes = [ + // AnimationEvent + 'animationName', 'elapsedTime', 'pseudoElement', + // CompositionEvent、InputEvent + 'data', + // DragEvent + 'dataTransfer', + // FocusEvent + 'relatedTarget', + // KeyboardEvent + 'key', 'keyCode', 'charCode', 'code', 'location', 'ctrlKey', 'shiftKey', + 'altKey', 'metaKey', 'repeat', 'locale', 'getModifierState', 'clipboardData', + // MouseEvent + 'button', 'buttons', 'clientX', 'clientY', 'movementX', 'movementY', + 'pageX', 'pageY', 'screenX', 'screenY', 'currentTarget', + // PointerEvent + 'pointerId', 'width', 'height', 'pressure', 'tangentialPressure', + 'tiltX', 'tiltY', 'twist', 'pointerType', 'isPrimary', + // TouchEvent + 'touches', 'targetTouches', 'changedTouches', + // TransitionEvent + 'propertyName', + // UIEvent + 'view', 'detail', + // WheelEvent + 'deltaX', 'deltaY', 'deltaZ', 'deltaMode', + ]; + + attributes.forEach(attr => { + if (typeof source[attr] !== 'undefined') { + if (typeof source[attr] === 'function') { + target[attr] = function() { + return source[attr].apply(source, arguments); + }; + } else { + target[attr] = source[attr]; + } + } + }) +} export class CustomBaseEvent { data: string; @@ -79,46 +121,3 @@ export class CustomBaseEvent { this.isPropagationStopped = () => true; } } - -// 从原生事件中复制属性到自定义事件中 -function extendAttribute(target, source) { - const attributes = [ - // AnimationEvent - 'animationName', 'elapsedTime', 'pseudoElement', - // CompositionEvent、InputEvent - 'data', - // DragEvent - 'dataTransfer', - // FocusEvent - 'relatedTarget', - // KeyboardEvent - 'key', 'keyCode', 'charCode', 'code', 'location', 'ctrlKey', 'shiftKey', - 'altKey', 'metaKey', 'repeat', 'locale', 'getModifierState', 'clipboardData', - // MouseEvent - 'button', 'buttons', 'clientX', 'clientY', 'movementX', 'movementY', - 'pageX', 'pageY', 'screenX', 'screenY', 'currentTarget', - // PointerEvent - 'pointerId', 'width', 'height', 'pressure', 'tangentialPressure', - 'tiltX', 'tiltY', 'twist', 'pointerType', 'isPrimary', - // TouchEvent - 'touches', 'targetTouches', 'changedTouches', - // TransitionEvent - 'propertyName', - // UIEvent - 'view', 'detail', - // WheelEvent - 'deltaX', 'deltaY', 'deltaZ', 'deltaMode', - ]; - - attributes.forEach(attr => { - if (typeof source[attr] !== 'undefined') { - if (typeof source[attr] === 'function') { - target[attr] = function() { - return source[attr].apply(source, arguments); - }; - } else { - target[attr] = source[attr]; - } - } - }) -} diff --git a/libs/horizon/src/event/customEvents/CustomKeyboardEvent.ts b/libs/horizon/src/event/customEvents/CustomKeyboardEvent.ts index 33c6d13c..f4d0ae89 100644 --- a/libs/horizon/src/event/customEvents/CustomKeyboardEvent.ts +++ b/libs/horizon/src/event/customEvents/CustomKeyboardEvent.ts @@ -72,7 +72,7 @@ export class CustomKeyboardEvent extends CustomBaseEvent { super(customEvtName, nativeEvtName, nativeEvt, vNode, target); this.key = getKey(nativeEvt); this.charCode = nativeEvtName === 'keypress' ? uniqueCharCode(nativeEvt) : 0; - this.keyCode = (nativeEvtName === 'keydown' || nativeEvtName === 'keyup') ? nativeEvt.keyCode : 0; + this.keyCode = nativeEvtName === 'keydown' || nativeEvtName === 'keyup' ? nativeEvt.keyCode : 0; this.which = this.charCode || this.keyCode; } } diff --git a/libs/horizon/src/event/simulatedEvtHandler/BeforeInputEventHandler.ts b/libs/horizon/src/event/simulatedEvtHandler/BeforeInputEventHandler.ts index 833db40a..1a18ba27 100644 --- a/libs/horizon/src/event/simulatedEvtHandler/BeforeInputEventHandler.ts +++ b/libs/horizon/src/event/simulatedEvtHandler/BeforeInputEventHandler.ts @@ -9,7 +9,7 @@ const SPACE_CHAR = String.fromCharCode(CHAR_CODE_SPACE); function getInputCharsByNative( eventName: string, nativeEvent: any, -): string | void { +): string | null | void { if (eventName === 'compositionend') { return (nativeEvent.detail && nativeEvent.detail.data) || null; } diff --git a/libs/horizon/src/event/simulatedEvtHandler/SelectionEventHandler.ts b/libs/horizon/src/event/simulatedEvtHandler/SelectionEventHandler.ts index 81bc1fcc..87724137 100644 --- a/libs/horizon/src/event/simulatedEvtHandler/SelectionEventHandler.ts +++ b/libs/horizon/src/event/simulatedEvtHandler/SelectionEventHandler.ts @@ -13,7 +13,7 @@ const horizonEventName = 'onSelect' let currentElement = null; let currentVNode = null; -let lastSelection = null; +let lastSelection: Selection | null = null; function initTargetCache(dom, vNode) { if (isTextInputElement(dom) || dom.contentEditable === 'true') { @@ -85,7 +85,7 @@ export function getListeners( target: null | EventTarget, ): ProcessingListenerList { const targetNode = vNode ? getDom(vNode) : window; - let eventUnitList = []; + let eventUnitList: ProcessingListenerList = []; switch (name) { case 'focusin': initTargetCache(targetNode, vNode); diff --git a/libs/horizon/src/renderer/render/IncompleteClassComponent.ts b/libs/horizon/src/renderer/render/IncompleteClassComponent.ts index 7de50b1a..2c50ed18 100644 --- a/libs/horizon/src/renderer/render/IncompleteClassComponent.ts +++ b/libs/horizon/src/renderer/render/IncompleteClassComponent.ts @@ -10,7 +10,18 @@ import { cacheOldCtx, } from '../components/context/CompatibleContext'; -export function captureRender(processing: VNode): Array | null { +function captureIncompleteClassComponent(processing, Component, nextProps) { + processing.tag = ClassComponent; + + const hasOldContext = isOldProvider(Component); + cacheOldCtx(processing, hasOldContext); + + resetDepContexts(processing); + + return getIncompleteClassComponent(Component, processing, nextProps); +} + +export function captureRender(processing: VNode): VNode | null { const Component = processing.type; const unresolvedProps = processing.props; const resolvedProps = @@ -28,14 +39,3 @@ export function bubbleRender(processing: VNode) { 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 index f3dcc954..5277c1d1 100644 --- a/libs/horizon/src/renderer/render/LazyComponent.ts +++ b/libs/horizon/src/renderer/render/LazyComponent.ts @@ -13,10 +13,6 @@ 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 = { @@ -26,6 +22,20 @@ const LazyRendererMap = { [MemoComponent]: captureMemoComponent, }; +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; +} + function captureLazyComponent( processing, lazyComponent, @@ -43,7 +53,8 @@ function captureLazyComponent( // 加载得到的Component存在type中 processing.type = Component; - const lazyVNodeTag = processing.tag = getLazyVNodeTag(Component); + const lazyVNodeTag = getLazyVNodeTag(Component); + processing.tag = getLazyVNodeTag(Component); const lazyVNodeProps = mergeDefaultProps(Component, processing.props); const lazyRender = LazyRendererMap[lazyVNodeTag]; @@ -68,16 +79,6 @@ function captureLazyComponent( } } -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; +export function captureRender(processing: VNode, shouldUpdate: boolean): VNode | null { + return captureLazyComponent(processing, processing.type, shouldUpdate); } diff --git a/libs/horizon/src/renderer/render/SuspenseComponent.ts b/libs/horizon/src/renderer/render/SuspenseComponent.ts index d0cb6f9e..200c0c3d 100644 --- a/libs/horizon/src/renderer/render/SuspenseComponent.ts +++ b/libs/horizon/src/renderer/render/SuspenseComponent.ts @@ -19,107 +19,6 @@ export enum SuspenseChildStatus { 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.child; - - if (childFragment.childShouldUpdate) { - if (processing.promiseResolve) { - // promise已完成,展示promise返回的新节点 - return captureSuspenseComponent(processing); - } else { - // promise未完成,继续显示fallback,不需要继续刷新子节点 - const fallbackFragment: VNode = processing.child.next; - 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.child; - const oldFallbackFragment: VNode | null = oldChildFragment.next; - - childFragment = updateVNode(oldChildFragment); - childFragment.next = null; - // 将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.child = childFragment; - processing.promiseResolve = false; - return processing.child; -} - // 创建fallback子节点 function createFallback(processing: VNode, fallbackChildren) { const childFragment: VNode = processing.child; @@ -152,13 +51,118 @@ function createFallback(processing: VNode, fallbackChildren) { return fallbackFragment; } +// 创建子节点 +function createSuspenseChildren(processing: VNode, newChildren) { + let childFragment: VNode; + if (!processing.isCreated) { + const oldChildFragment: VNode = processing.child; + const oldFallbackFragment: VNode | null = oldChildFragment.next; + + childFragment = updateVNode(oldChildFragment); + childFragment.next = null; + // 将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.child = childFragment; + processing.promiseResolve = false; + return processing.child; +} + +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 updateFallback(processing: VNode): Array | VNode | null { + const childFragment: VNode | null= processing.child; + + if (childFragment?.childShouldUpdate) { + if (processing.promiseResolve) { + // promise已完成,展示promise返回的新节点 + return captureSuspenseComponent(processing); + } else { + // promise未完成,继续显示fallback,不需要继续刷新子节点 + const fallbackFragment: VNode = processing.child.next; + 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 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); +} + +export function bubbleRender(processing: VNode) { + if (processing.suspenseChildStatus === SuspenseChildStatus.ShowFallback + || (!processing.isCreated && processing.oldSuspenseChildStatus === SuspenseChildStatus.ShowFallback) + ) { + FlagUtils.markUpdate(processing); + } + + return null; +} + +function canCapturePromise(vNode: VNode | null): boolean { + return vNode?.suspenseChildStatus !== SuspenseChildStatus.ShowFallback && vNode?.props.fallback !== undefined; +} + // 处理Suspense子组件抛出的promise -export function handleSuspenseChildThrowError(parent: VNode, processing: VNode, error: any): boolean { +export function handleSuspenseChildThrowError(parent: VNode | null, processing: VNode, error: any): boolean { let vNode = parent; // 向上找到最近的不在fallback状态的Suspense,并触发重新渲染 do { - if (vNode.tag === SuspenseComponent && canCapturePromise(vNode)) { + if (vNode?.tag === SuspenseComponent && canCapturePromise(vNode)) { if (vNode.suspensePromises === null) { vNode.suspensePromises = new Set(); } @@ -196,36 +200,8 @@ export function handleSuspenseChildThrowError(parent: VNode, processing: VNode, 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) { @@ -238,4 +214,27 @@ function resolvePromise(suspenseVNode: VNode, promise: PromiseType) { } } +// 对于每个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 = new PossiblyWeakSet(); + 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); + } + }); + } +} diff --git a/libs/horizon/src/renderer/render/TreeRoot.ts b/libs/horizon/src/renderer/render/TreeRoot.ts index 8b4f2026..c9f54646 100644 --- a/libs/horizon/src/renderer/render/TreeRoot.ts +++ b/libs/horizon/src/renderer/render/TreeRoot.ts @@ -6,10 +6,6 @@ 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); @@ -40,3 +36,7 @@ function updateTreeRoot(processing) { processing.child = createVNodeChildren(processing, newElement); return processing.child; } + +export function captureRender(processing: VNode): VNode | null { + return updateTreeRoot(processing); +} diff --git a/libs/horizon/src/renderer/render/class/ClassLifeCycleProcessor.ts b/libs/horizon/src/renderer/render/class/ClassLifeCycleProcessor.ts index d6e21c2c..db796eb5 100644 --- a/libs/horizon/src/renderer/render/class/ClassLifeCycleProcessor.ts +++ b/libs/horizon/src/renderer/render/class/ClassLifeCycleProcessor.ts @@ -1,7 +1,7 @@ import type { VNode } from '../../Types'; import type { Callback } from '../../UpdateHandler'; -import {shallowCompare} from '../../utils/compare'; +import { shallowCompare } from '../../utils/compare'; import { pushUpdate, newUpdate, @@ -25,9 +25,11 @@ export function callDerivedStateFromProps( const newState = getDerivedStateFromProps(nextProps, oldState); // 组件未返回state,需要返回旧的preState - processing.state = newState === null || newState === undefined - ? oldState - : { ...oldState, ...newState }; + if (newState) { + processing.state = { ...oldState, ...newState }; + return; + } + processing.state = oldState; } } @@ -40,7 +42,7 @@ function changeStateContent(type: UpdateState, content: object, callback: Callba if (type === UpdateState.Update || type === UpdateState.Override) { update.content = content; } - if (callback !== undefined && callback !== null) { + if (callback) { update.callback = callback; }