From 4d24ebd544526051dd33cf675b8674e8164233f0 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Wed, 22 Jun 2022 16:47:15 +0800
Subject: [PATCH] Match-id-eb7f82b390c5406b3d957108c9dc9e8f650a0a74
---
.../DOMPropertiesHandler.ts | 3 +-
libs/horizon/src/event/EventBinding.ts | 47 ++++++------
libs/horizon/src/event/EventCollection.ts | 15 ----
.../src/event/{const.ts => EventHub.ts} | 26 +++++--
.../EventFactory.ts => EventWrapper.ts} | 0
libs/horizon/src/event/HorizonEventMain.ts | 74 ++++++++++++++++---
libs/horizon/src/event/ListenerGetter.ts | 2 +-
libs/horizon/src/event/WrapperListener.ts | 0
.../simulatedEvtHandler/ChangeEventHandler.ts | 62 ----------------
libs/horizon/src/renderer/vnode/VNode.ts | 2 +
scripts/__tests__/EventTest/EventMain.test.js | 72 ++++++++++++++++--
scripts/__tests__/jest/testUtils.js | 4 +-
12 files changed, 181 insertions(+), 126 deletions(-)
delete mode 100644 libs/horizon/src/event/EventCollection.ts
rename libs/horizon/src/event/{const.ts => EventHub.ts} (73%)
rename libs/horizon/src/event/{customEvents/EventFactory.ts => EventWrapper.ts} (100%)
delete mode 100644 libs/horizon/src/event/WrapperListener.ts
delete mode 100644 libs/horizon/src/event/simulatedEvtHandler/ChangeEventHandler.ts
diff --git a/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts b/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts
index 0d12dd84..7864426d 100644
--- a/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts
+++ b/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts
@@ -1,4 +1,4 @@
-import { allDelegatedHorizonEvents } from '../../event/EventCollection';
+import { allDelegatedHorizonEvents } from '../../event/EventHub';
import { updateCommonProp } from './UpdateCommonProp';
import { setStyles } from './StyleHandler';
import { lazyDelegateOnRoot, listenNonDelegatedEvent } from '../../event/EventBinding';
@@ -19,7 +19,6 @@ export function setDomProps(dom: Element, props: Object, isNativeTag: boolean, i
setStyles(dom, propVal);
} else if (isEventProp(propName)) {
// 事件监听属性处理
- // TODO
const currentRoot = getCurrentRoot();
if (!allDelegatedHorizonEvents.has(propName)) {
listenNonDelegatedEvent(propName, dom, propVal);
diff --git a/libs/horizon/src/event/EventBinding.ts b/libs/horizon/src/event/EventBinding.ts
index 73c43c35..9398d648 100644
--- a/libs/horizon/src/event/EventBinding.ts
+++ b/libs/horizon/src/event/EventBinding.ts
@@ -1,27 +1,29 @@
/**
* 事件绑定实现,分为绑定委托事件和非委托事件
*/
-import { allDelegatedHorizonEvents, allDelegatedNativeEvents } from './EventCollection';
-import {isDocument} from '../dom/utils/Common';
import {
- getNearestVNode,
- getNonDelegatedListenerMap,
-} from '../dom/DOMInternalKeys';
-import {runDiscreteUpdates} from '../renderer/TreeBuilder';
-import {isMounted} from '../renderer/vnode/VNodeUtils';
-import {SuspenseComponent} from '../renderer/vnode/VNodeTags';
-import {handleEventMain} from './HorizonEventMain';
-import {decorateNativeEvent} from './customEvents/EventFactory';
+ allDelegatedHorizonEvents,
+ allDelegatedNativeEvents,
+} from './EventHub';
+import { isDocument } from '../dom/utils/Common';
+import { getNearestVNode, getNonDelegatedListenerMap } from '../dom/DOMInternalKeys';
+import { runDiscreteUpdates } from '../renderer/TreeBuilder';
+import { handleEventMain } from './HorizonEventMain';
+import { decorateNativeEvent } from './EventWrapper';
import { VNode } from '../renderer/vnode/VNode';
-const listeningMarker = '_horizonListening' + Math.random().toString(36).slice(4);
+const listeningMarker =
+ '_horizonListening' +
+ Math.random()
+ .toString(36)
+ .slice(4);
// 触发委托事件
function triggerDelegatedEvent(
nativeEvtName: string,
isCapture: boolean,
targetDom: EventTarget,
- nativeEvent, // 事件对象event
+ nativeEvent // 事件对象event
) {
// 执行之前的调度事件
runDiscreteUpdates();
@@ -33,11 +35,7 @@ function triggerDelegatedEvent(
}
// 监听委托事件
-function listenToNativeEvent(
- nativeEvtName: string,
- delegatedElement: Element,
- isCapture: boolean,
-): void {
+function listenToNativeEvent(nativeEvtName: string, delegatedElement: Element, isCapture: boolean): void {
let dom: Element | Document = delegatedElement;
// document层次可能触发selectionchange事件,为了捕获这类事件,selectionchange事件绑定在document节点上
if (nativeEvtName === 'selectionchange' && !isDocument(delegatedElement)) {
@@ -70,10 +68,15 @@ export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) {
const isCapture = isCaptureEvent(eventName);
const nativeEvents = allDelegatedHorizonEvents.get(eventName);
- nativeEvents.forEach(nativeEvents => {
- listenToNativeEvent(nativeEvents, currentRoot.realNode, isCapture);
+
+ nativeEvents.forEach(nativeEvent => {
+ if (!currentRoot.delegatedNativeEvents.has(nativeEvent)) {
+ listenToNativeEvent(nativeEvent, currentRoot.realNode, isCapture);
+ currentRoot.delegatedNativeEvents.add(nativeEvent);
+ }
});
}
+
// 通过horizon事件名获取到native事件名
function getNativeEvtName(horizonEventName, capture) {
let nativeName;
@@ -105,11 +108,7 @@ function getWrapperListener(horizonEventName, nativeEvtName, targetElement, list
}
// 非委托事件单独监听到各自dom节点
-export function listenNonDelegatedEvent(
- horizonEventName: string,
- domElement: Element,
- listener,
-): void {
+export function listenNonDelegatedEvent(horizonEventName: string, domElement: Element, listener): void {
const isCapture = isCaptureEvent(horizonEventName);
const nativeEvtName = getNativeEvtName(horizonEventName, isCapture);
diff --git a/libs/horizon/src/event/EventCollection.ts b/libs/horizon/src/event/EventCollection.ts
deleted file mode 100644
index d70dcf10..00000000
--- a/libs/horizon/src/event/EventCollection.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import {horizonEventToNativeMap} from './const';
-
-// 需要委托的horizon事件和原生事件对应关系
-export const allDelegatedHorizonEvents = new Map();
-// 所有委托的原生事件集合
-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/const.ts b/libs/horizon/src/event/EventHub.ts
similarity index 73%
rename from libs/horizon/src/event/const.ts
rename to libs/horizon/src/event/EventHub.ts
index 87d10821..944112f2 100644
--- a/libs/horizon/src/event/const.ts
+++ b/libs/horizon/src/event/EventHub.ts
@@ -1,3 +1,7 @@
+// 需要委托的horizon事件和原生事件对应关系
+export const allDelegatedHorizonEvents = new Map();
+// 所有委托的原生事件集合
+export const allDelegatedNativeEvents = new Set();
// Horizon事件和原生事件对应关系
export const horizonEventToNativeMap = new Map([
@@ -33,10 +37,9 @@ export const horizonEventToNativeMap = new Map([
['onAnimationEnd', ['animationend']],
['onAnimationIteration', ['animationiteration']],
['onAnimationStart', ['animationstart']],
- ['onTransitionEnd', ['transitionend']]
+ ['onTransitionEnd', ['transitionend']],
]);
-
-export const CommonEventToHorizonMap = {
+export const NativeEventToHorizonMap = {
click: 'click',
dblclick: 'doubleClick',
contextmenu: 'contextMenu',
@@ -69,11 +72,22 @@ export const CommonEventToHorizonMap = {
compositionend: 'compositionEnd',
compositionupdate: 'compositionUpdate',
};
-
export const CHAR_CODE_SPACE = 32;
-
-
export const EVENT_TYPE_BUBBLE = 'Bubble';
export const EVENT_TYPE_CAPTURE = 'Capture';
export const EVENT_TYPE_ALL = 'All';
+horizonEventToNativeMap.forEach((dependencies, horizonEvent) => {
+ allDelegatedHorizonEvents.set(horizonEvent, dependencies);
+ allDelegatedHorizonEvents.set(horizonEvent + 'Capture', dependencies);
+
+ dependencies.forEach(d => {
+ allDelegatedNativeEvents.add(d);
+ });
+});
+
+export function transformToHorizonEvent(nativeEvtName: string) {
+ const name = NativeEventToHorizonMap[nativeEvtName];
+ // 例:dragEnd -> onDragEnd
+ return !name ? '' : `on${name[0].toUpperCase()}${name.slice(1)}`;
+}
diff --git a/libs/horizon/src/event/customEvents/EventFactory.ts b/libs/horizon/src/event/EventWrapper.ts
similarity index 100%
rename from libs/horizon/src/event/customEvents/EventFactory.ts
rename to libs/horizon/src/event/EventWrapper.ts
diff --git a/libs/horizon/src/event/HorizonEventMain.ts b/libs/horizon/src/event/HorizonEventMain.ts
index e183ce2f..aff68d17 100644
--- a/libs/horizon/src/event/HorizonEventMain.ts
+++ b/libs/horizon/src/event/HorizonEventMain.ts
@@ -1,19 +1,76 @@
-import type { AnyNativeEvent } from './Types';
-import { ListenerUnitList } from './Types';
+import { AnyNativeEvent, ListenerUnitList } from './Types';
import type { VNode } from '../renderer/Types';
-
-import { CommonEventToHorizonMap, EVENT_TYPE_BUBBLE, EVENT_TYPE_CAPTURE, horizonEventToNativeMap } from './const';
-import { getListeners as getChangeListeners } from './simulatedEvtHandler/ChangeEventHandler';
-import { setPropertyWritable } from './utils';
-import { decorateNativeEvent } from './customEvents/EventFactory';
+import { isInputElement, setPropertyWritable } from './utils';
+import { decorateNativeEvent } from './EventWrapper';
import { getListenersFromTree } from './ListenerGetter';
import { asyncUpdates, runDiscreteUpdates } from '../renderer/Renderer';
import { findRoot } from '../renderer/vnode/VNodeUtils';
import { syncRadiosHandler } from '../dom/valueHandler/InputValueHandler';
+import {
+ EVENT_TYPE_ALL,
+ EVENT_TYPE_BUBBLE,
+ EVENT_TYPE_CAPTURE,
+ horizonEventToNativeMap,
+ transformToHorizonEvent,
+} from './EventHub';
+import { getDomTag } from '../dom/utils/Common';
+import { updateInputValueIfChanged } from '../dom/valueHandler/ValueChangeHandler';
+import { getDom } from '../dom/DOMInternalKeys';
// web规范,鼠标右键key值
const RIGHT_MOUSE_BUTTON = 2;
+// 返回是否需要触发change事件标记
+// | 元素 | 事件 | 需要值变更 |
+// | --- | --- | --------------- |
+// | / | click | YES |
+// | | input / change | YES |
+function shouldTriggerChangeEvent(targetDom, evtName) {
+ const { type } = targetDom;
+ const domTag = getDomTag(targetDom);
+
+ if (domTag === 'select' || (domTag === 'input' && type === 'file')) {
+ return evtName === 'change';
+ } else if (domTag === 'input' && (type === 'checkbox' || type === 'radio')) {
+ if (evtName === 'click') {
+ return updateInputValueIfChanged(targetDom);
+ }
+ } else if (isInputElement(targetDom)) {
+ if (evtName === 'input' || evtName === 'change') {
+ return updateInputValueIfChanged(targetDom);
+ }
+ }
+ return false;
+}
+
+/**
+ *
+ * 支持input/textarea/select的onChange事件
+ */
+function getChangeListeners(
+ nativeEvtName: string,
+ nativeEvt: AnyNativeEvent,
+ vNode: null | VNode,
+): ListenerUnitList {
+ if (!vNode) {
+ return [];
+ }
+ const targetDom = getDom(vNode);
+
+ // 判断是否需要触发change事件
+ if (shouldTriggerChangeEvent(targetDom, nativeEvtName)) {
+ const event = decorateNativeEvent(
+ 'onChange',
+ 'change',
+ nativeEvt,
+ );
+ return getListenersFromTree(vNode, 'onChange', event, EVENT_TYPE_ALL);
+ }
+
+ return [];
+}
+
// 获取事件触发的普通事件监听方法队列
function getCommonListeners(
nativeEvtName: string,
@@ -22,8 +79,7 @@ function getCommonListeners(
target: null | EventTarget,
isCapture: boolean,
): ListenerUnitList {
- const name = CommonEventToHorizonMap[nativeEvtName];
- const horizonEvtName = !name ? '' : `on${name[0].toUpperCase()}${name.slice(1)}`; // 例:dragEnd -> onDragEnd
+ const horizonEvtName = transformToHorizonEvent(nativeEvtName);
if (!horizonEvtName) {
return [];
diff --git a/libs/horizon/src/event/ListenerGetter.ts b/libs/horizon/src/event/ListenerGetter.ts
index d6860f35..731368ed 100644
--- a/libs/horizon/src/event/ListenerGetter.ts
+++ b/libs/horizon/src/event/ListenerGetter.ts
@@ -1,7 +1,7 @@
import { VNode } from '../renderer/Types';
import { DomComponent } from '../renderer/vnode/VNodeTags';
-import { EVENT_TYPE_ALL, EVENT_TYPE_CAPTURE, EVENT_TYPE_BUBBLE } from './const';
import { AnyNativeEvent, ListenerUnitList } from './Types';
+import { EVENT_TYPE_ALL, EVENT_TYPE_BUBBLE, EVENT_TYPE_CAPTURE } from './EventHub';
// 从vnode属性中获取事件listener
function getListenerFromVNode(vNode: VNode, eventName: string): Function | null {
diff --git a/libs/horizon/src/event/WrapperListener.ts b/libs/horizon/src/event/WrapperListener.ts
deleted file mode 100644
index e69de29b..00000000
diff --git a/libs/horizon/src/event/simulatedEvtHandler/ChangeEventHandler.ts b/libs/horizon/src/event/simulatedEvtHandler/ChangeEventHandler.ts
deleted file mode 100644
index 285ac435..00000000
--- a/libs/horizon/src/event/simulatedEvtHandler/ChangeEventHandler.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import {decorateNativeEvent} from '../customEvents/EventFactory';
-import {getDom} from '../../dom/DOMInternalKeys';
-import {updateInputValueIfChanged} from '../../dom/valueHandler/ValueChangeHandler';
-import {isInputElement} from '../utils';
-import {EVENT_TYPE_ALL} from '../const';
-import {AnyNativeEvent, ListenerUnitList} from '../Types';
-import {
- getListenersFromTree,
-} from '../ListenerGetter';
-import {VNode} from '../../renderer/Types';
-import {getDomTag} from '../../dom/utils/Common';
-
-// 返回是否需要触发change事件标记
-// | 元素 | 事件 | 需要值变更 |
-// | --- | --- | --------------- |
-// | / | click | YES |
-// | | input / change | YES |
-function shouldTriggerChangeEvent(targetDom, evtName) {
- const { type } = targetDom;
- const domTag = getDomTag(targetDom);
-
- if (domTag === 'select' || (domTag === 'input' && type === 'file')) {
- return evtName === 'change';
- } else if (domTag === 'input' && (type === 'checkbox' || type === 'radio')) {
- if (evtName === 'click') {
- return updateInputValueIfChanged(targetDom);
- }
- } else if (isInputElement(targetDom)) {
- if (evtName === 'input' || evtName === 'change') {
- return updateInputValueIfChanged(targetDom);
- }
- }
- return false;
-}
-
-/**
- *
- * 支持input/textarea/select的onChange事件
- */
-export function getListeners(
- nativeEvtName: string,
- nativeEvt: AnyNativeEvent,
- vNode: null | VNode
-): ListenerUnitList {
- if (!vNode) {
- return [];
- }
- const targetDom = getDom(vNode);
-
- // 判断是否需要触发change事件
- if (shouldTriggerChangeEvent(targetDom, nativeEvtName)) {
- const event = decorateNativeEvent(
- 'onChange',
- 'change',
- nativeEvt,
- );
- return getListenersFromTree(vNode, 'onChange', event, EVENT_TYPE_ALL);
- }
-
- return [];
-}
diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts
index d67f0e73..7c360e47 100644
--- a/libs/horizon/src/renderer/vnode/VNode.ts
+++ b/libs/horizon/src/renderer/vnode/VNode.ts
@@ -78,6 +78,7 @@ export class VNode {
// 根节点数据
toUpdateNodes: Set | null; // 保存要更新的节点
delegatedEvents: Set
+ delegatedNativeEvents: Set
belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用
@@ -98,6 +99,7 @@ export class VNode {
this.task = null;
this.toUpdateNodes = new Set();
this.delegatedEvents = new Set();
+ this.delegatedNativeEvents = new Set();
this.updates = null;
this.stateCallbacks = null;
this.state = null;
diff --git a/scripts/__tests__/EventTest/EventMain.test.js b/scripts/__tests__/EventTest/EventMain.test.js
index 1e6c52b7..2b3868ba 100644
--- a/scripts/__tests__/EventTest/EventMain.test.js
+++ b/scripts/__tests__/EventTest/EventMain.test.js
@@ -1,6 +1,13 @@
import * as Horizon from '@cloudsop/horizon/index.ts';
import * as TestUtils from '../jest/testUtils';
+function dispatchChangeEvent(input) {
+ const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
+ nativeInputValueSetter.call(input, 'test');
+
+ input.dispatchEvent(new Event('input', { bubbles: true }));
+}
+
describe('事件', () => {
const LogUtils = TestUtils.getLogUtils();
it('根节点挂载全量事件', () => {
@@ -162,11 +169,7 @@ describe('事件', () => {
LogUtils.log('change');
},
});
-
- const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
- nativeInputValueSetter.call(inputRef.current, 'test');
-
- inputRef.current.dispatchEvent(new Event('input', { bubbles: true }));
+ dispatchChangeEvent(inputRef.current);
expect(LogUtils.getAndClear()).toEqual(['change']);
});
@@ -209,4 +212,63 @@ describe('事件', () => {
// 先选择选项1,radio1应该重新触发onchange
clickRadioAndExpect(radio1Ref.current, [2, 1]);
});
+
+ it('多根节点下,事件挂载正确', () => {
+ const root1 = document.createElement('div');
+ const root2 = document.createElement('div');
+ root1.key = 'root1';
+ root2.key = 'root2';
+ let input1, input2, update1, update2;
+
+ function App1() {
+ const [props, setProps] = Horizon.useState({});
+ update1 = setProps;
+ return (
+ (input1 = n)}
+ onChange={() => {
+ LogUtils.log('input1 changed');
+ }}
+ />
+ );
+ }
+
+ function App2() {
+ const [props, setProps] = Horizon.useState({});
+ update2 = setProps;
+
+ return (
+ (input2 = n)}
+ onChange={() => {
+ LogUtils.log('input2 changed');
+ }}
+ />
+ );
+ }
+
+ // 多根mount阶段挂载onChange事件
+ Horizon.render(, root1);
+ Horizon.render(, root2);
+
+ dispatchChangeEvent(input1);
+ expect(LogUtils.getAndClear()).toEqual(['input1 changed']);
+ dispatchChangeEvent(input2);
+ expect(LogUtils.getAndClear()).toEqual(['input2 changed']);
+
+ // 多根update阶段挂载onClick事件
+ update1({
+ onClick: () => LogUtils.log('input1 clicked'),
+ });
+ update2({
+ onClick: () => LogUtils.log('input2 clicked'),
+ });
+
+ input1.click();
+ expect(LogUtils.getAndClear()).toEqual(['input1 clicked']);
+ input2.click();
+ expect(LogUtils.getAndClear()).toEqual(['input2 clicked']);
+ });
});
diff --git a/scripts/__tests__/jest/testUtils.js b/scripts/__tests__/jest/testUtils.js
index 6d4b7ac2..65cea65e 100755
--- a/scripts/__tests__/jest/testUtils.js
+++ b/scripts/__tests__/jest/testUtils.js
@@ -1,4 +1,4 @@
-import { allDelegatedNativeEvents } from '../../../libs/horizon/src/event/EventCollection';
+import { allDelegatedNativeEvents } from '@cloudsop/horizon/src/event/EventHub';
//import * as LogUtils from './logUtils';
export const stopBubbleOrCapture = (e, value) => {
@@ -107,4 +107,4 @@ export function getLogUtils() {
logger = new LogUtils();
}
return logger;
-}
\ No newline at end of file
+}