diff --git a/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts b/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts index a0ab84b3..5c0773c1 100644 --- a/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts +++ b/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts @@ -48,7 +48,7 @@ export function setDomProps(dom: Element, props: Object, isNativeTag: boolean, i } } else if (propName === 'dangerouslySetInnerHTML') { dom.innerHTML = propVal.__html; - } else if (!isInit || (isInit && propVal != null)) { + } else if (!isInit || propVal != null) { updateCommonProp(dom, propName, propVal, isNativeTag); } } @@ -70,7 +70,7 @@ export function compareProps(oldProps: Object, newProps: Object): Object { for (let i = 0; i < oldPropsLength; i++) { propName = keysOfOldProps[i]; // 新属性中包含该属性或者该属性为空值的属性不需要处理 - if (keysOfNewProps.includes(propName) || oldProps[propName] == null) { + if ( oldProps[propName] == null || keysOfNewProps.includes(propName)) { continue; } diff --git a/libs/horizon/src/event/EventBinding.ts b/libs/horizon/src/event/EventBinding.ts index 25c52b90..fd7a4d0c 100644 --- a/libs/horizon/src/event/EventBinding.ts +++ b/libs/horizon/src/event/EventBinding.ts @@ -82,8 +82,7 @@ export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) { } if (!events[nativeFullName]) { - const listener = listenToNativeEvent(nativeEvent, currentRoot.realNode, isCapture); - events[nativeFullName] = listener; + events[nativeFullName] = listenToNativeEvent(nativeEvent, currentRoot.realNode, isCapture); } }); } diff --git a/libs/horizon/src/external/ChildrenUtil.ts b/libs/horizon/src/external/ChildrenUtil.ts index 39c2e96c..3624a736 100644 --- a/libs/horizon/src/external/ChildrenUtil.ts +++ b/libs/horizon/src/external/ChildrenUtil.ts @@ -17,6 +17,7 @@ import { throwIfTrue } from '../renderer/utils/throwIfTrue'; import { TYPE_COMMON_ELEMENT, TYPE_PORTAL } from './JSXElementType'; import { isValidElement, JSXElement } from './JSXElement'; +import { BELONG_CLASS_VNODE_KEY } from '../renderer/vnode/VNode'; // 生成key function getItemKey(item: any, index: number): string { @@ -83,7 +84,7 @@ function callMapFun(children: any, arr: Array, prefix: string, callback: Fu mappedChild.type, newKey, mappedChild.ref, - mappedChild.belongClassVNode, + mappedChild[BELONG_CLASS_VNODE_KEY], mappedChild.props, mappedChild.src ); diff --git a/libs/horizon/src/external/JSXElement.ts b/libs/horizon/src/external/JSXElement.ts index 3de212b3..ccf43079 100644 --- a/libs/horizon/src/external/JSXElement.ts +++ b/libs/horizon/src/external/JSXElement.ts @@ -16,6 +16,7 @@ import { TYPE_COMMON_ELEMENT } from './JSXElementType'; import { getProcessingClassVNode } from '../renderer/GlobalVar'; import { Source } from '../renderer/Types'; +import { BELONG_CLASS_VNODE_KEY } from '../renderer/vnode/VNode'; /** * vtype 节点的类型,这里固定是element @@ -36,17 +37,9 @@ export function JSXElement(type, key, ref, vNode, props, source: Source | null) ref: ref, props: props, - // 所属的class组件 - belongClassVNode: null, + // 所属的class组件,clonedeep jsxElement时需要防止无限循环 + [BELONG_CLASS_VNODE_KEY]: vNode, }; - - // 在 cloneDeep JSXElement 的时候会出现死循环,需要设置belongClassVNode的enumerable为false - Object.defineProperty(ele, 'belongClassVNode', { - configurable: false, - enumerable: false, - value: vNode, - }); - if (isDev) { // 为了test判断两个 JSXElement 对象是否相等时忽略src属性,需要设置src的enumerable为false Object.defineProperty(ele, 'src', { @@ -60,11 +53,6 @@ export function JSXElement(type, key, ref, vNode, props, source: Source | null) return ele; } -function isValidKey(key) { - const keyArray = ['key', 'ref', '__source', '__self']; - return !keyArray.includes(key); -} - function mergeDefault(sourceObj, defaultObj) { Object.keys(defaultObj).forEach(key => { if (sourceObj[key] === undefined) { @@ -73,19 +61,20 @@ function mergeDefault(sourceObj, defaultObj) { }); } +// ['key', 'ref', '__source', '__self']属性不从setting获取 +const keyArray = ['key', 'ref', '__source', '__self']; + function buildElement(isClone, type, setting, children) { // setting中的值优先级最高,clone情况下从 type 中取值,创建情况下直接赋值为 null - const key = setting && setting.key !== undefined ? String(setting.key) : isClone ? type.key : null; - const ref = setting && setting.ref !== undefined ? setting.ref : isClone ? type.ref : null; + const key = (setting && setting.key !== undefined) ? String(setting.key) : (isClone ? type.key : null); + const ref = (setting && setting.ref !== undefined) ? setting.ref : (isClone ? type.ref : null); const props = isClone ? { ...type.props } : {}; - let vNode = isClone ? type.belongClassVNode : getProcessingClassVNode(); + let vNode = isClone ? type[BELONG_CLASS_VNODE_KEY] : getProcessingClassVNode(); if (setting !== null && setting !== undefined) { - const keys = Object.keys(setting); - const keyLength = keys.length; - for (let i = 0; i < keyLength; i++) { - const k = keys[i]; - if (isValidKey(k)) { + + for (const k in setting) { + if (!keyArray.includes(k)) { props[k] = setting[k]; } } @@ -109,7 +98,6 @@ function buildElement(isClone, type, setting, children) { lineNumber: setting.__source.lineNumber, }; } - return JSXElement(element, key, ref, vNode, props, src); } diff --git a/libs/horizon/src/renderer/RootStack.ts b/libs/horizon/src/renderer/RootStack.ts index 6a793a0f..814ca840 100644 --- a/libs/horizon/src/renderer/RootStack.ts +++ b/libs/horizon/src/renderer/RootStack.ts @@ -15,15 +15,20 @@ import { VNode } from './vnode/VNode'; -const currentRootStack: VNode[] = []; +const currentRootStack: (VNode | undefined)[] = []; +let index = -1; export function getCurrentRoot() { - return currentRootStack[currentRootStack.length - 1]; + return currentRootStack[index]; } export function pushCurrentRoot(root: VNode) { - return currentRootStack.push(root); + index++; + currentRootStack[index] = root; } export function popCurrentRoot() { - return currentRootStack.pop(); + const target = currentRootStack[index]; + currentRootStack[index] = undefined; + index--; + return target; } diff --git a/libs/horizon/src/renderer/Types.ts b/libs/horizon/src/renderer/Types.ts index 91b6bfbd..9598855e 100644 --- a/libs/horizon/src/renderer/Types.ts +++ b/libs/horizon/src/renderer/Types.ts @@ -13,6 +13,8 @@ * See the Mulan PSL v2 for more details. */ +import { BELONG_CLASS_VNODE_KEY } from './vnode/VNode'; + export { VNode } from './vnode/VNode'; type Trigger = (A) => void; @@ -32,7 +34,7 @@ export type JSXElement = { key: any; ref: any; props: any; - belongClassVNode: any; + [BELONG_CLASS_VNODE_KEY]: any; }; export type ProviderType = { diff --git a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts index 61706bea..42f30b53 100644 --- a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts +++ b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts @@ -27,6 +27,7 @@ import { import { isSameType, getIteratorFn, isTextType, isIteratorType, isObjectType } from './DiffTools'; import { travelChildren } from '../vnode/VNodeUtils'; import { markVNodePath } from '../utils/vNodePath'; +import { BELONG_CLASS_VNODE_KEY } from '../vnode/VNode'; enum DiffCategory { TEXT_NODE = 'TEXT_NODE', @@ -166,11 +167,11 @@ function getNewNode(parentNode: VNode, newChild: any, oldNode: VNode | null) { if (oldNode === null || !isSameType(oldNode, newChild)) { resultNode = createVNodeFromElement(newChild); resultNode.ref = newChild.ref; - resultNode.belongClassVNode = newChild.belongClassVNode; + resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY]; } else { resultNode = updateVNode(oldNode, newChild.props); resultNode.ref = newChild.ref; - resultNode.belongClassVNode = newChild.belongClassVNode; + resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY]; } break; } else if (newChild.vtype === TYPE_PORTAL) { @@ -570,7 +571,7 @@ function diffObjectNodeHandler( } else if (isSameType(canReuseNode, newChild)) { resultNode = updateVNode(canReuseNode, newChild.props); resultNode.ref = newChild.ref; - resultNode.belongClassVNode = newChild.belongClassVNode; + resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY]; startDelVNode = resultNode.next; resultNode.next = null; } @@ -583,7 +584,7 @@ function diffObjectNodeHandler( } else { resultNode = createVNodeFromElement(newChild); resultNode.ref = newChild.ref; - resultNode.belongClassVNode = newChild.belongClassVNode; + resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY]; } } } else if (newChild.vtype === TYPE_PORTAL) { diff --git a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts index 056f18f9..5d02368e 100644 --- a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts +++ b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts @@ -52,6 +52,7 @@ import { import { handleSubmitError } from '../ErrorHandler'; import { travelVNodeTree, clearVNode, isDomVNode, getSiblingDom } from '../vnode/VNodeUtils'; import { shouldAutoFocus } from '../../dom/utils/Common'; +import { BELONG_CLASS_VNODE_KEY } from '../vnode/VNode'; function callComponentWillUnmount(vNode: VNode, instance: any) { try { @@ -163,8 +164,8 @@ function handleRef(vNode: VNode, ref, val) { } else if (refType === 'object') { (ref).current = val; } else { - if (vNode.belongClassVNode && vNode.belongClassVNode.realNode) { - vNode.belongClassVNode.realNode.refs[String(ref)] = val; + if (vNode[BELONG_CLASS_VNODE_KEY] && vNode[BELONG_CLASS_VNODE_KEY].realNode) { + vNode[BELONG_CLASS_VNODE_KEY].realNode.refs[String(ref)] = val; } } } diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 40ba0554..4911f887 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -38,6 +38,8 @@ import type { Hook } from '../hooks/HookType'; import { InitFlag } from './VNodeFlags'; import { Observer } from '../../horizonx/proxy/Observer'; +export const BELONG_CLASS_VNODE_KEY = Symbol('belongClassVNode'); + export class VNode { tag: VNodeTag; key: string | null; // 唯一标识符 @@ -97,7 +99,7 @@ export class VNode { toUpdateNodes: Set | null; // 保存要更新的节点 delegatedEvents: Set; - belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用 + [BELONG_CLASS_VNODE_KEY]: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用 // 状态管理器HorizonX使用 isStoreChange: boolean; diff --git a/libs/horizon/src/renderer/vnode/VNodeUtils.ts b/libs/horizon/src/renderer/vnode/VNodeUtils.ts index d9bb0f50..b195f886 100644 --- a/libs/horizon/src/renderer/vnode/VNodeUtils.ts +++ b/libs/horizon/src/renderer/vnode/VNodeUtils.ts @@ -20,9 +20,9 @@ import type {VNode} from '../Types'; import {DomComponent, DomPortal, DomText, TreeRoot} from './VNodeTags'; -import {isComment} from '../../dom/utils/Common'; import {getNearestVNode} from '../../dom/DOMInternalKeys'; import {Addition, InitFlag} from './VNodeFlags'; +import { BELONG_CLASS_VNODE_KEY } from './VNode'; export function travelChildren( beginVNode: VNode | null, @@ -124,7 +124,7 @@ export function clearVNode(vNode: VNode) { vNode.toUpdateNodes = null; - vNode.belongClassVNode = null; + vNode[BELONG_CLASS_VNODE_KEY] = null; if (window.__HORIZON_DEV_HOOK__) { const hook = window.__HORIZON_DEV_HOOK__; hook.deleteVNode(vNode); diff --git a/scripts/__tests__/ComponentTest/JsxElement.test.js b/scripts/__tests__/ComponentTest/JsxElement.test.js new file mode 100644 index 00000000..8794fbbf --- /dev/null +++ b/scripts/__tests__/ComponentTest/JsxElement.test.js @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +import * as Horizon from '@cloudsop/horizon/index.ts'; + +describe('JSX Element test', () => { + it('symbol attribute prevent cloneDeep unlimited loop', function () { + + function cloneDeep(obj) { + const result = {}; + Object.keys(obj).forEach(key => { + if (obj[key] && typeof obj[key] === 'object') { + result[key] = cloneDeep(obj[key]); + } else { + result[key] = obj[key]; + } + }) + return result; + } + class Demo extends Horizon.Component { + render() { + return ( +
+ hello +
+ ); + } + } + + const ele = Horizon.createElement(Demo); + const copy = cloneDeep(ele); + expect(copy.vtype).toEqual(ele.vtype); + expect(Object.getOwnPropertySymbols(copy).length).toEqual(0); + }); +}); +