From c5a275d6570e796858b8403cfab8ed0924b86917 Mon Sep 17 00:00:00 2001
From: * <8>
Date: Tue, 13 Sep 2022 16:29:26 +0800
Subject: [PATCH 1/2] Match-id-cf2539a89122b0c25a798cf4990d143902cb20dd
---
.../ComponentTest/PortalComponent.test.js | 42 +++++++++++++++++++
.../__tests__/utils/dispatchChangeEvent.js | 9 ++++
2 files changed, 51 insertions(+)
create mode 100644 scripts/__tests__/utils/dispatchChangeEvent.js
diff --git a/scripts/__tests__/ComponentTest/PortalComponent.test.js b/scripts/__tests__/ComponentTest/PortalComponent.test.js
index 05908f18..5743093d 100755
--- a/scripts/__tests__/ComponentTest/PortalComponent.test.js
+++ b/scripts/__tests__/ComponentTest/PortalComponent.test.js
@@ -1,5 +1,6 @@
import * as Horizon from '@cloudsop/horizon/index.ts';
import { getLogUtils } from '../jest/testUtils';
+import dispatchChangeEvent from '../utils/dispatchChangeEvent';
describe('PortalComponent Test', () => {
const LogUtils = getLogUtils();
@@ -235,4 +236,45 @@ describe('PortalComponent Test', () => {
btnRef.current.click();
expect(onClick).toHaveBeenCalledTimes(1);
});
+
+ it('Portal onChange should activate', () => {
+ class Dialog extends Horizon.Component {
+ node;
+
+ constructor(props) {
+ super(props);
+ this.node = window.document.createElement('div');
+ window.document.body.appendChild(this.node);
+ }
+`` render() {
+ return Horizon.createPortal(this.props.children, this.node);
+ }
+ }
+
+ let showPortalInput;
+ const fn = jest.fn();
+ const inputRef = Horizon.createRef();
+ function Input() {
+ const [show, setShow] = Horizon.useState(false);
+ showPortalInput = setShow;
+
+ Horizon.useEffect(() => {
+ setTimeout(() => {
+ setShow(true);
+ }, 0);
+ }, []);
+
+ if (!show) {
+ return null;
+ }
+
+ return ;
+ }
+
+ Horizon.render(
, container);
+ showPortalInput(true);
+ jest.advanceTimersToNextTimer();
+ dispatchChangeEvent(inputRef.current, 'test');
+ expect(fn).toHaveBeenCalledTimes(1);
+ });
});
diff --git a/scripts/__tests__/utils/dispatchChangeEvent.js b/scripts/__tests__/utils/dispatchChangeEvent.js
new file mode 100644
index 00000000..1b8bbd04
--- /dev/null
+++ b/scripts/__tests__/utils/dispatchChangeEvent.js
@@ -0,0 +1,9 @@
+/*
+ * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
+ */
+export default function dispatchChangeEvent(inputEle, value) {
+ const nativeInputSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
+ nativeInputSetter.call(inputEle, value);
+
+ inputEle.dispatchEvent(new Event('input', { bubbles: true}));
+}
From 8e006f8cd26b46e59906694406f75657d2f2c84d Mon Sep 17 00:00:00 2001
From: * <8>
Date: Tue, 13 Sep 2022 16:36:55 +0800
Subject: [PATCH 2/2] Match-id-3d22079ded1ca2a343f8859618146c4dc7abe9a7
---
libs/horizon/src/dom/DOMExternal.ts | 2 +-
libs/horizon/src/renderer/ContextSaver.ts | 30 -----
libs/horizon/src/renderer/TreeBuilder.ts | 46 ++++++-
.../src/renderer/render/BaseComponent.ts | 8 +-
.../ComponentTest/PortalComponent.test.js | 122 +++++++++---------
5 files changed, 106 insertions(+), 102 deletions(-)
diff --git a/libs/horizon/src/dom/DOMExternal.ts b/libs/horizon/src/dom/DOMExternal.ts
index f3054261..0baa43c2 100644
--- a/libs/horizon/src/dom/DOMExternal.ts
+++ b/libs/horizon/src/dom/DOMExternal.ts
@@ -86,7 +86,7 @@ function removeRootEventLister(container: Container) {
// 卸载入口
function destroy(container: Container): boolean {
- if (container._treeRoot) {
+ if (container && container._treeRoot) {
syncUpdates(() => {
executeRender(null, container, () => {
removeRootEventLister(container);
diff --git a/libs/horizon/src/renderer/ContextSaver.ts b/libs/horizon/src/renderer/ContextSaver.ts
index 2e72ce3c..e0ea8432 100644
--- a/libs/horizon/src/renderer/ContextSaver.ts
+++ b/libs/horizon/src/renderer/ContextSaver.ts
@@ -7,7 +7,6 @@ import type { VNode, ContextType } from './Types';
import type { Container } from '../dom/DOMOperator';
import { getNSCtx } from '../dom/DOMOperator';
-import { ContextProvider } from './vnode/VNodeTags';
// 保存的是“http://www.w3.org/1999/xhtml”或“http://www.w3.org/2000/svg”,
// 用于识别是使用document.createElement()还是使用document.createElementNS()创建DOM
@@ -44,32 +43,3 @@ export function resetContext(providerVNode: VNode) {
context.value = providerVNode.context;
}
-
-// 在局部更新时,从上到下恢复父节点的context
-export function recoverParentContext(vNode: VNode) {
- const contextProviders: VNode[] = [];
- let parent = vNode.parent;
- while (parent !== null) {
- if (parent.tag === ContextProvider) {
- contextProviders.unshift(parent);
- }
- parent = parent.parent;
- }
- contextProviders.forEach(node => {
- setContext(node, node.props.value);
- });
-}
-
-// 在局部更新时,从下到上重置父节点的context
-export function resetParentContext(vNode: VNode) {
- let parent = vNode.parent;
-
- while (parent !== null) {
- if (parent.tag === ContextProvider) {
- resetContext(parent);
- }
- parent = parent.parent;
- }
-}
-
-
diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts
index 2622bc4a..d9a10bd7 100644
--- a/libs/horizon/src/renderer/TreeBuilder.ts
+++ b/libs/horizon/src/renderer/TreeBuilder.ts
@@ -2,7 +2,7 @@ import type { VNode } from './Types';
import { callRenderQueueImmediate, pushRenderCallback } from './taskExecutor/RenderQueue';
import { updateVNode } from './vnode/VNodeCreator';
-import { DomComponent, DomPortal, TreeRoot } from './vnode/VNodeTags';
+import { ContextProvider, DomComponent, DomPortal, TreeRoot } from './vnode/VNodeTags';
import { FlagUtils, InitFlag, Interrupted } from './vnode/VNodeFlags';
import { captureVNode } from './render/BaseComponent';
import { checkLoopingUpdateLimit, submitToRender } from './submit/Submit';
@@ -30,7 +30,12 @@ import {
isExecuting,
setExecuteMode,
} from './ExecuteMode';
-import { recoverParentContext, resetNamespaceCtx, resetParentContext, setNamespaceCtx } from './ContextSaver';
+import {
+ resetContext,
+ resetNamespaceCtx,
+ setContext,
+ setNamespaceCtx,
+} from './ContextSaver';
import {
updateChildShouldUpdate,
updateParentsChildShouldUpdate,
@@ -244,7 +249,7 @@ function buildVNodeTree(treeRoot: VNode) {
}
// 恢复父节点的context
- recoverParentContext(startVNode);
+ recoverTreeContext(startVNode);
}
// 重置环境变量,为重新进行深度遍历做准备
@@ -272,7 +277,7 @@ function buildVNodeTree(treeRoot: VNode) {
}
if (startVNode.tag !== TreeRoot) { // 不是根节点
// 恢复父节点的context
- resetParentContext(startVNode);
+ resetTreeContext(startVNode);
}
setProcessingClassVNode(null);
@@ -280,6 +285,39 @@ function buildVNodeTree(treeRoot: VNode) {
setExecuteMode(preMode);
}
+// 在局部更新时,从上到下恢复父节点的context和PortalStack
+function recoverTreeContext(vNode: VNode) {
+ const contextProviders: VNode[] = [];
+ let parent = vNode.parent;
+ while (parent !== null) {
+ if (parent.tag === ContextProvider) {
+ contextProviders.unshift(parent);
+ }
+ if(parent.tag === DomPortal){
+ pushCurrentRoot(parent);
+ }
+ parent = parent.parent;
+ }
+ contextProviders.forEach(node => {
+ setContext(node, node.props.value);
+ });
+}
+
+// 在局部更新时,从下到上重置父节点的context
+function resetTreeContext(vNode: VNode) {
+ let parent = vNode.parent;
+
+ while (parent !== null) {
+ if (parent.tag === ContextProvider) {
+ resetContext(parent);
+ }
+ if(parent.tag === DomPortal){
+ popCurrentRoot();
+ }
+ parent = parent.parent;
+ }
+}
+
// 总体任务入口
function renderFromRoot(treeRoot) {
runAsyncEffects();
diff --git a/libs/horizon/src/renderer/render/BaseComponent.ts b/libs/horizon/src/renderer/render/BaseComponent.ts
index e027696c..34ba6b6c 100644
--- a/libs/horizon/src/renderer/render/BaseComponent.ts
+++ b/libs/horizon/src/renderer/render/BaseComponent.ts
@@ -7,9 +7,10 @@ import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator';
import componentRenders from './index';
import { setProcessingVNode } from '../GlobalVar';
import { clearVNodeObservers } from '../../horizonx/store/StoreHandler';
+import { pushCurrentRoot } from '../RootStack';
-// 复用vNode时,也需对stack进行处理
-function handlerContext(processing: VNode) {
+// 复用vNode时,也需对树的上下文值处理,如context,portal, namespaceContext
+function setTreeContextValue(processing: VNode) {
switch (processing.tag) {
case TreeRoot:
setNamespaceCtx(processing, processing.realNode);
@@ -19,6 +20,7 @@ function handlerContext(processing: VNode) {
break;
case DomPortal:
setNamespaceCtx(processing, processing.realNode);
+ pushCurrentRoot(processing);
break;
case ContextProvider: {
const newValue = processing.props.value;
@@ -36,7 +38,7 @@ export function captureVNode(processing: VNode): VNode | null {
// 该vNode没有变化,不用进入capture,直接复用。
if (!processing.isCreated && processing.oldProps === processing.props && !processing.shouldUpdate) {
// 复用还需对stack进行处理
- handlerContext(processing);
+ setTreeContextValue(processing);
return onlyUpdateChildVNodes(processing);
}
diff --git a/scripts/__tests__/ComponentTest/PortalComponent.test.js b/scripts/__tests__/ComponentTest/PortalComponent.test.js
index 5743093d..2dcadc38 100755
--- a/scripts/__tests__/ComponentTest/PortalComponent.test.js
+++ b/scripts/__tests__/ComponentTest/PortalComponent.test.js
@@ -15,12 +15,10 @@ describe('PortalComponent Test', () => {
}
render() {
- return Horizon.createPortal(
- this.props.child,
- this.element,
- );
+ return Horizon.createPortal(this.props.child, this.element);
}
}
+
Horizon.render(
PortalApp } />, container);
expect(container.textContent).toBe('');
//