Match-id-fda5629a5eba25fc511063c1cebf7752506caf2d

This commit is contained in:
* 2022-02-14 17:40:20 +08:00 committed by *
commit 5b6f0a11d0
42 changed files with 369 additions and 729 deletions

View File

@ -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;
// 根节点挂接全量事件

View File

@ -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<string> {
let elementListeners = dom[internalKeys.events];
if (!elementListeners) {
elementListeners = new Set();
dom[internalKeys.events] = elementListeners;
}
return elementListeners;
}
export function getEventToListenerMap(target: EventTarget): Map<string, EventListener> {
let eventsMap = target[internalKeys.nonDelegatedEvents];
export function getNonDelegatedListenerMap(dom: Element | Text): Map<string, EventListener> {
let eventsMap = dom[internalKeys.nonDelegatedEvents];
if (!eventsMap) {
eventsMap = new Map();
target[internalKeys.nonDelegatedEvents] = eventsMap;
dom[internalKeys.nonDelegatedEvents] = eventsMap;
}
return eventsMap;
}

View File

@ -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);
}

View File

@ -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)) {
// 事件监听属性处理

View File

@ -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;

View File

@ -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);
}

View File

@ -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(<HTMLInputElement>dom, properties);

View File

@ -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<any> | null = null;
// 受控组件值重新赋值
function updateValue(target: Element) {
const props = getVNodeProps(target);
if (props) {
const type = getDomTag(target);
resetValue(target, type, props);
resetValue(target, props);
}
}

View File

@ -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);
}

View File

@ -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);
});

View File

@ -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;
}
}

View File

@ -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()) {

View File

@ -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;
}

View File

@ -12,5 +12,3 @@ export type ListenerUnit = {
};
export type ListenerUnitList = Array<ListenerUnit>;
export type ProcessingListenerList = Array<ListenerUnitList>;

View File

@ -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);
}

View File

@ -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']],

View File

@ -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() {
}
// 阻止默认行为

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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);

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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() {

View File

@ -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;

View File

@ -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,

View File

@ -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中调用setStateparent可能是null因为startUpdateVNode会被clear
// 当在componentWillUnmount中调用setStateparent可能是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 {

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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<any>) {
suspenseVNode.promiseResolve = true;
const root = updateShouldUpdateOfTree(suspenseVNode);
if (root !== null) {
tryRenderRoot(root);
tryRenderFromRoot(root);
}
}

View File

@ -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<HookEffect | VNode> = [];
let hookRemoveEffects: Array<HookEffect | VNode> = [];
// 是否正在异步调度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();
}
});
}

View File

@ -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;
}

View File

@ -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;
}
// 非批量即同步执行的没有必要去执行RenderQueueRenderQueue放的是异步的
if (!checkMode(BySync)) { // 非批量
callRenderQueueImmediate();
}
return null;
}
@ -101,7 +89,6 @@ function beforeSubmit(dirtyNodes: Array<VNode>) {
callBeforeSubmitLifeCycles(node);
}
} catch (error) {
throwIfTrue(node === null, 'Should be working on an effect.');
handleSubmitError(node, error);
}
});
@ -143,7 +130,6 @@ function submit(dirtyNodes: Array<VNode>) {
}
}
} catch (error) {
throwIfTrue(node === null, 'Should be working on an effect.');
handleSubmitError(node, error);
}
});
@ -160,7 +146,6 @@ function afterSubmit(dirtyNodes: Array<VNode>) {
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;
}
});
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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) {

View File

@ -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;
}