From 19a39513e695a3ab94c87529c89cdd789b93ed9d Mon Sep 17 00:00:00 2001 From: * <8> Date: Sat, 25 Dec 2021 11:52:31 +0800 Subject: [PATCH] Match-id-34cc8d2e71fcba103088bb87b33f8df28ae8c31d --- libs/horizon/src/dom/DOMExternal.ts | 12 +- libs/horizon/src/dom/DOMInternalKeys.ts | 27 +-- libs/horizon/src/dom/DOMOperator.ts | 170 +++++------------- .../DOMPropertiesHandler.ts | 2 +- .../dom/DOMPropertiesHandler/StyleHandler.ts | 43 +++-- .../DOMPropertiesHandler/UpdateCommonProp.ts | 18 +- libs/horizon/src/dom/utils/Common.ts | 27 +-- libs/horizon/src/dom/utils/DomCreator.ts | 2 - .../src/dom/validators/ValidateProps.ts | 2 +- .../src/dom/valueHandler/InputValueHandler.ts | 16 +- .../dom/valueHandler/TextareaValueHandler.ts | 6 +- .../{ValueHandler.ts => index.ts} | 0 libs/horizon/src/external/ChildrenUtil.ts | 150 ++++++++++++++++ libs/horizon/src/external/Horizon.ts | 60 +++++++ libs/horizon/src/external/HorizonElement.ts | 82 +++++++++ 15 files changed, 411 insertions(+), 206 deletions(-) rename libs/horizon/src/dom/valueHandler/{ValueHandler.ts => index.ts} (100%) create mode 100644 libs/horizon/src/external/ChildrenUtil.ts create mode 100644 libs/horizon/src/external/Horizon.ts create mode 100644 libs/horizon/src/external/HorizonElement.ts diff --git a/libs/horizon/src/dom/DOMExternal.ts b/libs/horizon/src/dom/DOMExternal.ts index 37d9dd79..237e8de2 100644 --- a/libs/horizon/src/dom/DOMExternal.ts +++ b/libs/horizon/src/dom/DOMExternal.ts @@ -3,19 +3,17 @@ import { syncUpdates, startUpdate, } from '../renderer/Renderer'; import {createPortal} from '../renderer/components/CreatePortal'; -import { - clearContainer, saveContainer, -} from './DOMInternalKeys'; import type {Container} from './DOMOperator'; import {isElement} from './utils/Common'; import {listenDelegatedEvents} from '../event/EventBinding'; import {findDOMByClassInst} from '../renderer/vnode/VNodeUtils'; import {TreeRoot} from '../renderer/vnode/VNodeTags'; +import {Callback} from '../renderer/UpdateHandler'; function executeRender( children: any, container: Container, - callback?: Function, + callback?: Callback, ) { let treeRoot = container._treeRoot; @@ -36,7 +34,7 @@ function executeRender( return getFirstCustomDom(treeRoot); } -function createRoot(children: any, container: Container, callback?: Function) { +function createRoot(children: any, container: Container, callback?: Callback) { // 清空容器 let child = container.lastChild; while (child) { @@ -46,11 +44,10 @@ function createRoot(children: any, container: Container, callback?: Function) { // 调度器创建根节点,并给容器dom赋vNode结构体 const treeRoot = createVNode(TreeRoot, container); - saveContainer(container, treeRoot._domRoot); container._treeRoot = treeRoot; // 根节点挂接全量事件 - listenDelegatedEvents(container); + listenDelegatedEvents(container as Element); // 执行回调 if (typeof callback === 'function') { @@ -89,7 +86,6 @@ function destroy(container: Container) { syncUpdates(() => { executeRender(null, container, () => { container._treeRoot = null; - clearContainer(container); }); }); diff --git a/libs/horizon/src/dom/DOMInternalKeys.ts b/libs/horizon/src/dom/DOMInternalKeys.ts index 445db94c..3b59dd2d 100644 --- a/libs/horizon/src/dom/DOMInternalKeys.ts +++ b/libs/horizon/src/dom/DOMInternalKeys.ts @@ -11,7 +11,7 @@ import type { import { DomComponent, DomText, - DomRoot, + TreeRoot, } from '../renderer/vnode/VNodeTags'; const suffixKey = new Date().getTime().toString(); @@ -20,7 +20,6 @@ const prefix = '_horizon'; const internalKeys = { VNode: `${prefix}VNode@${suffixKey}`, props: `${prefix}Props@${suffixKey}`, - container: `${prefix}Container@${suffixKey}`, events: `${prefix}Events@${suffixKey}`, nonDelegatedEvents: `${prefix}NonDelegatedEvents@${suffixKey}`, }; @@ -42,33 +41,33 @@ export function saveVNode( } // 用 DOM 节点,来找其对应的 VNode 实例 -export function getVNode(dom: Node): VNode | null { - const vNode = dom[internalKeys.VNode] || dom[internalKeys.container]; +export function getVNode(dom: Node|Container): VNode | null { + const vNode = dom[internalKeys.VNode] || (dom as Container)._treeRoot; if (vNode) { const {tag} = vNode; - if (tag === DomComponent || tag === DomText || tag === DomRoot) { + if (tag === DomComponent || tag === DomText || tag === TreeRoot) { return vNode; } } return null; } -// 用 DOM 对象,来寻找其对应或者说是最近的 VNode 实例 +// 用 DOM 对象,来寻找其对应或者说是最近父级的 vNode export function getNearestVNode(dom: Node): null | VNode { let vNode = dom[internalKeys.VNode]; if (vNode) { // 如果是已经被框架标记过的 DOM 节点,那么直接返回其 VNode 实例 return vNode; } // 下面处理的是为被框架标记过的 DOM 节点,向上找其父节点是否被框架标记过 - let parent = dom.parentNode; + let parentDom = dom.parentNode; let nearVNode = null; - while (parent) { - vNode = parent[internalKeys.VNode]; + while (parentDom) { + vNode = parentDom[internalKeys.VNode]; if (vNode) { nearVNode = vNode; break; } - parent = parent.parentNode; + parentDom = parentDom.parentNode; } return nearVNode; } @@ -99,11 +98,3 @@ export function getEventToListenerMap(target: EventTarget): Map { +): Array { // 校验两个对象的不同 validateProps(type, nextRawProps); @@ -177,15 +129,11 @@ export function getPropChangeList( } export function isTextChild(type: string, props: Props): boolean { - if (type === 'textarea') { + const typeArray = ['textarea', 'option', 'noscript']; + const typeOfPropsChild = ['string', 'number']; + if (typeArray.indexOf(type) >= 0) { return true; - } else if (type === 'option') { - return true; - } else if (type === 'noscript') { - return true; - } else if (typeof props.children === 'string') { - return true; - } else if (typeof props.children === 'number') { + } else if (typeOfPropsChild.indexOf(typeof props.children) >= 0) { return true; } else { return ( @@ -205,19 +153,8 @@ export function newTextDom( return textNode; } -export function submitMount( - dom: Element, - type: string, - newProps: Props, -): void { - if (shouldAutoFocus(type, newProps)) { - // button、input、select、textarea、如果有 autoFocus 属性需要focus - dom.focus(); - } -} - // 提交vNode的类型为Component或者Text的更新 -export function submitDomUpdate(tag: number, vNode: VNode) { +export function submitDomUpdate(tag: string, vNode: VNode) { const newProps = vNode.props; const element: Element = vNode.realNode; @@ -251,71 +188,44 @@ export function clearText(dom: Element): void { } // 添加child元素 -export function appendChildElement(isContainer: boolean, +export function appendChildElement( parent: Element | Container, - child: Element | Text): void { - if (isContainer && isComment(parent)) { - parent.parentNode.insertBefore(child, parent); - } else { - parent.appendChild(child); - } + child: Element | Text +): void { + parent.appendChild(child); } // 插入dom元素 export function insertDomBefore( - isContainer: boolean, parent: Element | Container, child: Element | Text, beforeChild: Element | Text, ) { - if (isContainer && isComment(parent)) { - parent.parentNode.insertBefore(child, beforeChild); - } else { - parent.insertBefore(child, beforeChild); - } + parent.insertBefore(child, beforeChild); } export function removeChildDom( - isContainer: boolean, parent: Element | Container, child: Element | Text ) { - if (isContainer && isComment(parent)) { - parent.parentNode.removeChild(child); - } else { - parent.removeChild(child); - } + parent.removeChild(child); } // 隐藏元素 -export function hideDom(tag: number, element: Element | Text) { +export function hideDom(tag: string, dom: Element | Text) { if (tag === DomComponent) { - // DomComponent类型 - const {style} = element; - if (style.setProperty && typeof style.setProperty === 'function') { - style.setProperty('display', 'none', 'important'); - } else { - style.display = 'none'; - } + dom.style.display = 'none'; } else if (tag === DomText) { - // text类型 - element.textContent = ''; + dom.textContent = ''; } } // 不隐藏元素 -export function unHideDom(tag: number, element: Element | Text, props: Props) { +export function unHideDom(tag: string, dom: Element | Text, props: Props) { if (tag === DomComponent) { - // DomComponent类型 - const style = props.style; - let display = null; - if (style !== undefined && style !== null && style.hasOwnProperty('display')) { - display = style.display; - } - element.style.display = adjustStyleValue('display', display); + dom.style.display = adjustStyleValue('display', props?.style?.display ?? ''); } else if (tag === DomText) { - // text类型 - element.textContent = props; + dom.textContent = props; } } diff --git a/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts b/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts index b0c1352e..156f9710 100644 --- a/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts +++ b/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts @@ -63,7 +63,7 @@ function updateOneProp(dom, propName, propVal, isNativeTag, isInit = false) { export function compareProps( oldProps: Object, newProps: Object, -): null | Array { +): Array { let updatesForStyle = {}; const toBeDeletedProps = []; const toBeUpdatedProps = []; diff --git a/libs/horizon/src/dom/DOMPropertiesHandler/StyleHandler.ts b/libs/horizon/src/dom/DOMPropertiesHandler/StyleHandler.ts index 48e75c46..23e1b91c 100644 --- a/libs/horizon/src/dom/DOMPropertiesHandler/StyleHandler.ts +++ b/libs/horizon/src/dom/DOMPropertiesHandler/StyleHandler.ts @@ -7,29 +7,44 @@ export function setStyles(dom, styles) { } const style = dom.style; - const styleKeys = Object.keys(styles); + Object.keys(styles).forEach((name) => { + const styleVal = styles[name]; - for (let i = 0; i < styleKeys.length; i++) { - const styleKey = styleKeys[i]; - const styleVal = styles[styleKey]; + const validStyleValue = adjustStyleValue(name, styleVal); - const validStyleValue = adjustStyleValue(styleKey, styleVal); - - style[styleKey] = validStyleValue; - } + style[name] = validStyleValue; + }); } /** - * 1. 对空值或布尔值进行适配,转为空字符串 - * 2. 去掉多余空字符 + * 不需要加长度单位的 css 属性 + */ +const noUnitCSS = ['animationIterationCount', 'columnCount', 'columns', 'gridArea', 'fontWeight', 'lineClamp', + 'lineHeight', 'opacity', 'order', 'orphans', 'tabSize', 'widows', 'zIndex', 'zoom']; + +function isNeedUnitCSS(propName: string) { + return !(noUnitCSS.includes(propName) + || propName.startsWith('borderImage') + || propName.startsWith('flex') + || propName.startsWith('gridRow') + || propName.startsWith('gridColumn') + || propName.startsWith('stroke') + || propName.startsWith('box') + || propName.endsWith('Opacity')); +} + +/** + * 对一些没有写单位的样式进行适配,例如:width: 10 => width: 10px + * 对空值或布尔值进行适配,转为空字符串 + * 去掉多余空字符 */ export function adjustStyleValue(name, value) { - let validValue; + let validValue = value; - if (value === '' || value == null || typeof value === 'boolean') { + if (typeof value === 'number' && value !== 0 && isNeedUnitCSS(name)) { + validValue = `${value}px`; + } else if (value === '' || value == null || typeof value === 'boolean') { validValue = ''; - } else { - validValue = String(value).trim(); } return validValue; diff --git a/libs/horizon/src/dom/DOMPropertiesHandler/UpdateCommonProp.ts b/libs/horizon/src/dom/DOMPropertiesHandler/UpdateCommonProp.ts index 6c2a940a..e9a6fce1 100644 --- a/libs/horizon/src/dom/DOMPropertiesHandler/UpdateCommonProp.ts +++ b/libs/horizon/src/dom/DOMPropertiesHandler/UpdateCommonProp.ts @@ -5,6 +5,18 @@ import { import {isInvalidValue} from '../validators/ValidateProps'; import {getNamespaceCtx} from '../../renderer/ContextSaver'; import {NSS} from '../utils/DomCreator'; +import {getDomTag} from '../utils/Common'; + +const svgHumpAttr = new Set(['allowReorder', 'autoReverse', 'baseFrequency', 'baseProfile', 'calcMode', 'clipPathUnits', + 'contentScriptType', 'contentStyleType', 'diffuseConstant', 'edgeMode', 'externalResourcesRequired', 'filterRes', + 'filterUnits', 'glyphRef', 'gradientTransform', 'gradientUnits', 'kernelMatrix', 'kernelUnitLength', 'keyPoints', + 'keySplines', 'keyTimes', 'lengthAdjust', 'limitingConeAngle', 'markerHeight', 'markerUnits', 'markerWidth', + 'maskContentUnits', 'maskUnits', 'numOctaves', 'pathLength', 'patternContentUnits', 'patternTransform,', + 'patternUnits', 'pointsAtX', 'pointsAtY', 'pointsAtZ', 'preserveAlpha', 'preserveAspectRatio', 'primitiveUnits', + 'referrerPolicy', 'refX', 'refY', 'repeatCount', 'repeatDur', 'requiredExtensions', 'requiredFeatures', + 'specularConstant', 'specularExponent', 'spreadMethod', 'startOffset', 'stdDeviation', 'stitchTiles', 'surfaceScale', + 'systemLanguage', 'tableValues', 'targetX', 'targetY', 'textLength', 'viewBox', 'viewTarget', 'xChannelSelector', + 'yChannelSelector', 'zoomAndPan']); /** * 给 dom 设置属性 @@ -20,8 +32,10 @@ export function updateCommonProp(dom: Element, attrName: string, value: any, isN if (!isNativeTag || propDetails === null) { // 特殊处理svg的属性,把驼峰式的属性名称转成'-' - if (dom.tagName.toLowerCase() === 'svg' || getNamespaceCtx() === NSS.svg) { - attrName = convertToLowerCase(attrName); + if (getDomTag(dom) === 'svg' || getNamespaceCtx() === NSS.svg) { + if (!svgHumpAttr.has(attrName)) { + attrName = convertToLowerCase(attrName); + } } if (value === null) { diff --git a/libs/horizon/src/dom/utils/Common.ts b/libs/horizon/src/dom/utils/Common.ts index 771105bb..dd836240 100644 --- a/libs/horizon/src/dom/utils/Common.ts +++ b/libs/horizon/src/dom/utils/Common.ts @@ -1,13 +1,14 @@ import {HorizonDom} from './Interface'; +import {Props} from '../DOMOperator'; /** * 获取当前聚焦的 input 或者 textarea 元素 - * @param currentDoc 指定 document + * @param doc 指定 document */ -export function getFocusedDom(currentDoc?: Document): HorizonDom | void { +export function getFocusedDom(doc?: Document): HorizonDom | void { let currentDocument; - if (currentDoc) { - currentDocument = currentDoc; + if (doc) { + currentDocument = doc; } else { if (document) { currentDocument = document; @@ -63,13 +64,13 @@ export function isDocumentFragment(dom) { return dom.nodeType === 11; } -export function getRootElement(dom: HorizonDom): HorizonDom { - let rootElement = dom; - - while (rootElement.parentNode) { - // @ts-ignore - rootElement = rootElement.parentNode; - } - - return rootElement; +export function getDomTag(dom) { + return dom.nodeName.toLowerCase(); +} + +const types = ['button', 'input', 'select', 'textarea']; + +// button、input、select、textarea、如果有 autoFocus 属性需要focus +export function shouldAutoFocus(tagName: string, props: Props): boolean { + return types.includes(tagName) ? Boolean(props.autoFocus) : false; } diff --git a/libs/horizon/src/dom/utils/DomCreator.ts b/libs/horizon/src/dom/utils/DomCreator.ts index ee3aa387..360aea81 100644 --- a/libs/horizon/src/dom/utils/DomCreator.ts +++ b/libs/horizon/src/dom/utils/DomCreator.ts @@ -8,7 +8,6 @@ export const NSS = { // 创建DOM元素 export function createDom( tagName: string, - props: Object, parentNamespace: string, ): Element { let dom: Element; @@ -20,6 +19,5 @@ export function createDom( } else { dom = document.createElement(tagName); } - return dom; } diff --git a/libs/horizon/src/dom/validators/ValidateProps.ts b/libs/horizon/src/dom/validators/ValidateProps.ts index d04aec3e..dddd8175 100644 --- a/libs/horizon/src/dom/validators/ValidateProps.ts +++ b/libs/horizon/src/dom/validators/ValidateProps.ts @@ -100,7 +100,7 @@ export function validateProps(type, props) { throw new Error('style should be a object.'); } - if (__DEV__) { + if (isDev) { // 校验属性 const invalidProps = Object.keys(props).filter(key => !isValidProp(type, key, props[key])); diff --git a/libs/horizon/src/dom/valueHandler/InputValueHandler.ts b/libs/horizon/src/dom/valueHandler/InputValueHandler.ts index 5632085f..e081a5ea 100644 --- a/libs/horizon/src/dom/valueHandler/InputValueHandler.ts +++ b/libs/horizon/src/dom/valueHandler/InputValueHandler.ts @@ -1,7 +1,6 @@ import {updateCommonProp} from '../DOMPropertiesHandler/UpdateCommonProp'; import {getVNodeProps} from '../DOMInternalKeys'; import {IProperty} from '../utils/Interface'; -import {getRootElement} from '../utils/Common'; import {isInputValueChanged} from './ValueChangeHandler'; function getInitValue(dom: HTMLInputElement, properties: IProperty) { @@ -33,12 +32,12 @@ export function getInputPropsWithoutValue(dom: HTMLInputElement, properties: IPr export function updateInputValue(dom: HTMLInputElement, properties: IProperty) { const {value, checked} = properties; - if (checked != null) { - updateCommonProp(dom, 'checked', checked); - } else if (value != null) { // 处理 dom.value 逻辑 + if (value != null) { // 处理 dom.value 逻辑 if (dom.value !== String(value)) { dom.value = String(value); } + } else if (checked != null) { + updateCommonProp(dom, 'checked', checked); } } @@ -64,17 +63,10 @@ export function resetInputValue(dom: HTMLInputElement, properties: IProperty) { const {name, type} = properties; // 如果是 radio,先更新相同 name 的 radio if (type === 'radio' && name != null) { - // radio 的根节点 - const radioRoot = getRootElement(dom); - - const radioList = radioRoot.querySelectorAll(`input[type="radio"]`); + const radioList = document.querySelectorAll(`input[type="radio"][name="${name}"]`); for (let i = 0; i < radioList.length; i++) { const radio = radioList[i]; - // @ts-ignore - if (radio.name !== name) { - continue; - } if (radio === dom) { continue; } diff --git a/libs/horizon/src/dom/valueHandler/TextareaValueHandler.ts b/libs/horizon/src/dom/valueHandler/TextareaValueHandler.ts index dbb40723..d10e43d5 100644 --- a/libs/horizon/src/dom/valueHandler/TextareaValueHandler.ts +++ b/libs/horizon/src/dom/valueHandler/TextareaValueHandler.ts @@ -12,11 +12,7 @@ function getInitValue(properties: IProperty) { // children content存在时,会覆盖defaultValue if (children != null) { // 子节点不是纯文本,则取第一个子节点 - if (children instanceof Array) { - initValue = children[0]; - } else { - initValue = children; - } + initValue = children instanceof Array ? children[0] : children; } // defaultValue 属性未配置,置为空字符串 diff --git a/libs/horizon/src/dom/valueHandler/ValueHandler.ts b/libs/horizon/src/dom/valueHandler/index.ts similarity index 100% rename from libs/horizon/src/dom/valueHandler/ValueHandler.ts rename to libs/horizon/src/dom/valueHandler/index.ts diff --git a/libs/horizon/src/external/ChildrenUtil.ts b/libs/horizon/src/external/ChildrenUtil.ts new file mode 100644 index 00000000..179cf73e --- /dev/null +++ b/libs/horizon/src/external/ChildrenUtil.ts @@ -0,0 +1,150 @@ +import {throwIfTrue} from '../renderer/utils/throwIfTrue'; +import {TYPE_ELEMENT, TYPE_PORTAL} from '../renderer/utils/elementType'; + +import {isValidElement, HorizonElement} from './HorizonElement'; + +// 生成key +function getItemKey(item: any, index: number): string { + if (typeof item === 'object' && item !== null && item.key != null) { + return '.$' + item.key; + } + // 使用36进制减少生成字符串的长度以节省空间 + return '.' + index.toString(36); +} + +function mapChildrenToArray( + children: any, + arr: Array, + prefix: string, + callback?: Function, +): number | void { + const type = typeof children; + switch (type) { + // 继承原有规格,undefined和boolean类型按照null处理 + case 'undefined': + case 'boolean': + callMapFun(null, arr, prefix, callback); + return; + case 'number': + case 'string': + callMapFun(children, arr, prefix, callback); + return; + case 'object': + if (children === null) { + callMapFun(null, arr, prefix, callback); + return; + } + const vtype = children.vtype; + if (vtype === TYPE_ELEMENT || vtype === TYPE_PORTAL) { + callMapFun(children, arr, prefix, callback) ; + return; + } + if (Array.isArray(children)) { + processArrayChildren(children, arr, prefix, callback); + return; + } + throw new Error( + 'Object is invalid as a Horizon child. ' + ); + default: + return; + } +} + +function processArrayChildren( + children: any, + arr: Array, + prefix: string, + callback: Function, +) { + for (let i = 0; i < children.length; i++) { + const childItem = children[i]; + const nextPrefix = prefix + getItemKey(childItem, i); + mapChildrenToArray( + childItem, + arr, + nextPrefix, + callback, + ); + } +} + +function callMapFun( + children: any, + arr: Array, + prefix: string, + callback: Function, +) { + let mappedChild = callback(children); + if (Array.isArray(mappedChild)) { + // 维持原有规格,如果callback返回结果是数组,处理函数修改为返回数组item + processArrayChildren(mappedChild, arr, prefix + '/', subChild => subChild); + } else if (mappedChild !== null && mappedChild !== undefined) { + // 给一个key值,确保返回的对象一定带有key + if (isValidElement(mappedChild)) { + const childKey = prefix === '' ? getItemKey(children, 0) : ''; + const mappedKey = getItemKey(mappedChild, 0); + const newKey = prefix + childKey + (mappedChild.key && mappedKey !== getItemKey(children, 0) + ? '.$' + mappedChild.key + : ''); + // 返回一个修改key的children + mappedChild = HorizonElement( + mappedChild.type, + newKey, + mappedChild.ref, + mappedChild._vNode, + mappedChild.props, + ); + } + arr.push(mappedChild); + } +} + +// 在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg +function mapChildren( + children: any, + func: Function, + context?: any, +): Array { + if (children === null || children === undefined) { + return children; + } + let count = 0; + const result = []; + mapChildrenToArray(children, result, '', (child) => { + return func.call(context, child, count++); + }); + return result; +} + +const Children = { + forEach: (children, func, context?: any) => { + // 不返回数组即可 + mapChildren(children, func, context); + }, + map: mapChildren, + // 并非所有元素都会计数,只计数调用callMapFun函数次数 + count: (children) => { + let n = 0; + mapChildren(children, () => { + n++; + }); + return n; + }, + only: (children) => { + throwIfTrue( + !isValidElement(children), + 'Horizon.Children.only function received invalid element.' + ); + return children; + }, + toArray: (children) => { + const result = []; + mapChildrenToArray(children, result, '', child => child); + return result; + }, +} + +export { + Children +}; diff --git a/libs/horizon/src/external/Horizon.ts b/libs/horizon/src/external/Horizon.ts new file mode 100644 index 00000000..8c933c80 --- /dev/null +++ b/libs/horizon/src/external/Horizon.ts @@ -0,0 +1,60 @@ +import { + TYPE_FRAGMENT, + TYPE_PROFILER, + TYPE_STRICT_MODE, + TYPE_SUSPENSE, +} from '../renderer/utils/elementType'; + +import {Component, PureComponent} from '../renderer/components/BaseClassComponent'; +import {createRef} from '../renderer/components/CreateRef'; +import {Children} from './ChildrenUtil'; +import { + createElement, + cloneElement, + isValidElement, +} from './HorizonElement'; +import {createContext} from '../renderer/components/context/CreateContext'; +import {lazy} from '../renderer/components/Lazy'; +import {forwardRef} from '../renderer/components/ForwardRef'; +import {memo} from '../renderer/components/Memo'; +import hookMapping from '../renderer/hooks/HookMapping'; + +import { + useCallback, + useContext, + useEffect, + useImperativeHandle, + useLayoutEffect, + useMemo, + useReducer, + useRef, + useState, +} from '../renderer/hooks/HookExternal'; + +export { + Children, + createRef, + Component, + PureComponent, + createContext, + forwardRef, + lazy, + memo, + useCallback, + useContext, + useEffect, + useImperativeHandle, + useLayoutEffect, + useMemo, + useReducer, + useRef, + useState, + TYPE_FRAGMENT as Fragment, + TYPE_PROFILER as Profiler, + TYPE_STRICT_MODE as StrictMode, + TYPE_SUSPENSE as Suspense, + createElement, + cloneElement, + isValidElement, + hookMapping, +}; diff --git a/libs/horizon/src/external/HorizonElement.ts b/libs/horizon/src/external/HorizonElement.ts new file mode 100644 index 00000000..186f343c --- /dev/null +++ b/libs/horizon/src/external/HorizonElement.ts @@ -0,0 +1,82 @@ +import { TYPE_ELEMENT } from '../renderer/utils/elementType'; +import ProcessingVNode from '../renderer/vnode/ProcessingVNode'; + + +/** + * vtype, 节点的类型,这里固定是element + * type,保存dom节点的名称或者组件的函数地址 + * key key属性 + * ref ref属性 + * props 其他常规属性 + */ +export function HorizonElement(type, key, ref, vNode, props) { + return { + // Horizon元素标识符 + vtype: TYPE_ELEMENT, + + // 属于元素的内置属性 + type: type, + key: key, + ref: ref, + props: props, + + // 记录负责创建此元素的组件。 + _vNode: vNode, + }; +}; + +function isValidKey(key) { + return key !== 'key' && key !== 'ref' && key !== '__source'; +} + +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 props = isClone ? {...type.props} : {}; + let vNode = isClone ? type._vNode : ProcessingVNode.val; + + if (setting != null) { + Object.keys(setting).forEach(k => { + if (isValidKey(k)) { + props[k] = setting[k]; + } + }); + if (setting.ref !== undefined && isClone) { + vNode = ProcessingVNode.val; + } + } + + if (children.length) { + props.children = children.length === 1 ? children[0] : children; + } + const element = isClone ? type.type : type; + //合并默认属性 + if (element && element.defaultProps) { + mergeDefault(props, element.defaultProps); + } + + return HorizonElement(element, key, ref, vNode, props); +} + +//创建Element结构体,供JSX编译时调用 +export function createElement(type, setting, ...children) { + return buildElement(false, type, setting, ...children); +} + +function mergeDefault(sourceObj, defaultObj) { + Object.keys(defaultObj).forEach((key) => { + if (sourceObj[key] === undefined) { + sourceObj[key] = defaultObj[key]; + } + }); +} + +export function cloneElement(element, setting, ...children) { + return buildElement(true, element, setting, ...children); +} + +// 检测结构体是否为合法的Element +export function isValidElement(element) { + return !!(element && element.vtype === TYPE_ELEMENT); +}