Match-id-53306a6f1b254950ca8a57da1d3ba3e232d8492f
This commit is contained in:
commit
b569d2114d
|
@ -86,7 +86,7 @@ function removeRootEventLister(container: Container) {
|
||||||
|
|
||||||
// 卸载入口
|
// 卸载入口
|
||||||
function destroy(container: Container): boolean {
|
function destroy(container: Container): boolean {
|
||||||
if (container._treeRoot) {
|
if (container && container._treeRoot) {
|
||||||
syncUpdates(() => {
|
syncUpdates(() => {
|
||||||
executeRender(null, container, () => {
|
executeRender(null, container, () => {
|
||||||
removeRootEventLister(container);
|
removeRootEventLister(container);
|
||||||
|
|
|
@ -7,7 +7,6 @@ import type { VNode, ContextType } from './Types';
|
||||||
import type { Container } from '../dom/DOMOperator';
|
import type { Container } from '../dom/DOMOperator';
|
||||||
|
|
||||||
import { getNSCtx } 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”,
|
// 保存的是“http://www.w3.org/1999/xhtml”或“http://www.w3.org/2000/svg”,
|
||||||
// 用于识别是使用document.createElement()还是使用document.createElementNS()创建DOM
|
// 用于识别是使用document.createElement()还是使用document.createElementNS()创建DOM
|
||||||
|
@ -44,32 +43,3 @@ export function resetContext(providerVNode: VNode) {
|
||||||
|
|
||||||
context.value = providerVNode.context;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { VNode } from './Types';
|
||||||
|
|
||||||
import { callRenderQueueImmediate, pushRenderCallback } from './taskExecutor/RenderQueue';
|
import { callRenderQueueImmediate, pushRenderCallback } from './taskExecutor/RenderQueue';
|
||||||
import { updateVNode } from './vnode/VNodeCreator';
|
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 { FlagUtils, InitFlag, Interrupted } from './vnode/VNodeFlags';
|
||||||
import { captureVNode } from './render/BaseComponent';
|
import { captureVNode } from './render/BaseComponent';
|
||||||
import { checkLoopingUpdateLimit, submitToRender } from './submit/Submit';
|
import { checkLoopingUpdateLimit, submitToRender } from './submit/Submit';
|
||||||
|
@ -30,7 +30,12 @@ import {
|
||||||
isExecuting,
|
isExecuting,
|
||||||
setExecuteMode,
|
setExecuteMode,
|
||||||
} from './ExecuteMode';
|
} from './ExecuteMode';
|
||||||
import { recoverParentContext, resetNamespaceCtx, resetParentContext, setNamespaceCtx } from './ContextSaver';
|
import {
|
||||||
|
resetContext,
|
||||||
|
resetNamespaceCtx,
|
||||||
|
setContext,
|
||||||
|
setNamespaceCtx,
|
||||||
|
} from './ContextSaver';
|
||||||
import {
|
import {
|
||||||
updateChildShouldUpdate,
|
updateChildShouldUpdate,
|
||||||
updateParentsChildShouldUpdate,
|
updateParentsChildShouldUpdate,
|
||||||
|
@ -244,7 +249,7 @@ function buildVNodeTree(treeRoot: VNode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 恢复父节点的context
|
// 恢复父节点的context
|
||||||
recoverParentContext(startVNode);
|
recoverTreeContext(startVNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置环境变量,为重新进行深度遍历做准备
|
// 重置环境变量,为重新进行深度遍历做准备
|
||||||
|
@ -272,7 +277,7 @@ function buildVNodeTree(treeRoot: VNode) {
|
||||||
}
|
}
|
||||||
if (startVNode.tag !== TreeRoot) { // 不是根节点
|
if (startVNode.tag !== TreeRoot) { // 不是根节点
|
||||||
// 恢复父节点的context
|
// 恢复父节点的context
|
||||||
resetParentContext(startVNode);
|
resetTreeContext(startVNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
setProcessingClassVNode(null);
|
setProcessingClassVNode(null);
|
||||||
|
@ -280,6 +285,39 @@ function buildVNodeTree(treeRoot: VNode) {
|
||||||
setExecuteMode(preMode);
|
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) {
|
function renderFromRoot(treeRoot) {
|
||||||
runAsyncEffects();
|
runAsyncEffects();
|
||||||
|
|
|
@ -7,9 +7,10 @@ import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator';
|
||||||
import componentRenders from './index';
|
import componentRenders from './index';
|
||||||
import { setProcessingVNode } from '../GlobalVar';
|
import { setProcessingVNode } from '../GlobalVar';
|
||||||
import { clearVNodeObservers } from '../../horizonx/store/StoreHandler';
|
import { clearVNodeObservers } from '../../horizonx/store/StoreHandler';
|
||||||
|
import { pushCurrentRoot } from '../RootStack';
|
||||||
|
|
||||||
// 复用vNode时,也需对stack进行处理
|
// 复用vNode时,也需对树的上下文值处理,如context,portal, namespaceContext
|
||||||
function handlerContext(processing: VNode) {
|
function setTreeContextValue(processing: VNode) {
|
||||||
switch (processing.tag) {
|
switch (processing.tag) {
|
||||||
case TreeRoot:
|
case TreeRoot:
|
||||||
setNamespaceCtx(processing, processing.realNode);
|
setNamespaceCtx(processing, processing.realNode);
|
||||||
|
@ -19,6 +20,7 @@ function handlerContext(processing: VNode) {
|
||||||
break;
|
break;
|
||||||
case DomPortal:
|
case DomPortal:
|
||||||
setNamespaceCtx(processing, processing.realNode);
|
setNamespaceCtx(processing, processing.realNode);
|
||||||
|
pushCurrentRoot(processing);
|
||||||
break;
|
break;
|
||||||
case ContextProvider: {
|
case ContextProvider: {
|
||||||
const newValue = processing.props.value;
|
const newValue = processing.props.value;
|
||||||
|
@ -36,7 +38,7 @@ export function captureVNode(processing: VNode): VNode | null {
|
||||||
// 该vNode没有变化,不用进入capture,直接复用。
|
// 该vNode没有变化,不用进入capture,直接复用。
|
||||||
if (!processing.isCreated && processing.oldProps === processing.props && !processing.shouldUpdate) {
|
if (!processing.isCreated && processing.oldProps === processing.props && !processing.shouldUpdate) {
|
||||||
// 复用还需对stack进行处理
|
// 复用还需对stack进行处理
|
||||||
handlerContext(processing);
|
setTreeContextValue(processing);
|
||||||
|
|
||||||
return onlyUpdateChildVNodes(processing);
|
return onlyUpdateChildVNodes(processing);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as Horizon from '@cloudsop/horizon/index.ts';
|
import * as Horizon from '@cloudsop/horizon/index.ts';
|
||||||
import { getLogUtils } from '../jest/testUtils';
|
import { getLogUtils } from '../jest/testUtils';
|
||||||
|
import dispatchChangeEvent from '../utils/dispatchChangeEvent';
|
||||||
|
|
||||||
describe('PortalComponent Test', () => {
|
describe('PortalComponent Test', () => {
|
||||||
const LogUtils = getLogUtils();
|
const LogUtils = getLogUtils();
|
||||||
|
@ -14,12 +15,10 @@ describe('PortalComponent Test', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return Horizon.createPortal(
|
return Horizon.createPortal(this.props.child, this.element);
|
||||||
this.props.child,
|
|
||||||
this.element,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Horizon.render(<PortalApp child={<div>PortalApp</div>} />, container);
|
Horizon.render(<PortalApp child={<div>PortalApp</div>} />, container);
|
||||||
expect(container.textContent).toBe('');
|
expect(container.textContent).toBe('');
|
||||||
// <div>PortalApp</div>被渲染到了portalRoot而非container
|
// <div>PortalApp</div>被渲染到了portalRoot而非container
|
||||||
|
@ -43,17 +42,12 @@ describe('PortalComponent Test', () => {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return [
|
return [
|
||||||
Horizon.createPortal(
|
Horizon.createPortal(this.props.child, this.element),
|
||||||
this.props.child,
|
Horizon.createPortal(this.props.child, this.newElement),
|
||||||
this.element,
|
|
||||||
),
|
|
||||||
Horizon.createPortal(
|
|
||||||
this.props.child,
|
|
||||||
this.newElement,
|
|
||||||
)
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Horizon.render(<PortalApp child={<div>PortalApp</div>} />, container);
|
Horizon.render(<PortalApp child={<div>PortalApp</div>} />, container);
|
||||||
expect(container.textContent).toBe('');
|
expect(container.textContent).toBe('');
|
||||||
// <div>PortalApp</div>被渲染到了portalRoot而非container
|
// <div>PortalApp</div>被渲染到了portalRoot而非container
|
||||||
|
@ -82,21 +76,16 @@ describe('PortalComponent Test', () => {
|
||||||
render() {
|
render() {
|
||||||
return [
|
return [
|
||||||
<div>PortalApp1st</div>,
|
<div>PortalApp1st</div>,
|
||||||
Horizon.createPortal([
|
|
||||||
<div>PortalApp4</div>,
|
|
||||||
Horizon.createPortal(
|
|
||||||
this.props.child,
|
|
||||||
this.element3rd,
|
|
||||||
),
|
|
||||||
], this.element),
|
|
||||||
<div>PortalApp2nd</div>,
|
|
||||||
Horizon.createPortal(
|
Horizon.createPortal(
|
||||||
this.props.child,
|
[<div>PortalApp4</div>, Horizon.createPortal(this.props.child, this.element3rd)],
|
||||||
this.newElement,
|
this.element
|
||||||
)
|
),
|
||||||
|
<div>PortalApp2nd</div>,
|
||||||
|
Horizon.createPortal(this.props.child, this.newElement),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Horizon.render(<PortalApp child={<div>PortalApp</div>} />, container);
|
Horizon.render(<PortalApp child={<div>PortalApp</div>} />, container);
|
||||||
expect(container.textContent).toBe('PortalApp1stPortalApp2nd');
|
expect(container.textContent).toBe('PortalApp1stPortalApp2nd');
|
||||||
// <div>PortalApp4</div>会挂载在this.element上
|
// <div>PortalApp4</div>会挂载在this.element上
|
||||||
|
@ -120,25 +109,23 @@ describe('PortalComponent Test', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return Horizon.createPortal(
|
return Horizon.createPortal(this.props.child, this.element);
|
||||||
this.props.child,
|
|
||||||
this.element,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Horizon.render(<PortalApp key='portal' child={<div>PortalApp</div>} />, container);
|
|
||||||
|
Horizon.render(<PortalApp key="portal" child={<div>PortalApp</div>} />, container);
|
||||||
expect(container.textContent).toBe('');
|
expect(container.textContent).toBe('');
|
||||||
expect(portalRoot.textContent).toBe('PortalApp');
|
expect(portalRoot.textContent).toBe('PortalApp');
|
||||||
|
|
||||||
Horizon.render(<PortalApp key='portal' child={<div>AppPortal</div>} />, container);
|
Horizon.render(<PortalApp key="portal" child={<div>AppPortal</div>} />, container);
|
||||||
expect(container.textContent).toBe('');
|
expect(container.textContent).toBe('');
|
||||||
expect(portalRoot.textContent).toBe('AppPortal');
|
expect(portalRoot.textContent).toBe('AppPortal');
|
||||||
|
|
||||||
Horizon.render(<PortalApp key='portal' child={['por', 'tal']} />, container);
|
Horizon.render(<PortalApp key="portal" child={['por', 'tal']} />, container);
|
||||||
expect(container.textContent).toBe('');
|
expect(container.textContent).toBe('');
|
||||||
expect(portalRoot.textContent).toBe('portal');
|
expect(portalRoot.textContent).toBe('portal');
|
||||||
|
|
||||||
Horizon.render(<PortalApp key='portal' child={null} />, container);
|
Horizon.render(<PortalApp key="portal" child={null} />, container);
|
||||||
expect(container.textContent).toBe('');
|
expect(container.textContent).toBe('');
|
||||||
expect(portalRoot.textContent).toBe('');
|
expect(portalRoot.textContent).toBe('');
|
||||||
|
|
||||||
|
@ -158,10 +145,7 @@ describe('PortalComponent Test', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return Horizon.createPortal(
|
return Horizon.createPortal(this.props.child, this.element);
|
||||||
this.props.child,
|
|
||||||
this.element,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +157,6 @@ describe('PortalComponent Test', () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
LogUtils.log('bubble click event');
|
LogUtils.log('bubble click event');
|
||||||
|
@ -185,9 +168,7 @@ describe('PortalComponent Test', () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onClickCapture={handleCaptureClick()} onClick={handleClick()}>
|
<div onClickCapture={handleCaptureClick()} onClick={handleClick()}>
|
||||||
<PortalApp child={<Child />}>
|
<PortalApp child={<Child />}></PortalApp>
|
||||||
|
|
||||||
</PortalApp>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -199,24 +180,23 @@ describe('PortalComponent Test', () => {
|
||||||
expect(LogUtils.getAndClear()).toEqual([
|
expect(LogUtils.getAndClear()).toEqual([
|
||||||
// 从外到内先捕获再冒泡
|
// 从外到内先捕获再冒泡
|
||||||
'capture click event',
|
'capture click event',
|
||||||
'bubble click event'
|
'bubble click event',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Create portal at app root should not add event listener multiple times', () => {
|
it('Create portal at app root should not add event listener multiple times', () => {
|
||||||
const btnRef = Horizon.createRef();
|
const btnRef = Horizon.createRef();
|
||||||
|
|
||||||
class PortalApp extends Horizon.Component {
|
class PortalApp extends Horizon.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return Horizon.createPortal(
|
return Horizon.createPortal(this.props.child, container);
|
||||||
this.props.child,
|
|
||||||
container,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClick = jest.fn();
|
const onClick = jest.fn();
|
||||||
|
|
||||||
class App extends Horizon.Component {
|
class App extends Horizon.Component {
|
||||||
|
@ -225,14 +205,70 @@ describe('PortalComponent Test', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div>
|
return (
|
||||||
<button onClick={onClick} ref={btnRef}></button>
|
<div>
|
||||||
<PortalApp />
|
<button onClick={onClick} ref={btnRef}></button>
|
||||||
</div>;
|
<PortalApp />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Horizon.render(<App />, container);
|
Horizon.render(<App />, container);
|
||||||
btnRef.current.click();
|
btnRef.current.click();
|
||||||
expect(onClick).toHaveBeenCalledTimes(1);
|
expect(onClick).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('#76 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 App() {
|
||||||
|
const Input = () => {
|
||||||
|
const [show, setShow] = Horizon.useState(false);
|
||||||
|
showPortalInput = setShow;
|
||||||
|
|
||||||
|
Horizon.useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setShow(true);
|
||||||
|
}, 0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!show) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <input onChange={fn} ref={inputRef} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Dialog>
|
||||||
|
<Input />
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Horizon.render(<App />, container);
|
||||||
|
showPortalInput(true);
|
||||||
|
jest.advanceTimersToNextTimer();
|
||||||
|
dispatchChangeEvent(inputRef.current, 'test');
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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}));
|
||||||
|
}
|
Loading…
Reference in New Issue