Match-id-529f7b4df4c14005c057b34b7fba761b8bbd74c3

This commit is contained in:
* 2022-09-05 17:23:34 +08:00 committed by *
commit b69ebe308a
9 changed files with 84 additions and 51 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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__ 全局变量

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import { getLogUtils } from '../jest/testUtils';
describe('PortalComponent Test', () => {
const LogUtils = getLogUtils();
it('将子节点渲染到存在于父组件以外的 DOM 节点', () => {
const portalRoot = document.createElement('div');
@ -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);
});
});