Match-id-529f7b4df4c14005c057b34b7fba761b8bbd74c3
This commit is contained in:
commit
b69ebe308a
|
@ -71,13 +71,14 @@ function findDOMNode(domOrEle?: Element): null | Element | Text {
|
|||
|
||||
// 情况根节点监听器
|
||||
function removeRootEventLister(container: Container) {
|
||||
const root = container._treeRoot;
|
||||
if (root) {
|
||||
Object.keys(root.delegatedNativeEvents).forEach(event => {
|
||||
const listener = root.delegatedNativeEvents[event];
|
||||
const events = (container._treeRoot as any).$EV;
|
||||
if (events) {
|
||||
Object.keys(events).forEach(event => {
|
||||
const listener = events[event];
|
||||
|
||||
if (listener) {
|
||||
container.removeEventListener(event, listener);
|
||||
events[event] = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -225,7 +225,3 @@ export function unHideDom(tag: string, dom: Element | Text, props: Props) {
|
|||
dom.textContent = props;
|
||||
}
|
||||
}
|
||||
|
||||
export function prePortal(portal: Element): void {
|
||||
listenDelegatedEvents(portal);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { updateCommonProp } from './UpdateCommonProp';
|
|||
import { setStyles } from './StyleHandler';
|
||||
import { lazyDelegateOnRoot, listenNonDelegatedEvent } from '../../event/EventBinding';
|
||||
import { isEventProp } from '../validators/ValidateProps';
|
||||
import { getCurrentRoot } from '../../renderer/TreeBuilder';
|
||||
import { getCurrentRoot } from '../../renderer/RootStack';
|
||||
|
||||
// 初始化DOM属性和更新 DOM 属性
|
||||
export function setDomProps(dom: Element, props: Object, isNativeTag: boolean, isInit: boolean): void {
|
||||
|
|
|
@ -48,22 +48,6 @@ function listenToNativeEvent(nativeEvtName: string, delegatedElement: Element, i
|
|||
return listener;
|
||||
}
|
||||
|
||||
// 监听所有委托事件
|
||||
export function listenDelegatedEvents(dom: Element) {
|
||||
if (dom[listeningMarker]) {
|
||||
// 不需要重复注册事件
|
||||
return;
|
||||
}
|
||||
dom[listeningMarker] = true;
|
||||
|
||||
allDelegatedNativeEvents.forEach((nativeEvtName: string) => {
|
||||
// 委托冒泡事件
|
||||
listenToNativeEvent(nativeEvtName, dom, false);
|
||||
// 委托捕获事件
|
||||
listenToNativeEvent(nativeEvtName, dom, true);
|
||||
});
|
||||
}
|
||||
|
||||
// 事件懒委托,当用户定义事件后,再进行委托到根节点
|
||||
export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) {
|
||||
currentRoot.delegatedEvents.add(eventName);
|
||||
|
@ -73,9 +57,17 @@ export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) {
|
|||
|
||||
nativeEvents.forEach(nativeEvent => {
|
||||
const nativeFullName = isCapture ? nativeEvent + 'capture' : nativeEvent;
|
||||
if (!currentRoot.delegatedNativeEvents[nativeFullName]) {
|
||||
|
||||
// 事件存储在DOM节点属性,避免多个VNode(root和portal)对应同一个DOM, 造成事件重复监听
|
||||
let events = currentRoot.realNode.$EV;
|
||||
|
||||
if (!events) {
|
||||
events = (currentRoot.realNode as any).$EV = {};
|
||||
}
|
||||
|
||||
if (!events[nativeFullName]) {
|
||||
const listener = listenToNativeEvent(nativeEvent, currentRoot.realNode, isCapture);
|
||||
currentRoot.delegatedNativeEvents[nativeFullName] = listener;
|
||||
events[nativeFullName] = listener;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
|
||||
*/
|
||||
|
||||
import { VNode } from './vnode/VNode';
|
||||
const currentRootStack: VNode[] = [];
|
||||
export function getCurrentRoot() {
|
||||
return currentRootStack[currentRootStack.length - 1];
|
||||
}
|
||||
|
||||
export function pushCurrentRoot(root: VNode) {
|
||||
return currentRootStack.push(root);
|
||||
}
|
||||
|
||||
export function popCurrentRoot() {
|
||||
return currentRootStack.pop();
|
||||
}
|
|
@ -2,7 +2,7 @@ import type { VNode } from './Types';
|
|||
|
||||
import { callRenderQueueImmediate, pushRenderCallback } from './taskExecutor/RenderQueue';
|
||||
import { updateVNode } from './vnode/VNodeCreator';
|
||||
import { TreeRoot, DomComponent, DomPortal } from './vnode/VNodeTags';
|
||||
import { DomComponent, DomPortal, TreeRoot } from './vnode/VNodeTags';
|
||||
import { FlagUtils, InitFlag, Interrupted } from './vnode/VNodeFlags';
|
||||
import { captureVNode } from './render/BaseComponent';
|
||||
import { checkLoopingUpdateLimit, submitToRender } from './submit/Submit';
|
||||
|
@ -12,41 +12,39 @@ import componentRenders from './render';
|
|||
import {
|
||||
BuildCompleted,
|
||||
BuildFatalErrored,
|
||||
BuildInComplete, getBuildResult,
|
||||
BuildInComplete,
|
||||
getBuildResult,
|
||||
getStartVNode,
|
||||
setBuildResult,
|
||||
setProcessingClassVNode,
|
||||
setStartVNode
|
||||
setStartVNode,
|
||||
} from './GlobalVar';
|
||||
import {
|
||||
ByAsync,
|
||||
BySync,
|
||||
InRender,
|
||||
InEvent,
|
||||
changeMode,
|
||||
checkMode,
|
||||
copyExecuteMode,
|
||||
InEvent,
|
||||
InRender,
|
||||
isExecuting,
|
||||
setExecuteMode
|
||||
setExecuteMode,
|
||||
} from './ExecuteMode';
|
||||
import { recoverParentContext, resetParentContext, resetNamespaceCtx, setNamespaceCtx } from './ContextSaver';
|
||||
import { recoverParentContext, resetNamespaceCtx, resetParentContext, setNamespaceCtx } from './ContextSaver';
|
||||
import {
|
||||
updateChildShouldUpdate,
|
||||
updateParentsChildShouldUpdate,
|
||||
updateShouldUpdateOfTree
|
||||
updateShouldUpdateOfTree,
|
||||
} from './vnode/VNodeShouldUpdate';
|
||||
import { getPathArr } from './utils/vNodePath';
|
||||
import { injectUpdater } from '../external/devtools';
|
||||
import { popCurrentRoot, pushCurrentRoot } from './RootStack';
|
||||
|
||||
// 不可恢复错误
|
||||
let unrecoverableErrorDuringBuild: any = null;
|
||||
|
||||
// 当前运行的vNode节点
|
||||
let processing: VNode | null = null;
|
||||
let currentRoot: VNode | null = null;
|
||||
export function getCurrentRoot() {
|
||||
return currentRoot;
|
||||
}
|
||||
|
||||
export function setProcessing(vNode: VNode | null) {
|
||||
processing = vNode;
|
||||
|
@ -280,7 +278,7 @@ function buildVNodeTree(treeRoot: VNode) {
|
|||
// 总体任务入口
|
||||
function renderFromRoot(treeRoot) {
|
||||
runAsyncEffects();
|
||||
currentRoot = treeRoot;
|
||||
pushCurrentRoot(treeRoot);
|
||||
// 1. 构建vNode树
|
||||
buildVNodeTree(treeRoot);
|
||||
|
||||
|
@ -291,8 +289,7 @@ function renderFromRoot(treeRoot) {
|
|||
|
||||
// 2. 提交变更
|
||||
submitToRender(treeRoot);
|
||||
currentRoot = null;
|
||||
|
||||
popCurrentRoot();
|
||||
if (window.__HORIZON_DEV_HOOK__) {
|
||||
const hook = window.__HORIZON_DEV_HOOK__;
|
||||
// injector.js 可能在 Horizon 代码之后加载,此时无 __HORIZON_DEV_HOOK__ 全局变量
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
import type { VNode } from '../Types';
|
||||
import { resetNamespaceCtx, setNamespaceCtx } from '../ContextSaver';
|
||||
import { createChildrenByDiff } from '../diff/nodeDiffComparator';
|
||||
import { prePortal } from '../../dom/DOMOperator';
|
||||
import { popCurrentRoot, pushCurrentRoot } from '../RootStack';
|
||||
|
||||
export function bubbleRender(processing: VNode) {
|
||||
resetNamespaceCtx(processing);
|
||||
|
||||
if (processing.isCreated) {
|
||||
prePortal(processing.realNode);
|
||||
}
|
||||
popCurrentRoot();
|
||||
}
|
||||
|
||||
function capturePortalComponent(processing: VNode) {
|
||||
setNamespaceCtx(processing, processing.realNode);
|
||||
pushCurrentRoot(processing);
|
||||
|
||||
const newElements = processing.props;
|
||||
if (processing.isCreated) {
|
||||
|
|
|
@ -78,7 +78,6 @@ export class VNode {
|
|||
// 根节点数据
|
||||
toUpdateNodes: Set<VNode> | null; // 保存要更新的节点
|
||||
delegatedEvents: Set<string>;
|
||||
delegatedNativeEvents: Record<string, () => void>;
|
||||
|
||||
belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用
|
||||
|
||||
|
@ -100,7 +99,6 @@ export class VNode {
|
|||
this.task = null;
|
||||
this.toUpdateNodes = new Set<VNode>();
|
||||
this.delegatedEvents = new Set<string>();
|
||||
this.delegatedNativeEvents = {};
|
||||
this.updates = null;
|
||||
this.stateCallbacks = null;
|
||||
this.state = null;
|
||||
|
@ -137,6 +135,7 @@ export class VNode {
|
|||
case DomPortal:
|
||||
this.realNode = null;
|
||||
this.context = null;
|
||||
this.delegatedEvents = new Set<string>();
|
||||
this.src = null;
|
||||
break;
|
||||
case DomComponent:
|
||||
|
|
|
@ -202,4 +202,37 @@ describe('PortalComponent Test', () => {
|
|||
'bubble click event'
|
||||
]);
|
||||
});
|
||||
|
||||
it('Create portal at app root should not add event listener multiple times', () => {
|
||||
const btnRef = Horizon.createRef();
|
||||
class PortalApp extends Horizon.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return Horizon.createPortal(
|
||||
this.props.child,
|
||||
container,
|
||||
);
|
||||
}
|
||||
}
|
||||
const onClick = jest.fn();
|
||||
|
||||
class App extends Horizon.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<button onClick={onClick} ref={btnRef}></button>
|
||||
<PortalApp />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
Horizon.render(<App />, container);
|
||||
btnRef.current.click();
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue