diff --git a/libs/horizon/src/dom/DOMExternal.ts b/libs/horizon/src/dom/DOMExternal.ts index e9404cb7..077d0801 100644 --- a/libs/horizon/src/dom/DOMExternal.ts +++ b/libs/horizon/src/dom/DOMExternal.ts @@ -1,13 +1,13 @@ import { - asyncUpdates, createVNode, getFirstCustomDom, + asyncUpdates, getFirstCustomDom, syncUpdates, startUpdate, + createTreeRootVNode, } from '../renderer/Renderer'; import {createPortal} from '../renderer/components/CreatePortal'; import type {Container} from './DOMOperator'; import {isElement} from './utils/Common'; import {listenDelegatedEvents} from '../event/EventBinding'; import {findDOMByClassInst} from '../renderer/vnode/VNodeUtils'; -import {TreeRoot} from '../renderer/vnode/VNodeTags'; import {Callback} from '../renderer/UpdateHandler'; function createRoot(children: any, container: Container, callback?: Callback) { @@ -19,7 +19,7 @@ function createRoot(children: any, container: Container, callback?: Callback) { } // 调度器创建根节点,并给容器dom赋vNode结构体 - const treeRoot = createVNode(TreeRoot, container); + const treeRoot = createTreeRootVNode(container); container._treeRoot = treeRoot; // 根节点挂接全量事件 diff --git a/libs/horizon/src/dom/DOMInternalKeys.ts b/libs/horizon/src/dom/DOMInternalKeys.ts index 2be5522e..6ddbdb5b 100644 --- a/libs/horizon/src/dom/DOMInternalKeys.ts +++ b/libs/horizon/src/dom/DOMInternalKeys.ts @@ -19,7 +19,6 @@ const prefix = '_horizon'; const internalKeys = { VNode: `${prefix}VNode`, props: `${prefix}Props`, - events: `${prefix}Events`, nonDelegatedEvents: `${prefix}NonDelegatedEvents`, }; @@ -58,6 +57,7 @@ export function getNearestVNode(dom: Node): null | VNode { if (vNode) { // 如果是已经被框架标记过的 DOM 节点,那么直接返回其 VNode 实例 return vNode; } + // 下面处理的是为被框架标记过的 DOM 节点,向上找其父节点是否被框架标记过 let parentDom = dom.parentNode; let nearVNode = null; @@ -82,20 +82,11 @@ export function updateVNodeProps(dom: Element | Text, props: Props): void { dom[internalKeys.props] = props; } -export function getEventListeners(dom: EventTarget): Set { - let elementListeners = dom[internalKeys.events]; - if (!elementListeners) { - elementListeners = new Set(); - dom[internalKeys.events] = elementListeners; - } - return elementListeners; -} - -export function getEventToListenerMap(target: EventTarget): Map { - let eventsMap = target[internalKeys.nonDelegatedEvents]; +export function getNonDelegatedListenerMap(dom: Element | Text): Map { + let eventsMap = dom[internalKeys.nonDelegatedEvents]; if (!eventsMap) { eventsMap = new Map(); - target[internalKeys.nonDelegatedEvents] = eventsMap; + dom[internalKeys.nonDelegatedEvents] = eventsMap; } return eventsMap; } diff --git a/libs/horizon/src/dom/DOMOperator.ts b/libs/horizon/src/dom/DOMOperator.ts index cc023da9..969219f0 100644 --- a/libs/horizon/src/dom/DOMOperator.ts +++ b/libs/horizon/src/dom/DOMOperator.ts @@ -226,15 +226,6 @@ export function unHideDom(tag: string, dom: Element | Text, props: Props) { } } -export function clearContainer(container: Container): void { - if (isElement(container)) { - container.textContent = ''; - } - if (isDocument(container) && container.body != null) { - container.body.textContent = ''; - } -} - export function prePortal(portal: Element): void { listenDelegatedEvents(portal); } diff --git a/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts b/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts index 5ec79c33..482a15a9 100644 --- a/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts +++ b/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts @@ -14,8 +14,11 @@ function updateOneProp(dom, propName, isNativeTag, propVal?, isInit?: boolean) { } 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); + const type = typeof propVal; + if (type === 'string') { + dom.textContent = propVal; + } else if (type === 'number') { + dom.textContent = propVal + ''; // 这种数字转字符串的方式效率最高 } } else if (isEventProp(propName)) { // 事件监听属性处理 diff --git a/libs/horizon/src/dom/utils/Common.ts b/libs/horizon/src/dom/utils/Common.ts index dd836240..8de8f7c4 100644 --- a/libs/horizon/src/dom/utils/Common.ts +++ b/libs/horizon/src/dom/utils/Common.ts @@ -5,7 +5,7 @@ import {Props} from '../DOMOperator'; * 获取当前聚焦的 input 或者 textarea 元素 * @param doc 指定 document */ -export function getFocusedDom(doc?: Document): HorizonDom | void { +export function getFocusedDom(doc?: Document): HorizonDom | null { let currentDocument; if (doc) { currentDocument = doc; diff --git a/libs/horizon/src/dom/utils/DomCreator.ts b/libs/horizon/src/dom/utils/DomCreator.ts index 360aea81..9d89ef5f 100644 --- a/libs/horizon/src/dom/utils/DomCreator.ts +++ b/libs/horizon/src/dom/utils/DomCreator.ts @@ -5,6 +5,13 @@ export const NSS = { svg: 'http://www.w3.org/2000/svg', }; +const div = document.createElement('div'); +const span = document.createElement('span'); +const tr = document.createElement('tr'); +const td = document.createElement('td'); +const a = document.createElement('a'); +const p = document.createElement('p'); + // 创建DOM元素 export function createDom( tagName: string, @@ -16,6 +23,18 @@ export function createDom( if (ns !== NSS.html) { dom = document.createElementNS(ns, tagName); + } else if (tagName === 'div') { + dom = div.cloneNode(false); + } else if (tagName === 'span') { + dom = span.cloneNode(false); + } else if (tagName === 'tr') { + dom = tr.cloneNode(false); + } else if (tagName === 'td') { + dom = td.cloneNode(false); + } else if (tagName === 'a') { + dom = a.cloneNode(false); + } else if (tagName === 'p') { + dom = p.cloneNode(false); } else { dom = document.createElement(tagName); } diff --git a/libs/horizon/src/dom/valueHandler/index.ts b/libs/horizon/src/dom/valueHandler/index.ts index ca2ba25b..88a35e5c 100644 --- a/libs/horizon/src/dom/valueHandler/index.ts +++ b/libs/horizon/src/dom/valueHandler/index.ts @@ -21,6 +21,7 @@ import { getTextareaPropsWithoutValue, updateTextareaValue, } from './TextareaValueHandler'; +import {getDomTag} from "../utils/Common"; // 获取元素除了被代理的值以外的属性 function getPropsWithoutValue(type: string, dom: HorizonDom, properties: IProperty) { @@ -72,7 +73,8 @@ function updateValue(type: string, dom: HorizonDom, properties: IProperty) { } } -function resetValue(dom: HorizonDom, type: string, properties: IProperty) { +function resetValue(dom: HorizonDom, properties: IProperty) { + const type = getDomTag(dom); switch (type) { case 'input': resetInputValue(dom, properties); diff --git a/libs/horizon/src/event/ControlledValueUpdater.ts b/libs/horizon/src/event/ControlledValueUpdater.ts index dde7f99b..5e565a1d 100644 --- a/libs/horizon/src/event/ControlledValueUpdater.ts +++ b/libs/horizon/src/event/ControlledValueUpdater.ts @@ -1,15 +1,13 @@ import {getVNodeProps} from '../dom/DOMInternalKeys'; import {resetValue} from '../dom/valueHandler'; -import {getDomTag} from '../dom/utils/Common'; -let updateList = null; +let updateList: Array | null = null; // 受控组件值重新赋值 function updateValue(target: Element) { const props = getVNodeProps(target); if (props) { - const type = getDomTag(target); - resetValue(target, type, props); + resetValue(target, props); } } diff --git a/libs/horizon/src/event/EventBinding.ts b/libs/horizon/src/event/EventBinding.ts index 068280ea..512e87b5 100644 --- a/libs/horizon/src/event/EventBinding.ts +++ b/libs/horizon/src/event/EventBinding.ts @@ -1,50 +1,60 @@ /** - * 事件绑定实现 + * 事件绑定实现,分为绑定委托事件和非委托事件 */ import {allDelegatedNativeEvents} from './EventCollection'; import {isDocument} from '../dom/utils/Common'; import { - getEventListeners, - getEventToListenerMap, + getNearestVNode, + getNonDelegatedListenerMap, } from '../dom/DOMInternalKeys'; -import {createCustomEventListener} from './WrapperListener'; import {CustomBaseEvent} from './customEvents/CustomBaseEvent'; +import {runDiscreteUpdates} from '../renderer/TreeBuilder'; +import {isMounted} from '../renderer/vnode/VNodeUtils'; +import {SuspenseComponent} from '../renderer/vnode/VNodeTags'; +import {handleEventMain} from './HorizonEventMain'; -const listeningMarker = - '_horizonListening' + - Math.random() - .toString(36) - .slice(4); +const listeningMarker = '_horizonListening' + Math.random().toString(36).slice(4); -// 获取节点上已经委托事件名称 -function getListenerSetKey(nativeEvtName: string, isCapture: boolean): string { - const sufix = isCapture ? 'capture' : 'bubble'; - return `${nativeEvtName}__${sufix}`; +// 触发委托事件 +function triggerDelegatedEvent( + nativeEvtName: string, + isCapture: boolean, + targetDom: EventTarget, + nativeEvent, // 事件对象event +) { + // 执行之前的调度事件 + runDiscreteUpdates(); + + const nativeEventTarget = nativeEvent.target || nativeEvent.srcElement; + let targetVNode = getNearestVNode(nativeEventTarget); + + if (targetVNode !== null) { + if (isMounted(targetVNode)) { + if (targetVNode.tag === SuspenseComponent) { + targetVNode = null; + } + } else { + // vNode已销毁 + targetVNode = null; + } + } + handleEventMain(nativeEvtName, isCapture, nativeEvent, targetVNode, targetDom); } +// 监听委托事件 function listenToNativeEvent( nativeEvtName: string, delegatedElement: Element, isCapture: boolean, ): void { - let target: Element | Document = delegatedElement; + let dom: Element | Document = delegatedElement; // document层次可能触发selectionchange事件,为了捕获这类事件,selectionchange事件绑定在document节点上 if (nativeEvtName === 'selectionchange' && !isDocument(delegatedElement)) { - target = delegatedElement.ownerDocument; + dom = delegatedElement.ownerDocument; } - const listenerSet = getEventListeners(target); - const listenerSetKey = getListenerSetKey(nativeEvtName, isCapture); - - if (!listenerSet.has(listenerSetKey)) { - const listener = createCustomEventListener( - target, - nativeEvtName, - isCapture, - ); - target.addEventListener(nativeEvtName, listener, !!isCapture); - listenerSet.add(listenerSetKey); - } + const listener = triggerDelegatedEvent.bind(null, nativeEvtName, isCapture, dom); + dom.addEventListener(nativeEvtName, listener, isCapture); } // 监听所有委托事件 @@ -54,11 +64,12 @@ export function listenDelegatedEvents(dom: Element) { return; } dom[listeningMarker] = true; - allDelegatedNativeEvents.forEach((eventName: string) => { + + allDelegatedNativeEvents.forEach((nativeEvtName: string) => { // 委托冒泡事件 - listenToNativeEvent(eventName, dom, false); + listenToNativeEvent(nativeEvtName, dom, false); // 委托捕获事件 - listenToNativeEvent(eventName, dom, true); + listenToNativeEvent(nativeEvtName, dom, true); }); } @@ -77,7 +88,7 @@ function getNativeEvtName(horizonEventName, capture) { } // 是否捕获事件 -function getIsCapture(horizonEventName) { +function isCaptureEvent(horizonEventName) { if (horizonEventName === 'onLostPointerCapture' || horizonEventName === 'onGotPointerCapture') { return false; } @@ -86,7 +97,7 @@ function getIsCapture(horizonEventName) { // 封装监听函数 function getWrapperListener(horizonEventName, nativeEvtName, targetElement, listener) { - return (event) => { + return event => { const customEvent = new CustomBaseEvent(horizonEventName, nativeEvtName, event, null, targetElement); listener(customEvent); }; @@ -98,23 +109,24 @@ export function listenNonDelegatedEvent( domElement: Element, listener, ): void { - const isCapture = getIsCapture(horizonEventName); + const isCapture = isCaptureEvent(horizonEventName); const nativeEvtName = getNativeEvtName(horizonEventName, isCapture); // 先判断是否存在老的监听事件,若存在则移除 - const eventToListenerMap = getEventToListenerMap(domElement); - if (eventToListenerMap.get(horizonEventName)) { - domElement.removeEventListener(nativeEvtName, eventToListenerMap.get(horizonEventName)); + const nonDelegatedListenerMap = getNonDelegatedListenerMap(domElement); + const currentListener = nonDelegatedListenerMap.get(horizonEventName); + if (currentListener) { + domElement.removeEventListener(nativeEvtName, currentListener); + nonDelegatedListenerMap.delete(horizonEventName); } if (typeof listener !== 'function') { - eventToListenerMap.delete(nativeEvtName); return; } // 为了和委托事件对外行为一致,将事件对象封装成CustomBaseEvent const wrapperListener = getWrapperListener(horizonEventName, nativeEvtName, domElement, listener); // 添加新的监听 - eventToListenerMap.set(horizonEventName, wrapperListener); + nonDelegatedListenerMap.set(horizonEventName, wrapperListener); domElement.addEventListener(nativeEvtName, wrapperListener, isCapture); } diff --git a/libs/horizon/src/event/EventCollection.ts b/libs/horizon/src/event/EventCollection.ts index 8163df63..d70dcf10 100644 --- a/libs/horizon/src/event/EventCollection.ts +++ b/libs/horizon/src/event/EventCollection.ts @@ -8,6 +8,7 @@ export const allDelegatedNativeEvents = new Set(); horizonEventToNativeMap.forEach((dependencies, horizonEvent) => { allDelegatedHorizonEvents.set(horizonEvent, dependencies); allDelegatedHorizonEvents.set(horizonEvent + 'Capture', dependencies); + dependencies.forEach(d => { allDelegatedNativeEvents.add(d); }); diff --git a/libs/horizon/src/event/EventError.ts b/libs/horizon/src/event/EventError.ts deleted file mode 100644 index e5df0770..00000000 --- a/libs/horizon/src/event/EventError.ts +++ /dev/null @@ -1,24 +0,0 @@ -// 处理事件的错误 -let hasError = false; -let caughtError = null; - -// 执行事件监听器,并且捕捉第一个错误,事件执行完成后抛出第一个错误 -export function runListenerAndCatchFirstError(listener, event) { - try { - listener(event); - } catch (error) { - if (!hasError) { - hasError = true; - caughtError = error; - } - } -} - -export function throwCaughtEventError() { - if (hasError) { - const err = caughtError; - caughtError = null; - hasError = false; - throw err; - } -} diff --git a/libs/horizon/src/event/HorizonEventMain.ts b/libs/horizon/src/event/HorizonEventMain.ts index ad3dffda..9a9e2719 100644 --- a/libs/horizon/src/event/HorizonEventMain.ts +++ b/libs/horizon/src/event/HorizonEventMain.ts @@ -1,4 +1,4 @@ -import type { AnyNativeEvent, ProcessingListenerList } from './types'; +import type { AnyNativeEvent } from './Types'; import type { VNode } from '../renderer/Types'; import { @@ -7,24 +7,19 @@ import { EVENT_TYPE_BUBBLE, EVENT_TYPE_CAPTURE, } from './const'; -import { - throwCaughtEventError, - runListenerAndCatchFirstError, -} from './EventError'; import { getListeners as getBeforeInputListeners } from './simulatedEvtHandler/BeforeInputEventHandler'; import { getListeners as getCompositionListeners } from './simulatedEvtHandler/CompositionEventHandler'; import { getListeners as getChangeListeners } from './simulatedEvtHandler/ChangeEventHandler'; import { getListeners as getSelectionListeners } from './simulatedEvtHandler/SelectionEventHandler'; import { - getCustomEventNameWithOn, - uniqueCharCode, - getEventTarget + addOnPrefix, } from './utils'; -import { createCommonCustomEvent } from './customEvents/EventFactory'; +import { createCustomEvent } from './customEvents/EventFactory'; import { getListenersFromTree } from './ListenerGetter'; import { shouldUpdateValue, updateControlledValue } from './ControlledValueUpdater'; import { asyncUpdates, runDiscreteUpdates } from '../renderer/Renderer'; import { getExactNode } from '../renderer/vnode/VNodeUtils'; +import {ListenerUnitList} from './Types'; // 获取事件触发的普通事件监听方法队列 function getCommonListeners( @@ -33,14 +28,9 @@ function getCommonListeners( nativeEvent: AnyNativeEvent, target: null | EventTarget, isCapture: boolean, -): ProcessingListenerList { - const customEventName = getCustomEventNameWithOn(CommonEventToHorizonMap[nativeEvtName]); - if (!customEventName) { - return []; - } - - // 火狐浏览器兼容。火狐浏览器下功能键将触发keypress事件 火狐下keypress的charcode有值,keycode为0 - if (nativeEvtName === 'keypress' && uniqueCharCode(nativeEvent) === 0) { +): ListenerUnitList { + const horizonEvtName = addOnPrefix(CommonEventToHorizonMap[nativeEvtName]); + if (!horizonEvtName) { return []; } @@ -52,49 +42,42 @@ function getCommonListeners( if (nativeEvtName === 'focusin') { nativeEvtName = 'focus'; } + if (nativeEvtName === 'focusout') { nativeEvtName = 'blur'; } - const customEvent = createCommonCustomEvent(customEventName, nativeEvtName, nativeEvent, null, target); + const horizonEvent = createCustomEvent(horizonEvtName, nativeEvtName, nativeEvent, null, target); return getListenersFromTree( vNode, - customEventName, - customEvent, + horizonEvtName, + horizonEvent, isCapture ? EVENT_TYPE_CAPTURE : EVENT_TYPE_BUBBLE, ); } // 按顺序执行事件队列 -export function processListeners( - processingEventsList: ProcessingListenerList -): void { - processingEventsList.forEach(eventUnitList => { - let lastVNode; - eventUnitList.forEach(eventUnit => { - const { vNode, currentTarget, listener, event } = eventUnit; - if (vNode !== lastVNode && event.isPropagationStopped()) { - return; - } - event.currentTarget = currentTarget; - runListenerAndCatchFirstError(listener, event); - event.currentTarget = null; - lastVNode = vNode; - }); +function processListeners(listenerList: ListenerUnitList): void { + listenerList.forEach(eventUnit => { + const { currentTarget, listener, event } = eventUnit; + if (event.isPropagationStopped()) { + return; + } + event.currentTarget = currentTarget; + listener(event); + event.currentTarget = null; }); - // 执行所有事件后,重新throw遇到的第一个错误 - throwCaughtEventError(); } function getProcessListenersFacade( nativeEvtName: string, - vNode: VNode, + vNode: VNode | null, nativeEvent: AnyNativeEvent, target, isCapture: boolean -): ProcessingListenerList { +): ListenerUnitList { // 触发普通委托事件 - let processingListenerList: ProcessingListenerList = getCommonListeners( + let listenerList: ListenerUnitList = getCommonListeners( nativeEvtName, vNode, nativeEvent, @@ -105,7 +88,7 @@ function getProcessListenersFacade( // 触发特殊handler委托事件 if (!isCapture) { if (horizonEventToNativeMap.get('onChange').includes(nativeEvtName)) { - processingListenerList = processingListenerList.concat(getChangeListeners( + listenerList = listenerList.concat(getChangeListeners( nativeEvtName, nativeEvent, vNode, @@ -114,7 +97,7 @@ function getProcessListenersFacade( } if (horizonEventToNativeMap.get('onSelect').includes(nativeEvtName)) { - processingListenerList = processingListenerList.concat(getSelectionListeners( + listenerList = listenerList.concat(getSelectionListeners( nativeEvtName, nativeEvent, vNode, @@ -125,7 +108,7 @@ function getProcessListenersFacade( if (nativeEvtName === 'compositionend' || nativeEvtName === 'compositionstart' || nativeEvtName === 'compositionupdate') { - processingListenerList = processingListenerList.concat(getCompositionListeners( + listenerList = listenerList.concat(getCompositionListeners( nativeEvtName, nativeEvent, vNode, @@ -134,7 +117,7 @@ function getProcessListenersFacade( } if (horizonEventToNativeMap.get('onBeforeInput').includes(nativeEvtName)) { - processingListenerList = processingListenerList.concat(getBeforeInputListeners( + listenerList = listenerList.concat(getBeforeInputListeners( nativeEvtName, nativeEvent, vNode, @@ -142,7 +125,7 @@ function getProcessListenersFacade( )); } } - return processingListenerList; + return listenerList; } // 触发可以被执行的horizon事件监听 @@ -150,55 +133,47 @@ function triggerHorizonEvents( nativeEvtName: string, isCapture: boolean, nativeEvent: AnyNativeEvent, - vNode: null | VNode, + vNode: VNode | null, ): void { - const nativeEventTarget = getEventTarget(nativeEvent); - const processingListenerList = getProcessListenersFacade( - nativeEvtName, - vNode, - nativeEvent, - nativeEventTarget, - isCapture); + const nativeEventTarget = nativeEvent.target || nativeEvent.srcElement; + + // 获取委托事件队列 + const listenerList = getProcessListenersFacade(nativeEvtName, vNode, nativeEvent, nativeEventTarget, isCapture); // 处理触发的事件队列 - processListeners(processingListenerList); + processListeners(listenerList); } // 其他事件正在执行中标记 let isInEventsExecution = false; +// 处理委托事件入口 export function handleEventMain( nativeEvtName: string, isCapture: boolean, nativeEvent: AnyNativeEvent, vNode: null | VNode, - target: EventTarget, + targetContainer: EventTarget, ): void { - let rootVNode = vNode; - if (vNode !== null) { - rootVNode = getExactNode(vNode, target); - if (!rootVNode) { + let startVNode = vNode; + if (startVNode !== null) { + startVNode = getExactNode(startVNode, targetContainer); + if (!startVNode) { return; } } // 有事件正在执行,同步执行事件 if (isInEventsExecution) { - triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, rootVNode); + triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode); return; } // 没有事件在执行,经过调度再执行事件 isInEventsExecution = true; try { - asyncUpdates(() => - triggerHorizonEvents( - nativeEvtName, - isCapture, - nativeEvent, - rootVNode, - )); + asyncUpdates(() => triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode)); } finally { isInEventsExecution = false; if (shouldUpdateValue()) { diff --git a/libs/horizon/src/event/ListenerGetter.ts b/libs/horizon/src/event/ListenerGetter.ts index 3c233c48..8c20b960 100644 --- a/libs/horizon/src/event/ListenerGetter.ts +++ b/libs/horizon/src/event/ListenerGetter.ts @@ -1,73 +1,20 @@ import {VNode} from '../renderer/Types'; import {DomComponent} from '../renderer/vnode/VNodeTags'; -import {throwIfTrue} from '../renderer/utils/throwIfTrue'; -import type {Props} from '../dom/DOMOperator'; import {EVENT_TYPE_ALL, EVENT_TYPE_CAPTURE, EVENT_TYPE_BUBBLE} from './const'; -import {ProcessingListenerList, ListenerUnitList} from './Types'; +import {ListenerUnitList} from './Types'; import {CustomBaseEvent} from './customEvents/CustomBaseEvent'; -// 返回是否应该阻止事件响应标记,disabled组件不响应鼠标事件 -function shouldPrevent( - name: string, - type: string, - props: Props, -): boolean { - const canPreventMouseEvents = [ - 'onClick', - 'onClickCapture', - 'onDoubleClick', - 'onDoubleClickCapture', - 'onMouseDown', - 'onMouseDownCapture', - 'onMouseMove', - 'onMouseMoveCapture', - 'onMouseUp', - 'onMouseUpCapture', - 'onMouseEnter', - ]; - const interActiveElements = ['button', 'input', 'select', 'textarea']; - if (canPreventMouseEvents.includes(name)) { - return !!(props.disabled && interActiveElements.includes(type)); - } - return false; -} - -// 从vnode属性中获取事件listener -function getListener( - vNode: VNode, - eventName: string, -): Function | null { - const realNode = vNode.realNode; - if (realNode === null) { - return null; - } - const props = vNode.props; - if (props === null) { - return null; - } - const listener = props[eventName]; - if (shouldPrevent(eventName, vNode.type, props)) { - return null; - } - throwIfTrue( - listener && typeof listener !== 'function', - '`%s` listener should be a function.', - eventName - ); - return listener; -} - // 获取监听事件 export function getListenersFromTree( targetVNode: VNode | null, - name: string | null, + horizonEvtName: string | null, horizonEvent: CustomBaseEvent, eventType: string, -): ProcessingListenerList { - if (!name) { +): ListenerUnitList { + if (!horizonEvtName) { return []; } - const captureName = name + EVENT_TYPE_CAPTURE; + const listeners: ListenerUnitList = []; let vNode = targetVNode; @@ -77,7 +24,8 @@ export function getListenersFromTree( const {realNode, tag} = vNode; if (tag === DomComponent && realNode !== null) { if (eventType === EVENT_TYPE_ALL || eventType === EVENT_TYPE_CAPTURE) { - const captureListener = getListener(vNode, captureName); + const captureName = horizonEvtName + EVENT_TYPE_CAPTURE; + const captureListener = vNode.props[captureName]; if (captureListener) { listeners.unshift({ vNode, @@ -87,8 +35,9 @@ export function getListenersFromTree( }); } } + if (eventType === EVENT_TYPE_ALL || eventType === EVENT_TYPE_BUBBLE) { - const bubbleListener = getListener(vNode, name); + const bubbleListener = vNode.props[horizonEvtName]; if (bubbleListener) { listeners.push({ vNode, @@ -101,7 +50,8 @@ export function getListenersFromTree( } vNode = vNode.parent; } - return listeners.length > 0 ? [listeners]: []; + + return listeners; } diff --git a/libs/horizon/src/event/Types.ts b/libs/horizon/src/event/Types.ts index fb67cf86..b007bb2c 100644 --- a/libs/horizon/src/event/Types.ts +++ b/libs/horizon/src/event/Types.ts @@ -12,5 +12,3 @@ export type ListenerUnit = { }; export type ListenerUnitList = Array; - -export type ProcessingListenerList = Array; diff --git a/libs/horizon/src/event/WrapperListener.ts b/libs/horizon/src/event/WrapperListener.ts index ba5d309b..e69de29b 100644 --- a/libs/horizon/src/event/WrapperListener.ts +++ b/libs/horizon/src/event/WrapperListener.ts @@ -1,41 +0,0 @@ -import {isMounted} from '../renderer/vnode/VNodeUtils'; -import {SuspenseComponent} from '../renderer/vnode/VNodeTags'; -import {getNearestVNode} from '../dom/DOMInternalKeys'; -import {handleEventMain} from './HorizonEventMain'; -import {runDiscreteUpdates} from '../renderer/Renderer'; -import {getEventTarget} from './utils'; - -// 触发委托事件 -function triggerDelegatedEvent( - nativeEvtName: string, - isCapture: boolean, - targetDom: EventTarget, - nativeEvent, -) { - // 执行之前的调度事件 - runDiscreteUpdates(); - - const nativeEventTarget = getEventTarget(nativeEvent); - let targetVNode = getNearestVNode(nativeEventTarget); - - if (targetVNode !== null) { - if (isMounted(targetVNode)) { - if (targetVNode.tag === SuspenseComponent) { - targetVNode = null; - } - } else { - // vnode已销毁 - targetVNode = null; - } - } - 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/const.ts b/libs/horizon/src/event/const.ts index 20c954ae..75bea89f 100644 --- a/libs/horizon/src/event/const.ts +++ b/libs/horizon/src/event/const.ts @@ -34,7 +34,7 @@ export const horizonEventToNativeMap = new Map([ ['onCompositionStart', ['compositionstart']], ['onCompositionUpdate', ['compositionupdate']], ['onBeforeInput', ['compositionend', 'keypress', 'textInput']], - ['onChange', ['change', 'click', 'focusout', 'input',]], + ['onChange', ['change', 'click', 'focusout', 'input']], ['onSelect', ['focusout', 'contextmenu', 'dragend', 'focusin', 'keydown', 'keyup', 'mousedown', 'mouseup', 'selectionchange']], diff --git a/libs/horizon/src/event/customEvents/CustomBaseEvent.ts b/libs/horizon/src/event/customEvents/CustomBaseEvent.ts index 5ea40d96..e9ca60df 100644 --- a/libs/horizon/src/event/customEvents/CustomBaseEvent.ts +++ b/libs/horizon/src/event/customEvents/CustomBaseEvent.ts @@ -6,37 +6,17 @@ 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', - ]; + let val; + let attr; + for (attr in source) { + // 这两个方法需要override + if (attr === 'preventDefault' || attr === 'stopPropagation') { + continue; + } - attributes.forEach(attr => { - if (typeof source[attr] !== 'undefined') { - if (typeof source[attr] === 'function') { + val = source[attr]; + if (val !== undefined) { + if (typeof val === 'function') { target[attr] = function() { return source[attr].apply(source, arguments); }; @@ -44,54 +24,71 @@ function extendAttribute(target, source) { target[attr] = source[attr]; } } - }) + } } + +// 兼容IE的event key +const uniqueKeyMap = new Map([ + ['Esc', 'Escape'], + ['Spacebar', ' '], + ['Left', 'ArrowLeft'], + ['Up', 'ArrowUp'], + ['Right', 'ArrowRight'], + ['Down', 'ArrowDown'], + ['Del', 'Delete'], +]); + export class CustomBaseEvent { - data: string; defaultPrevented: boolean; - customEventName: string; - targetVNode: VNode; - type: string; - nativeEvent: any; target: EventTarget; - timeStamp: number; isDefaultPrevented: () => boolean; isPropagationStopped: () => boolean; currentTarget: EventTarget; + relatedTarget: EventTarget; + + // custom事件自定义属性 + customEventName: string; + targetVNode: VNode; + type: string; + timeStamp: number; + nativeEvent: any; + + // 键盘事件属性 + key: string; + charCode: number; + keyCode: number; + which: number; constructor( customEvtName: string | null, nativeEvtName: string, nativeEvt: { [propName: string]: any }, - vNode: VNode, + vNode: VNode | null, target: null | EventTarget ) { // 复制原生属性到自定义事件 extendAttribute(this, nativeEvt); + const defaultPrevented = nativeEvt.defaultPrevented != null ? + nativeEvt.defaultPrevented : + nativeEvt.returnValue === false; + this.defaultPrevented = defaultPrevented; + this.preventDefault = this.preventDefault.bind(this); + this.stopPropagation = this.stopPropagation.bind(this); + this.isDefaultPrevented = () => defaultPrevented; + this.isPropagationStopped = () => false; + this.relatedTarget = nativeEvt.relatedTarget; + this.target = target; + + // 键盘事件属性 + this.key = uniqueKeyMap.get(nativeEvt.key) || nativeEvt.key; + // custom事件自定义属性 this.customEventName = customEvtName; this.targetVNode = vNode; this.type = nativeEvtName; this.nativeEvent = nativeEvt; - this.target = target; - this.timeStamp = nativeEvt.timeStamp || Date.now(); - - const defaultPrevented = nativeEvt.defaultPrevented != null ? - nativeEvt.defaultPrevented : - nativeEvt.returnValue === false; - this.defaultPrevented = defaultPrevented; - - this.preventDefault = this.preventDefault.bind(this); - this.stopPropagation = this.stopPropagation.bind(this); - this.isDefaultPrevented = () => defaultPrevented; - this.isPropagationStopped = () => false; - } - - // 兼容性方法 - persist() { - } // 阻止默认行为 diff --git a/libs/horizon/src/event/customEvents/CustomKeyboardEvent.ts b/libs/horizon/src/event/customEvents/CustomKeyboardEvent.ts deleted file mode 100644 index 33c6d13c..00000000 --- a/libs/horizon/src/event/customEvents/CustomKeyboardEvent.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * 自定义键盘事件 - */ - -import type {VNode} from '../../renderer/Types'; -import {uniqueCharCode} from '../utils'; -import {CustomBaseEvent} from './CustomBaseEvent'; -import {CHAR_CODE_ENTER} from '../const'; - -const uniqueKeyMap = new Map([ - ['Esc', 'Escape'], - ['Spacebar', ' '], - ['Left', 'ArrowLeft'], - ['Up', 'ArrowUp'], - ['Right', 'ArrowRight'], - ['Down', 'ArrowDown'], - ['Del', 'Delete'], -]); - -const charCodeToKeyMap = new Map([ - [8, 'Backspace'], - [9, 'Tab'], - [13, 'Enter'], - [16, 'Shift'], - [17, 'Control'], - [18, 'Alt'], - [19, 'Pause'], - [27, 'Escape'], - [32, ' '], - [33, 'PageUp'], - [34, 'PageDown'], - [35, 'End'], - [36, 'Home'], - [37, 'ArrowLeft'], - [38, 'ArrowUp'], - [39, 'ArrowRight'], - [40, 'ArrowDown'], - [46, 'Delete'] -]); - -function getKey(event) { - if (event.key) { - return uniqueKeyMap.get(event.key) || event.key; - } - - if (event.type === 'keypress') { - const charCode = uniqueCharCode(event); - return charCode === CHAR_CODE_ENTER ? 'Enter' : String.fromCharCode(charCode); - } - - if (event.type === 'keydown' || event.type === 'keyup') { - return charCodeToKeyMap.get(event.keyCode); - } - - return ''; -} - -export class CustomKeyboardEvent extends CustomBaseEvent { - - key: string; - charCode: number; - keyCode: number; - which: number; - - constructor( - customEvtName: string | null, - nativeEvtName: string, - nativeEvt: { [propName: string]: any }, - vNode: VNode, - target: null | EventTarget - ) { - 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.which = this.charCode || this.keyCode; - } -} diff --git a/libs/horizon/src/event/customEvents/CustomMouseEvent.ts b/libs/horizon/src/event/customEvents/CustomMouseEvent.ts deleted file mode 100644 index 8ed6f746..00000000 --- a/libs/horizon/src/event/customEvents/CustomMouseEvent.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type {VNode} from '../../renderer/Types'; -import {CustomBaseEvent} from './CustomBaseEvent'; - -export class CustomMouseEvent extends CustomBaseEvent { - relatedTarget: EventTarget; - - constructor( - customEvtName: string | null, - nativeEvtName: string, - nativeEvt: { [propName: string]: any }, - vNode: VNode, - target: null | EventTarget - ) { - super(customEvtName, nativeEvtName, nativeEvt, vNode, target); - - let relatedTarget = nativeEvt.relatedTarget; - if (relatedTarget === undefined) { - if (nativeEvt.fromElement === nativeEvt.srcElement) { - relatedTarget = nativeEvt.toElement; - } else { - relatedTarget = nativeEvt.fromElement; - } - } - this.relatedTarget = relatedTarget; - } -} diff --git a/libs/horizon/src/event/customEvents/EventFactory.ts b/libs/horizon/src/event/customEvents/EventFactory.ts index 74c36a2f..59f606ee 100644 --- a/libs/horizon/src/event/customEvents/EventFactory.ts +++ b/libs/horizon/src/event/customEvents/EventFactory.ts @@ -1,42 +1,8 @@ -import {CustomKeyboardEvent} from './CustomKeyboardEvent'; -import {CustomMouseEvent} from './CustomMouseEvent'; import {CustomBaseEvent} from './CustomBaseEvent'; -const CommonEventToCustom = { - keypress: CustomKeyboardEvent, - keydown: CustomKeyboardEvent, - keyup: CustomKeyboardEvent, - click: CustomMouseEvent, - dblclick: CustomMouseEvent, - mousedown: CustomMouseEvent, - mousemove: CustomMouseEvent, - mouseup: CustomMouseEvent, - mouseout: CustomMouseEvent, - mouseover: CustomMouseEvent, - contextmenu: CustomMouseEvent, - pointercancel: CustomMouseEvent, - pointerdown: CustomMouseEvent, - pointermove: CustomMouseEvent, - pointerout: CustomMouseEvent, - pointerover: CustomMouseEvent, - pointerup: CustomMouseEvent, -} - // 创建普通自定义事件对象实例,和原生事件对应 -export function createCommonCustomEvent(customEventName, nativeEvtName, nativeEvent, vNode, currentTarget) { - const EventConstructor = CommonEventToCustom[nativeEvtName] || CustomBaseEvent; - return new EventConstructor( - customEventName, - nativeEvtName, - nativeEvent, - vNode, - currentTarget, - ); -} - -// 创建模拟事件实例对象,需要handler特殊处理 -export function createHandlerCustomEvent(customEventName, nativeEvtName, nativeEvent, vNode, currentTarget) { - return new CustomMouseEvent( +export function createCustomEvent(customEventName, nativeEvtName, nativeEvent, vNode, currentTarget) { + return new CustomBaseEvent( customEventName, nativeEvtName, nativeEvent, diff --git a/libs/horizon/src/event/simulatedEvtHandler/BeforeInputEventHandler.ts b/libs/horizon/src/event/simulatedEvtHandler/BeforeInputEventHandler.ts index ccbe450a..2ab0fc07 100644 --- a/libs/horizon/src/event/simulatedEvtHandler/BeforeInputEventHandler.ts +++ b/libs/horizon/src/event/simulatedEvtHandler/BeforeInputEventHandler.ts @@ -1,9 +1,10 @@ import type {VNode} from '../../renderer/Types'; -import type {AnyNativeEvent, ProcessingListenerList} from '../Types'; +import type {AnyNativeEvent} from '../Types'; import {getListenersFromTree} from '../ListenerGetter'; -import {createHandlerCustomEvent} from '../customEvents/EventFactory'; +import {createCustomEvent} from '../customEvents/EventFactory'; import {CHAR_CODE_SPACE, EVENT_TYPE_ALL} from '../const'; import {CustomBaseEvent} from '../customEvents/CustomBaseEvent'; +import {ListenerUnitList} from '../Types'; const SPACE_CHAR = String.fromCharCode(CHAR_CODE_SPACE); function getInputCharsByNative( @@ -28,14 +29,14 @@ export function getListeners( nativeEvent: AnyNativeEvent, vNode: null | VNode, target: null | EventTarget, -): ProcessingListenerList { +): ListenerUnitList { const chars = getInputCharsByNative(nativeEvtName, nativeEvent); // 无字符将要输入,无需处理 if (!chars) { return []; } - const event: CustomBaseEvent = createHandlerCustomEvent( + const event: CustomBaseEvent = createCustomEvent( 'onBeforeInput', 'beforeinput', nativeEvent, diff --git a/libs/horizon/src/event/simulatedEvtHandler/ChangeEventHandler.ts b/libs/horizon/src/event/simulatedEvtHandler/ChangeEventHandler.ts index 781ea607..9c2040c2 100644 --- a/libs/horizon/src/event/simulatedEvtHandler/ChangeEventHandler.ts +++ b/libs/horizon/src/event/simulatedEvtHandler/ChangeEventHandler.ts @@ -1,10 +1,10 @@ -import {createHandlerCustomEvent} from '../customEvents/EventFactory'; +import {createCustomEvent} from '../customEvents/EventFactory'; import {getDom} from '../../dom/DOMInternalKeys'; import {isInputValueChanged} from '../../dom/valueHandler/ValueChangeHandler'; import {addValueUpdateList} from '../ControlledValueUpdater'; import {isTextInputElement} from '../utils'; import {EVENT_TYPE_ALL} from '../const'; -import {AnyNativeEvent, ProcessingListenerList} from '../Types'; +import {AnyNativeEvent, ListenerUnitList} from '../Types'; import { getListenersFromTree, } from '../ListenerGetter'; @@ -39,7 +39,7 @@ export function getListeners( nativeEvt: AnyNativeEvent, vNode: null | VNode, target: null | EventTarget, -): ProcessingListenerList { +): ListenerUnitList { if (!vNode) { return []; } @@ -48,7 +48,7 @@ export function getListeners( // 判断是否需要触发change事件 if (shouldTriggerChangeEvent(targetDom, nativeEvtName)) { addValueUpdateList(target); - const event = createHandlerCustomEvent( + const event = createCustomEvent( 'onChange', 'change', nativeEvt, diff --git a/libs/horizon/src/event/simulatedEvtHandler/CompositionEventHandler.ts b/libs/horizon/src/event/simulatedEvtHandler/CompositionEventHandler.ts index a3914893..be3a38f1 100644 --- a/libs/horizon/src/event/simulatedEvtHandler/CompositionEventHandler.ts +++ b/libs/horizon/src/event/simulatedEvtHandler/CompositionEventHandler.ts @@ -1,8 +1,9 @@ import type {VNode} from '../../renderer/Types'; -import type {AnyNativeEvent, ProcessingListenerList} from '../Types'; +import type {AnyNativeEvent} from '../Types'; import {getListenersFromTree} from '../ListenerGetter'; -import {createHandlerCustomEvent} from '../customEvents/EventFactory'; +import {createCustomEvent} from '../customEvents/EventFactory'; import {EVENT_TYPE_ALL} from '../const'; +import {ListenerUnitList} from '../Types'; const compositionEventObj = { compositionstart: 'onCompositionStart', @@ -16,10 +17,10 @@ export function getListeners( nativeEvt: AnyNativeEvent, vNode: null | VNode, target: null | EventTarget, -): ProcessingListenerList { +): ListenerUnitList { const evtType = compositionEventObj[evtName]; - const event = createHandlerCustomEvent( + const event = createCustomEvent( evtType, evtName, nativeEvt, diff --git a/libs/horizon/src/event/simulatedEvtHandler/SelectionEventHandler.ts b/libs/horizon/src/event/simulatedEvtHandler/SelectionEventHandler.ts index 87724137..c5a807c4 100644 --- a/libs/horizon/src/event/simulatedEvtHandler/SelectionEventHandler.ts +++ b/libs/horizon/src/event/simulatedEvtHandler/SelectionEventHandler.ts @@ -1,13 +1,14 @@ -import {createHandlerCustomEvent} from '../customEvents/EventFactory'; +import {createCustomEvent} from '../customEvents/EventFactory'; import {shallowCompare} from '../../renderer/utils/compare'; import {getFocusedDom} from '../../dom/utils/Common'; import {getDom} from '../../dom/DOMInternalKeys'; import {isDocument} from '../../dom/utils/Common'; import {isTextInputElement} from '../utils'; -import type {AnyNativeEvent, ProcessingListenerList} from '../Types'; +import type {AnyNativeEvent} from '../Types'; import {getListenersFromTree} from '../ListenerGetter'; import type {VNode} from '../../renderer/Types'; import {EVENT_TYPE_ALL} from '../const'; +import {ListenerUnitList} from '../Types'; const horizonEventName = 'onSelect' @@ -53,7 +54,7 @@ function getSelectEvent(nativeEvent, target) { if (!shallowCompare(lastSelection, currentSelection)) { lastSelection = currentSelection; - const event = createHandlerCustomEvent( + const event = createCustomEvent( horizonEventName, 'select', nativeEvent, @@ -83,9 +84,9 @@ export function getListeners( nativeEvt: AnyNativeEvent, vNode: null | VNode, target: null | EventTarget, -): ProcessingListenerList { +): ListenerUnitList { const targetNode = vNode ? getDom(vNode) : window; - let eventUnitList: ProcessingListenerList = []; + let eventUnitList: ListenerUnitList = []; switch (name) { case 'focusin': initTargetCache(targetNode, vNode); diff --git a/libs/horizon/src/event/utils.ts b/libs/horizon/src/event/utils.ts index a23e52da..51f022f1 100644 --- a/libs/horizon/src/event/utils.ts +++ b/libs/horizon/src/event/utils.ts @@ -1,35 +1,3 @@ -import {isText} from '../dom/utils/Common'; -import { CHAR_CODE_ENTER, CHAR_CODE_SPACE } from './const'; - -export function uniqueCharCode(nativeEvent): number { - let charCode = nativeEvent.charCode; - - // 火狐浏览器没有设置enter键的charCode,用keyCode - if (charCode === 0 && nativeEvent.keyCode === CHAR_CODE_ENTER) { - charCode = CHAR_CODE_ENTER; - } - - // 当ctrl按下时10表示enter键按下 - if (charCode === 10) { - charCode = CHAR_CODE_ENTER; - } - - // 忽略非打印的Enter键 - if (charCode >= CHAR_CODE_SPACE || charCode === CHAR_CODE_ENTER) { - return charCode; - } - - return 0; -} - -// 获取事件的target对象 -export function getEventTarget(nativeEvent) { - const target = nativeEvent.target || nativeEvent.srcElement || window; - if (isText(target)) { - return target.parentNode; - } - return target; -} // 支持的输入框类型 const supportedInputTypes = ['color', 'date', 'datetime', 'datetime-local', 'email', 'month', @@ -46,11 +14,9 @@ export function isTextInputElement(dom?: HTMLElement): boolean { // 例:dragEnd -> onDragEnd -export function getCustomEventNameWithOn(name) { +export function addOnPrefix(name) { if (!name) { return ''; } - const capitalizedEvent = name[0].toUpperCase() + name.slice(1); - const horizonEventName = 'on' + capitalizedEvent; - return horizonEventName; + return 'on' + name[0].toUpperCase() + name.slice(1); } diff --git a/libs/horizon/src/external/JSXElement.ts b/libs/horizon/src/external/JSXElement.ts index 9a06a957..2a138387 100644 --- a/libs/horizon/src/external/JSXElement.ts +++ b/libs/horizon/src/external/JSXElement.ts @@ -37,7 +37,7 @@ function mergeDefault(sourceObj, defaultObj) { }); } -function buildElement(isClone, type, setting, ...children) { +function buildElement(isClone, type, setting, children) { // setting中的值优先级最高,clone情况下从 type 中取值,创建情况下直接赋值为 null const key = (setting && setting.key !== undefined) ? String(setting.key) : (isClone ? type.key : null); const ref = (setting && setting.ref !== undefined) ? setting.ref : (isClone ? type.ref : null); @@ -45,11 +45,14 @@ function buildElement(isClone, type, setting, ...children) { let vNode = isClone ? type.belongClassVNode : getProcessingClassVNode(); if (setting != null) { - Object.keys(setting).forEach(k => { + const keys = Object.keys(setting); + const keyLength = keys.length; + for(let i = 0; i < keyLength; i++) { + const k = keys[i]; if (isValidKey(k)) { props[k] = setting[k]; } - }); + } if (setting.ref !== undefined && isClone) { vNode = getProcessingClassVNode(); } @@ -69,11 +72,11 @@ function buildElement(isClone, type, setting, ...children) { // 创建Element结构体,供JSX编译时调用 export function createElement(type, setting, ...children) { - return buildElement(false, type, setting, ...children); + return buildElement(false, type, setting, children); } export function cloneElement(element, setting, ...children) { - return buildElement(true, element, setting, ...children); + return buildElement(true, element, setting, children); } // 检测结构体是否为合法的Element diff --git a/libs/horizon/src/renderer/ErrorHandler.ts b/libs/horizon/src/renderer/ErrorHandler.ts index c6946c69..3c3305c1 100644 --- a/libs/horizon/src/renderer/ErrorHandler.ts +++ b/libs/horizon/src/renderer/ErrorHandler.ts @@ -8,10 +8,11 @@ import type {Update} from './UpdateHandler'; import {ClassComponent, TreeRoot} from './vnode/VNodeTags'; import {FlagUtils, Interrupted} from './vnode/VNodeFlags'; import {newUpdate, UpdateState, pushUpdate} from './UpdateHandler'; -import {launchUpdateFromVNode, setBuildResultError, tryRenderRoot} from './TreeBuilder'; +import {launchUpdateFromVNode, tryRenderFromRoot} from './TreeBuilder'; import {setRootThrowError} from './submit/Submit'; import {handleSuspenseChildThrowError} from './render/SuspenseComponent'; import {updateShouldUpdateOfTree} from './vnode/VNodeShouldUpdate'; +import {BuildErrored, setBuildResult} from './GlobalVar'; function consoleError(error: any): void { if (isDev) { @@ -82,7 +83,7 @@ export function handleRenderThrowError( } // 抛出错误无法作为suspense内容处理(或无suspense来处理),这次当成真的错误来处理 - setBuildResultError(); + setBuildResult(BuildErrored); // 向上遍历寻找ClassComponent组件(同时也是Error Boundaries组件) 或者 TreeRoot let vNode = sourceVNode.parent; @@ -133,7 +134,7 @@ function triggerUpdate(vNode, state) { const root = updateShouldUpdateOfTree(vNode); if (root !== null) { - tryRenderRoot(root); + tryRenderFromRoot(root); } } diff --git a/libs/horizon/src/renderer/ExecuteMode.ts b/libs/horizon/src/renderer/ExecuteMode.ts index 68c6f2c1..490c8595 100644 --- a/libs/horizon/src/renderer/ExecuteMode.ts +++ b/libs/horizon/src/renderer/ExecuteMode.ts @@ -2,14 +2,16 @@ export const ByAsync = 'BY_ASYNC'; export const BySync = 'BY_SYNC'; export const InRender = 'IN_RENDER'; +export const InEvent = 'IN_EVENT'; -type RenderMode = typeof ByAsync | typeof BySync | typeof InRender; +type RenderMode = typeof ByAsync | typeof BySync | typeof InRender | typeof InEvent; // 当前执行模式标记 let executeMode = { [ByAsync]: false, [BySync]: false, [InRender]: false, + [InEvent]: false, }; export function changeMode(mode: RenderMode, state = true) { @@ -21,7 +23,7 @@ export function checkMode(mode: RenderMode) { } export function isExecuting() { - return executeMode[ByAsync] || executeMode[BySync] || executeMode[InRender]; + return executeMode[ByAsync] || executeMode[BySync] || executeMode[InRender] || executeMode[InEvent]; } export function copyExecuteMode() { diff --git a/libs/horizon/src/renderer/GlobalVar.ts b/libs/horizon/src/renderer/GlobalVar.ts index e7ea335c..44911e69 100644 --- a/libs/horizon/src/renderer/GlobalVar.ts +++ b/libs/horizon/src/renderer/GlobalVar.ts @@ -1,6 +1,6 @@ import type {VNode} from './Types'; -// 当前处理的classVNode,用于inst.refs用法中的 +// 当前处理的classVNode,用于设置inst.refs let processingClassVNode: VNode | null = null; export function getProcessingClassVNode(): VNode | null { return processingClassVNode; diff --git a/libs/horizon/src/renderer/Renderer.ts b/libs/horizon/src/renderer/Renderer.ts index d5cf89da..8928805a 100644 --- a/libs/horizon/src/renderer/Renderer.ts +++ b/libs/horizon/src/renderer/Renderer.ts @@ -9,9 +9,9 @@ import { } from './TreeBuilder'; import { runAsyncEffects } from './submit/HookEffectHandler'; import { Callback, newUpdate, pushUpdate } from './UpdateHandler'; -import { getFirstChild } from './vnode/VNodeUtils'; -export { createVNode } from './vnode/VNodeCreator'; + +export { createVNode, createTreeRootVNode } from './vnode/VNodeCreator'; export { createPortal } from './components/CreatePortal'; export { asyncUpdates, diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts index 35caaba6..3c9b729b 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -10,7 +10,7 @@ import { runAsyncEffects } from './submit/HookEffectHandler'; import { handleRenderThrowError } from './ErrorHandler'; import componentRenders from './render'; import { - BuildCompleted, BuildErrored, + BuildCompleted, BuildFatalErrored, BuildInComplete, getBuildResult, getStartVNode, @@ -22,10 +22,11 @@ import { findDomParent, getSiblingVNode } from './vnode/VNodeUtils'; import { ByAsync, BySync, + InRender, + InEvent, changeMode, checkMode, copyExecuteMode, - InRender, isExecuting, setExecuteMode } from './ExecuteMode'; @@ -36,12 +37,11 @@ import { updateShouldUpdateOfTree } from './vnode/VNodeShouldUpdate'; -// 当前运行的vNode节点 -let processing: VNode | null = null; - // 不可恢复错误 let unrecoverableErrorDuringBuild: any = null; +// 当前运行的vNode节点 +let processing: VNode | null = null; export function setProcessing(vNode: VNode | null) { processing = vNode; } @@ -64,7 +64,7 @@ function collectDirtyNodes(vNode: VNode, parent: VNode): void { } } -// ============================== 向上递归 ============================== +// ============================== 向上冒泡 ============================== // 尝试完成当前工作单元,然后移动到下一个兄弟工作单元。如果没有更多的同级,请返回父vNode。 function bubbleVNode(vNode: VNode): void { @@ -201,18 +201,18 @@ function buildVNodeTree(treeRoot: VNode) { changeMode(InRender, true); // 计算出开始节点 - const startUpdateVNode = calcStartUpdateVNode(treeRoot); + const startVNode = calcStartUpdateVNode(treeRoot); // 缓存起来 - setStartVNode(startUpdateVNode); + setStartVNode(startVNode); // 清空toUpdateNodes treeRoot.toUpdateNodes.clear(); - if (startUpdateVNode.tag !== TreeRoot) { // 不是根节点 + if (startVNode.tag !== TreeRoot) { // 不是根节点 // 设置namespace,用于createElement - const parentObj = findDomParent(startUpdateVNode); + const parentObj = findDomParent(startVNode); - // 当在componentWillUnmount中调用setState,parent可能是null,因为startUpdateVNode会被clear + // 当在componentWillUnmount中调用setState,parent可能是null,因为startVNode会被clear if (parentObj !== null) { const domParent = parentObj.parent; resetNamespaceCtx(domParent); @@ -220,20 +220,20 @@ function buildVNodeTree(treeRoot: VNode) { } // 恢复父节点的context - recoverParentsContextCtx(startUpdateVNode); + recoverParentsContextCtx(startVNode); } // 重置环境变量,为重新进行深度遍历做准备 - resetProcessingVariables(startUpdateVNode); + resetProcessingVariables(startVNode); - do { + while (processing !== null) { try { while (processing !== null) { // 捕获创建 vNodes const next = captureVNode(processing); if (next === null) { - // 如果没有产生新的,那么就完成当前节点,向上遍历 + // 如果没有子节点,那么就完成当前节点,开始冒泡 bubbleVNode(processing); } else { processing = next; @@ -241,14 +241,10 @@ function buildVNodeTree(treeRoot: VNode) { } setProcessingClassVNode(null); - - break; } catch (thrownValue) { handleError(treeRoot, thrownValue); } - } while (true); - - processing = null; + } setExecuteMode(preMode); } @@ -272,7 +268,7 @@ function renderFromRoot(treeRoot) { } // 尝试去渲染,已有任务就跳出 -export function tryRenderRoot(treeRoot: VNode) { +export function tryRenderFromRoot(treeRoot: VNode) { if (treeRoot.shouldUpdate && treeRoot.task === null) { // 任务放进queue,但是调度开始还是异步的 treeRoot.task = pushRenderCallback( @@ -304,7 +300,7 @@ export function launchUpdateFromVNode(vNode: VNode) { // 不能改成下面的异步,否则会有时序问题,因为业务可能会依赖这个渲染的完成。 renderFromRoot(treeRoot); } else { - tryRenderRoot(treeRoot); + tryRenderFromRoot(treeRoot); if (!isExecuting()) { // 同步执行 @@ -313,12 +309,6 @@ export function launchUpdateFromVNode(vNode: VNode) { } } -export function setBuildResultError() { - if (getBuildResult() !== BuildCompleted) { - setBuildResult(BuildErrored); - } -} - // ============================== HorizonDOM使用 ============================== export function runDiscreteUpdates() { if (checkMode(ByAsync) || checkMode(InRender)) { @@ -331,7 +321,7 @@ export function runDiscreteUpdates() { export function asyncUpdates(fn, ...param) { const preMode = copyExecuteMode(); - changeMode(ByAsync, true); + changeMode(InEvent, true); try { return fn(...param); } finally { diff --git a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts index 09624161..797687b5 100644 --- a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts +++ b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts @@ -2,7 +2,7 @@ import type { VNode } from '../Types'; import { FlagUtils } from '../vnode/VNodeFlags'; import { TYPE_COMMON_ELEMENT, TYPE_FRAGMENT, TYPE_PORTAL } from '../../external/JSXElementType'; import { DomText, DomPortal, Fragment, DomComponent } from '../vnode/VNodeTags'; -import {updateVNode, createVNode, createVNodeFromElement, updateVNodePath} from '../vnode/VNodeCreator'; +import {updateVNode, createVNodeFromElement, updateVNodePath, createFragmentVNode, createPortalVNode, createDomTextVNode} from '../vnode/VNodeCreator'; import { isSameType, getIteratorFn, @@ -113,7 +113,7 @@ function getNewNode(parentNode: VNode, newChild: any, oldNode: VNode | null) { switch (newNodeType) { case DiffCategory.TEXT_NODE: { if (oldNode === null || oldNode.tag !== DomText) { - resultNode = createVNode(DomText, String(newChild)); + resultNode = createDomTextVNode(String(newChild)); } else { resultNode = updateVNode(oldNode, String(newChild)); } @@ -121,7 +121,7 @@ function getNewNode(parentNode: VNode, newChild: any, oldNode: VNode | null) { } case DiffCategory.ARR_NODE: { if (oldNode === null || oldNode.tag !== Fragment) { - resultNode = createVNode(Fragment, null, newChild); + resultNode = createFragmentVNode(null, newChild); } else { resultNode = updateVNode(oldNode, newChild); } @@ -132,7 +132,7 @@ function getNewNode(parentNode: VNode, newChild: any, oldNode: VNode | null) { if (newChild.type === TYPE_FRAGMENT) { if (oldNode === null || oldNode.tag !== Fragment) { const key = oldNode !== null ? oldNode.key : newChild.key; - resultNode = createVNode(Fragment, key, newChild.props.children); + resultNode = createFragmentVNode(key, newChild.props.children); } else { resultNode = updateVNode(oldNode, newChild); } @@ -151,7 +151,7 @@ function getNewNode(parentNode: VNode, newChild: any, oldNode: VNode | null) { break; } else if (newChild.vtype === TYPE_PORTAL) { if (oldNode === null || oldNode.tag !== DomPortal || oldNode.outerDom !== newChild.outerDom) { - resultNode = createVNode(DomPortal, newChild); + resultNode = createPortalVNode(newChild); } else { resultNode = updateVNode(oldNode, newChild.children || []); } @@ -504,7 +504,7 @@ function diffStringNodeHandler( deleteVNodes(parentNode, firstChildVNode.next); newTextNode.next = null; } else { - newTextNode = createVNode(DomText, String(newChild)); + newTextNode = createDomTextVNode(String(newChild)); deleteVNodes(parentNode, firstChildVNode); } @@ -562,7 +562,7 @@ function diffObjectNodeHandler( if (resultNode === null) { // 新建 if (newChild.type === TYPE_FRAGMENT) { - resultNode = createVNode(Fragment, newChild.key, newChild.props.children); + resultNode = createFragmentVNode(newChild.key, newChild.props.children); } else { resultNode = createVNodeFromElement(newChild); resultNode.ref = newChild.ref; @@ -580,7 +580,7 @@ function diffObjectNodeHandler( } if (resultNode === null) { // 新建 - resultNode = createVNode(DomPortal, newChild); + resultNode = createPortalVNode(newChild); } } diff --git a/libs/horizon/src/renderer/render/MemoComponent.ts b/libs/horizon/src/renderer/render/MemoComponent.ts index d4a10d22..a0cd75f3 100644 --- a/libs/horizon/src/renderer/render/MemoComponent.ts +++ b/libs/horizon/src/renderer/render/MemoComponent.ts @@ -1,14 +1,13 @@ import type {VNode} from '../Types'; import {mergeDefaultProps} from './LazyComponent'; -import {updateVNode, createVNode, onlyUpdateChildVNodes, updateVNodePath} from '../vnode/VNodeCreator'; +import {updateVNode, onlyUpdateChildVNodes, updateVNodePath, createFragmentVNode, createUndeterminedVNode} from '../vnode/VNodeCreator'; import {shallowCompare} from '../utils/compare'; import { TYPE_FRAGMENT, TYPE_PROFILER, TYPE_STRICT_MODE, } from '../../external/JSXElementType'; -import {Fragment} from '../vnode/VNodeTags'; export function bubbleRender() {} @@ -24,9 +23,9 @@ export function captureMemoComponent( let newChild = null; const type = Component.type; if (type === TYPE_STRICT_MODE || type === TYPE_FRAGMENT || type === TYPE_PROFILER) { - newChild = createVNode(Fragment, null, newProps.children); + newChild = createFragmentVNode(null, newProps.children); } else { - newChild = createVNode('props', type, null, newProps, processing); + newChild = createUndeterminedVNode(type, null, newProps); } newChild.parent = processing; newChild.ref = processing.ref; diff --git a/libs/horizon/src/renderer/render/SuspenseComponent.ts b/libs/horizon/src/renderer/render/SuspenseComponent.ts index b84b1582..c2d6773b 100644 --- a/libs/horizon/src/renderer/render/SuspenseComponent.ts +++ b/libs/horizon/src/renderer/render/SuspenseComponent.ts @@ -1,15 +1,14 @@ import type {VNode, PromiseType} from '../Types'; import {FlagUtils, Interrupted} from '../vnode/VNodeFlags'; -import {createVNode, onlyUpdateChildVNodes, updateVNode, updateVNodePath} from '../vnode/VNodeCreator'; +import {onlyUpdateChildVNodes, updateVNode, updateVNodePath, createFragmentVNode} from '../vnode/VNodeCreator'; import { ClassComponent, IncompleteClassComponent, SuspenseComponent, - Fragment, } from '../vnode/VNodeTags'; import {pushForceUpdate} from '../UpdateHandler'; -import {launchUpdateFromVNode, tryRenderRoot} from '../TreeBuilder'; +import {launchUpdateFromVNode, tryRenderFromRoot} from '../TreeBuilder'; import {updateShouldUpdateOfTree} from '../vnode/VNodeShouldUpdate'; import {getContextChangeCtx} from '../ContextSaver'; @@ -31,12 +30,12 @@ function createFallback(processing: VNode, fallbackChildren) { if (oldFallbackFragment !== null) { fallbackFragment = updateVNode(oldFallbackFragment, fallbackChildren); } else { - fallbackFragment = createVNode(Fragment, null, fallbackChildren); + fallbackFragment = createFragmentVNode(null, fallbackChildren); FlagUtils.markAddition(fallbackFragment); } } else { // 创建 - fallbackFragment = createVNode(Fragment, null, fallbackChildren); + fallbackFragment = createFragmentVNode(null, fallbackChildren); } processing.child = childFragment; @@ -72,7 +71,7 @@ function createSuspenseChildren(processing: VNode, newChildren) { // SuspenseComponent 中使用 processing.suspenseChildStatus = SuspenseChildStatus.ShowChild; } else { - childFragment = createVNode(Fragment, null, newChildren); + childFragment = createFragmentVNode(null, newChildren); } childFragment.parent = processing; @@ -210,7 +209,7 @@ function resolvePromise(suspenseVNode: VNode, promise: PromiseType) { suspenseVNode.promiseResolve = true; const root = updateShouldUpdateOfTree(suspenseVNode); if (root !== null) { - tryRenderRoot(root); + tryRenderFromRoot(root); } } diff --git a/libs/horizon/src/renderer/submit/HookEffectHandler.ts b/libs/horizon/src/renderer/submit/HookEffectHandler.ts index 36344ff3..b6706389 100644 --- a/libs/horizon/src/renderer/submit/HookEffectHandler.ts +++ b/libs/horizon/src/renderer/submit/HookEffectHandler.ts @@ -7,22 +7,16 @@ import type { Effect as HookEffect, EffectList, } from '../hooks/HookType'; -import { - callRenderQueueImmediate, -} from '../taskExecutor/RenderQueue'; import {runAsync} from '../taskExecutor/TaskExecutor'; import { copyExecuteMode, InRender, setExecuteMode,changeMode } from '../ExecuteMode'; -import {handleSubmitError} from '../ErrorHandler'; -import {clearDirtyNodes} from './Submit'; import {EffectConstant} from '../hooks/EffectConstant'; let hookEffects: Array = []; let hookRemoveEffects: Array = []; // 是否正在异步调度effects let isScheduling: boolean = false; -let hookEffectRoot: VNode | null = null; export function setSchedulingEffects(value) { isScheduling = value; @@ -31,13 +25,6 @@ export function isSchedulingEffects() { return isScheduling; } -export function setHookEffectRoot(root: VNode | null) { - hookEffectRoot = root; -} -export function getHookEffectRoot() { - return hookEffectRoot; -} - export function callUseEffects(vNode: VNode) { const effectList: EffectList = vNode.effectList; @@ -47,8 +34,8 @@ export function callUseEffects(vNode: VNode) { (effectConstant & EffectConstant.Effect) !== EffectConstant.NoEffect && (effectConstant & EffectConstant.DepsChange) !== EffectConstant.NoEffect ) { - hookEffects.push({effect, vNode}); - hookRemoveEffects.push({effect, vNode}); + hookEffects.push(effect); + hookRemoveEffects.push(effect); // 异步调用 if (!isScheduling) { @@ -60,20 +47,13 @@ export function callUseEffects(vNode: VNode) { } export function runAsyncEffects() { - if (hookEffectRoot === null) { - return false; - } - - const root = hookEffectRoot; - hookEffectRoot = null; - const preMode = copyExecuteMode(); changeMode(InRender, true); // 调用effect destroy const removeEffects = hookRemoveEffects; hookRemoveEffects = []; - removeEffects.forEach(({effect, vNode}) => { + removeEffects.forEach((effect) => { const destroy = effect.removeEffect; effect.removeEffect = undefined; @@ -81,7 +61,7 @@ export function runAsyncEffects() { try { destroy(); } catch (error) { - handleSubmitError(vNode, error); + // 不处理副作用阶段抛出的异常 } } }); @@ -89,24 +69,17 @@ export function runAsyncEffects() { // 调用effect create const createEffects = hookEffects; hookEffects = []; - createEffects.forEach(({effect, vNode}) => { + createEffects.forEach((effect) => { try { const create = effect.effect; effect.removeEffect = create(); } catch (error) { - handleSubmitError(vNode, error); + // 不处理副作用阶段抛出的异常 } }); - // 清理dirtyNodes - clearDirtyNodes(root.dirtyNodes); - setExecuteMode(preMode); - - callRenderQueueImmediate(); - - return true; } // 在销毁vNode的时候调用remove @@ -118,7 +91,7 @@ export function callEffectRemove(vNode: VNode) { if (removeEffect !== undefined) { if ((effectConstant & EffectConstant.Effect) !== EffectConstant.NoEffect) { // 如果是useEffect,就异步调用 - hookRemoveEffects.push({effect, vNode}); + hookRemoveEffects.push(effect); if (!isScheduling) { isScheduling = true; @@ -136,10 +109,10 @@ export function callUseLayoutEffectRemove(vNode: VNode) { const effectList: EffectList = vNode.effectList; const layoutLabel = EffectConstant.LayoutEffect | EffectConstant.DepsChange; - effectList.forEach(item => { - if ((item.effectConstant & layoutLabel) === layoutLabel) { - const remove = item.removeEffect; - item.removeEffect = undefined; + effectList.forEach(effect => { + if ((effect.effectConstant & layoutLabel) === layoutLabel) { + const remove = effect.removeEffect; + effect.removeEffect = undefined; if (typeof remove === 'function') { remove(); } @@ -152,10 +125,10 @@ export function callUseLayoutEffectCreate(vNode: VNode) { const effectList: EffectList = vNode.effectList; const layoutLabel = EffectConstant.LayoutEffect | EffectConstant.DepsChange; - effectList.forEach(item => { - if ((item.effectConstant & layoutLabel) === layoutLabel) { - const create = item.effect; - item.removeEffect = create(); + effectList.forEach(effect => { + if ((effect.effectConstant & layoutLabel) === layoutLabel) { + const create = effect.effect; + effect.removeEffect = create(); } }); } diff --git a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts index 9dafedd5..a6ae1835 100644 --- a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts +++ b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts @@ -27,7 +27,6 @@ import { removeChildDom, hideDom, unHideDom, - clearContainer, } from '../../dom/DOMOperator'; import { callEffectRemove, @@ -56,28 +55,17 @@ function callComponentWillUnmount(vNode: VNode, instance: any) { function callBeforeSubmitLifeCycles( vNode: VNode, ): void { - switch (vNode.tag) { - case ClassComponent: { // 调用instance.getSnapshotBeforeUpdate - if (!vNode.isCreated) { - const prevProps = vNode.isLazyComponent - ? mergeDefaultProps(vNode.type, vNode.oldProps) - : vNode.oldProps; - const prevState = vNode.oldState; - const instance = vNode.realNode; + if (vNode.tag === ClassComponent && !vNode.isCreated) { // 调用instance.getSnapshotBeforeUpdate + const prevProps = vNode.isLazyComponent + ? mergeDefaultProps(vNode.type, vNode.oldProps) + : vNode.oldProps; + const prevState = vNode.oldState; + const instance = vNode.realNode; - const snapshot = instance.getSnapshotBeforeUpdate(prevProps, prevState); + const snapshot = instance.getSnapshotBeforeUpdate(prevProps, prevState); - // __snapshotResult会在调用componentDidUpdate的时候作为第三个参数 - instance.__snapshotResult = snapshot; - } - return; - } - case TreeRoot: { - const root = vNode.realNode; - clearContainer(root.outerDom); - } - - // No Default + // __snapshotResult会在调用componentDidUpdate的时候作为第三个参数 + instance.__snapshotResult = snapshot; } } @@ -138,7 +126,6 @@ function callAfterSubmitLifeCycles( if (vNode.isCreated && vNode.flags.Update) { // button、input、select、textarea、如果有 autoFocus 属性需要focus if (shouldAutoFocus(vNode.type, vNode.props)) { - // button、input、select、textarea、如果有 autoFocus 属性需要focus vNode.realNode.focus(); } } @@ -333,11 +320,12 @@ function submitClear(vNode: VNode): void { clearVNode(clearChild); clearChild = clearChild.next as VNode; } - + // 在所有子项都卸载后,删除dom树中的节点 removeChildDom(currentParent, vNode.realNode); currentParent.append(cloneDom); vNode.realNode = cloneDom; + attachRef(vNode); FlagUtils.removeFlag(vNode, Clear); vNode.clearChild = null; } diff --git a/libs/horizon/src/renderer/submit/Submit.ts b/libs/horizon/src/renderer/submit/Submit.ts index 446569bb..e222564f 100644 --- a/libs/horizon/src/renderer/submit/Submit.ts +++ b/libs/horizon/src/renderer/submit/Submit.ts @@ -11,18 +11,16 @@ import { callBeforeSubmitLifeCycles, submitDeletion, submitAddition, submitResetTextContent, submitUpdate, detachRef, submitClear, } from './LifeCycleHandler'; -import {tryRenderRoot, setProcessing} from '../TreeBuilder'; +import {tryRenderFromRoot} from '../TreeBuilder'; import { - BySync, InRender, copyExecuteMode, setExecuteMode, - checkMode, changeMode, } from '../ExecuteMode'; import { isSchedulingEffects, - setSchedulingEffects, setHookEffectRoot, + setSchedulingEffects, } from './HookEffectHandler'; import {getStartVNode} from '../GlobalVar'; @@ -67,18 +65,13 @@ export function submitToRender(treeRoot) { if (isSchedulingEffects()) { setSchedulingEffects(false); - - // 记录root,说明这个root有副作用要执行 - setHookEffectRoot(treeRoot); - } else { - clearDirtyNodes(dirtyNodes); } // 统计root同步重渲染的次数,如果太多可能是无线循环 countLoopingUpdate(treeRoot); // 在退出`submit` 之前始终调用此函数,以确保任何已计划在此根上执行的update被执行。 - tryRenderRoot(treeRoot); + tryRenderFromRoot(treeRoot); if (rootThrowError) { const error = rootThrowError; @@ -86,11 +79,6 @@ export function submitToRender(treeRoot) { throw error; } - // 非批量:即同步执行的,没有必要去执行RenderQueue,RenderQueue放的是异步的 - if (!checkMode(BySync)) { // 非批量 - callRenderQueueImmediate(); - } - return null; } @@ -101,7 +89,6 @@ function beforeSubmit(dirtyNodes: Array) { callBeforeSubmitLifeCycles(node); } } catch (error) { - throwIfTrue(node === null, 'Should be working on an effect.'); handleSubmitError(node, error); } }); @@ -143,7 +130,6 @@ function submit(dirtyNodes: Array) { } } } catch (error) { - throwIfTrue(node === null, 'Should be working on an effect.'); handleSubmitError(node, error); } }); @@ -160,7 +146,6 @@ function afterSubmit(dirtyNodes: Array) { attachRef(node); } } catch (error) { - throwIfTrue(node === null, 'Should be working on an effect.'); handleSubmitError(node, error); } }); @@ -197,13 +182,3 @@ export function checkLoopingUpdateLimit() { ); } } - -// 清理dirtyNodes -export function clearDirtyNodes(dirtyNodes) { - dirtyNodes.forEach(node => { - if (node.flags.Deletion) { - node.realNode = null; - node.next = null; - } - }); -} diff --git a/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts b/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts index 31760489..de3203d8 100644 --- a/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts +++ b/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts @@ -28,22 +28,14 @@ function callRenderQueue() { // 防止重入 isCallingRenderQueue = true; - let i = 0; try { - for (; i < renderQueue.length; i++) { - let callback = renderQueue[i]; + let callback; + while (callback = renderQueue.shift()) { callback(); } + renderQueue = null; } catch (error) { - // 如果有异常抛出,请将剩余的回调留在队列中 - if (renderQueue !== null) { - renderQueue = renderQueue.slice(i + 1); - } - - // 在下一个异步中再调用 - runAsync(callRenderQueueImmediate, ImmediatePriority); - throw error; } finally { isCallingRenderQueue = false; diff --git a/libs/horizon/src/renderer/vnode/VNodeCreator.ts b/libs/horizon/src/renderer/vnode/VNodeCreator.ts index 5feb31f4..11abe1b1 100644 --- a/libs/horizon/src/renderer/vnode/VNodeCreator.ts +++ b/libs/horizon/src/renderer/vnode/VNodeCreator.ts @@ -87,59 +87,71 @@ export function updateVNode(vNode: VNode, vNodeProps?: any): VNode { function getVNodeTag(type: any) { let vNodeTag = ClsOrFunComponent; let isLazy = false; + const componentType = typeof type; - if (typeof type === 'function') { + if (componentType === 'function') { if (isClassComponent(type)) { vNodeTag = ClassComponent; } - } else if (typeof type === 'string') { + } else if (componentType === 'string') { vNodeTag = DomComponent; } else if (type === TYPE_SUSPENSE) { vNodeTag = SuspenseComponent; - } else if (typeof type === 'object' && type !== null && typeMap[type.vtype]) { + } else if (componentType === '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}`); + throw Error(`Component type is invalid, got: ${type == null ? type : componentType}`); } return { vNodeTag, isLazy }; } +export function createFragmentVNode(fragmentKey, fragmentProps) { + const vNode = newVirtualNode(Fragment, fragmentKey, fragmentProps); + vNode.shouldUpdate = true; + return vNode; +} + +export function createDomTextVNode(content) { + const vNode = newVirtualNode(DomText, null, content); + vNode.shouldUpdate = true; + return vNode; +} + +export function createPortalVNode(portal) { + const children = portal.children ?? []; + const vNode = newVirtualNode(DomPortal, portal.key, children); + vNode.shouldUpdate = true; + vNode.outerDom = portal.outerDom; + return vNode; +} + +export function createUndeterminedVNode(type, key, props) { + const { vNodeTag, isLazy } = getVNodeTag(type); + + const vNode = newVirtualNode(vNodeTag, key, props); + vNode.type = type; + vNode.shouldUpdate = true; + + // lazy类型的特殊处理 + vNode.isLazyComponent = isLazy; + if (isLazy) { + vNode.lazyType = type; + } + return vNode; +} + +export function createTreeRootVNode(container) { + const vNode = newVirtualNode(TreeRoot, null, null, container); + vNode.path.push(0); + createUpdateArray(vNode); + return vNode; +} + +// TODO: 暂时保留给测试用例使用,后续修改测试用例 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]); @@ -162,9 +174,9 @@ export function createVNodeFromElement(element: JSXElement): VNode { const props = element.props; if (type === TYPE_STRICT_MODE || type === TYPE_FRAGMENT || type === TYPE_PROFILER) { - return createVNode(Fragment, key, props.children); + return createFragmentVNode(key, props.children); } else { - return createVNode('props', type, key, props); + return createUndeterminedVNode(type, key, props); } } diff --git a/libs/horizon/src/renderer/vnode/VNodeFlags.ts b/libs/horizon/src/renderer/vnode/VNodeFlags.ts index f511ac85..48d0331d 100644 --- a/libs/horizon/src/renderer/vnode/VNodeFlags.ts +++ b/libs/horizon/src/renderer/vnode/VNodeFlags.ts @@ -39,14 +39,16 @@ export class FlagUtils { }); } static hasAnyFlag(node: VNode) { // 有标志位 - let keyFlag = false; - FlagArr.forEach(key => { - if (node.flags[key]) { - keyFlag = true; - return; + const flags = node.flags; + const arrLength = FlagArr.length; + let key; + for (let i = 0; i < arrLength; i++) { + key = FlagArr[i]; + if (flags[key]) { + return true; } - }); - return keyFlag; + } + return false; } static setNoFlags(node: VNode) { @@ -91,7 +93,6 @@ export class FlagUtils { static markForceUpdate(node: VNode) { node.flags.ForceUpdate = true; } - static markClear(node: VNode) { node.flags.Clear = true; } diff --git a/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts b/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts index 8dfe8c7e..72afb236 100644 --- a/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts +++ b/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts @@ -56,6 +56,7 @@ export function updateParentsChildShouldUpdate(vNode: VNode) { let isShouldUpdate = vNode.shouldUpdate || vNode.childShouldUpdate; if (isShouldUpdate) { // 开始节点是shouldUpdate或childShouldUpdate + // 更新从当前节点到根节点的childShouldUpdate为true setParentsChildShouldUpdate(node); } else { while (node !== null) { diff --git a/libs/horizon/src/renderer/vnode/VNodeUtils.ts b/libs/horizon/src/renderer/vnode/VNodeUtils.ts index 09ff6f0e..dc38b93e 100644 --- a/libs/horizon/src/renderer/vnode/VNodeUtils.ts +++ b/libs/horizon/src/renderer/vnode/VNodeUtils.ts @@ -81,7 +81,6 @@ export function clearVNode(vNode: VNode) { vNode.next = null; vNode.depContexts = []; vNode.dirtyNodes = []; - vNode.oldProps = null; vNode.state = null; vNode.hooks = []; vNode.suspenseChildStatus = ''; @@ -91,7 +90,9 @@ export function clearVNode(vNode: VNode) { vNode.changeList = null; vNode.effectList = []; vNode.updates = null; + vNode.realNode = null; + vNode.oldProps = null; vNode.oldHooks = []; vNode.oldState = null; vNode.oldRef = null; @@ -260,14 +261,14 @@ export function getExactNode(targetVNode, targetContainer) { 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); + return getExactNode(parentNode, targetContainer); } container = container.parentNode; }