diff --git a/libs/horizon/src/dom/DOMPropertiesHandler/StyleHandler.ts b/libs/horizon/src/dom/DOMPropertiesHandler/StyleHandler.ts index ae576bfe..49429fbe 100644 --- a/libs/horizon/src/dom/DOMPropertiesHandler/StyleHandler.ts +++ b/libs/horizon/src/dom/DOMPropertiesHandler/StyleHandler.ts @@ -1,3 +1,14 @@ +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 * 对空值或布尔值进行适配,转为空字符串 @@ -38,14 +49,3 @@ export function setStyles(dom, styles) { */ 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')); -} diff --git a/libs/horizon/src/dom/DOMPropertiesHandler/UpdateCommonProp.ts b/libs/horizon/src/dom/DOMPropertiesHandler/UpdateCommonProp.ts index a2fe3e9d..5af59913 100644 --- a/libs/horizon/src/dom/DOMPropertiesHandler/UpdateCommonProp.ts +++ b/libs/horizon/src/dom/DOMPropertiesHandler/UpdateCommonProp.ts @@ -21,7 +21,6 @@ const svgHumpAttr = new Set(['allowReorder', 'autoReverse', 'baseFrequency', 'ba // 驼峰 变 “-” function convertToLowerCase(str) { const replacer = (match, char) => `-${char.toLowerCase()}`; - return str.replace(/([A-Z])/g, replacer); } diff --git a/libs/horizon/src/dom/SelectionRangeHandler.ts b/libs/horizon/src/dom/SelectionRangeHandler.ts index 62931335..284faa41 100644 --- a/libs/horizon/src/dom/SelectionRangeHandler.ts +++ b/libs/horizon/src/dom/SelectionRangeHandler.ts @@ -107,9 +107,9 @@ export function getSelectionInfo() { export interface SelectionData { focusedDom: HTMLInputElement | HTMLTextAreaElement | void; - selectionRange: { - start: number | null; - end: number | null; + selectionRange?: { + start: number; + end: number; }; } diff --git a/libs/horizon/src/dom/utils/Interface.ts b/libs/horizon/src/dom/utils/Interface.ts index b4805619..2306d4b3 100644 --- a/libs/horizon/src/dom/utils/Interface.ts +++ b/libs/horizon/src/dom/utils/Interface.ts @@ -3,7 +3,7 @@ export interface IProperty { } export interface HorizonSelect extends HTMLSelectElement { - _multiple: boolean; + _multiple?: boolean; } export type HorizonDom = Element | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; diff --git a/libs/horizon/src/external/ChildrenUtil.ts b/libs/horizon/src/external/ChildrenUtil.ts index c5eff12e..d1a928fb 100644 --- a/libs/horizon/src/external/ChildrenUtil.ts +++ b/libs/horizon/src/external/ChildrenUtil.ts @@ -12,6 +12,44 @@ function getItemKey(item: any, index: number): string { 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_COMMON_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. ' + ); + // No Default + } +} + function processArrayChildren( children: any, arr: Array, @@ -61,44 +99,6 @@ function callMapFun( } } -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_COMMON_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. ' - ); - // No Default - } -} - // 在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg function mapChildren( children: any, diff --git a/libs/horizon/src/external/JSXElement.ts b/libs/horizon/src/external/JSXElement.ts index f0218ac9..9a06a957 100644 --- a/libs/horizon/src/external/JSXElement.ts +++ b/libs/horizon/src/external/JSXElement.ts @@ -41,7 +41,7 @@ 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} : {}; + const props = isClone ? { ...type.props } : {}; let vNode = isClone ? type.belongClassVNode : getProcessingClassVNode(); if (setting != null) { diff --git a/libs/horizon/src/renderer/ErrorHandler.ts b/libs/horizon/src/renderer/ErrorHandler.ts index f4e66484..c6946c69 100644 --- a/libs/horizon/src/renderer/ErrorHandler.ts +++ b/libs/horizon/src/renderer/ErrorHandler.ts @@ -13,6 +13,55 @@ import {setRootThrowError} from './submit/Submit'; import {handleSuspenseChildThrowError} from './render/SuspenseComponent'; import {updateShouldUpdateOfTree} from './vnode/VNodeShouldUpdate'; +function consoleError(error: any): void { + if (isDev) { + // 只打印message为了让测试用例能pass + console['error']('The codes throw the error: ' + error.message); + } else { + console['error'](error); + } +} + +function handleRootError( + error: any, +) { + // 注意:如果根节点抛出错误,不会销毁整棵树,只打印日志,抛出异常。 + setRootThrowError(error); + consoleError(error); +} + +function createClassErrorUpdate( + vNode: VNode, + error: any, +): Update { + const update = newUpdate(); + update.type = UpdateState.Error; + + const getDerivedStateFromError = vNode.type.getDerivedStateFromError; + if (typeof getDerivedStateFromError === 'function') { + update.content = () => { + consoleError(error); + return getDerivedStateFromError(error); + }; + } + + const inst = vNode.realNode; + if (inst !== null && typeof inst.componentDidCatch === 'function') { + update.callback = function callback() { + if (typeof getDerivedStateFromError !== 'function') { + // 打印错误 + consoleError(error); + } + + // @ts-ignore + this.componentDidCatch(error, { + componentStack: '', + }); + }; + } + return update; +} + // 处理capture和bubble阶段抛出的错误 export function handleRenderThrowError( sourceVNode: VNode, @@ -76,6 +125,19 @@ export function handleRenderThrowError( } while (vNode !== null); } +// 新增一个update,并且触发调度 +function triggerUpdate(vNode, state) { + const update = newUpdate(); + update.content = state; + pushUpdate(vNode, update); + + const root = updateShouldUpdateOfTree(vNode); + if (root !== null) { + tryRenderRoot(root); + } +} + + // 处理submit阶段的异常 export function handleSubmitError(vNode: VNode, error: any) { if (vNode.tag === TreeRoot) { @@ -126,64 +188,3 @@ export function handleSubmitError(vNode: VNode, error: any) { node = node.parent; } } - -function createClassErrorUpdate( - vNode: VNode, - error: any, -): Update { - const update = newUpdate(); - update.type = UpdateState.Error; - - const getDerivedStateFromError = vNode.type.getDerivedStateFromError; - if (typeof getDerivedStateFromError === 'function') { - update.content = () => { - consoleError(error); - return getDerivedStateFromError(error); - }; - } - - const inst = vNode.realNode; - if (inst !== null && typeof inst.componentDidCatch === 'function') { - update.callback = function callback() { - if (typeof getDerivedStateFromError !== 'function') { - // 打印错误 - consoleError(error); - } - - // @ts-ignore - this.componentDidCatch(error, { - componentStack: '', - }); - }; - } - return update; -} - -// 新增一个update,并且触发调度 -function triggerUpdate(vNode, state) { - const update = newUpdate(); - update.content = state; - pushUpdate(vNode, update); - - const root = updateShouldUpdateOfTree(vNode); - if (root !== null) { - tryRenderRoot(root); - } -} - -function handleRootError( - error: any, -) { - // 注意:如果根节点抛出错误,不会销毁整棵树,只打印日志,抛出异常。 - setRootThrowError(error); - consoleError(error); -} - -function consoleError(error: any): void { - if (isDev) { - // 只打印message为了让测试用例能pass - console['error']('The codes throw the error: ' + error.message); - } else { - console['error'](error); - } -} diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts index 7d6201a2..35caaba6 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -129,6 +129,27 @@ function handleError(root, error): void { bubbleVNode(processing); } +// 判断数组中节点的path的idx元素是否都相等 +function isEqualByIndex(idx: number, nodes: Array) { + let val = nodes[0].path[idx]; + for (let i = 1; i < nodes.length; i++) { + let node = nodes[i]; + if (val !== node.path[idx]) { + return false; + } + } + + return true; +} + +function getChildByIndex(vNode: VNode, idx: number) { + let node = vNode.child; + for (let i = 0; i < idx; i++) { + node = node.next; + } + return node; +} + // 从多个更新节点中,计算出开始节点。即:找到最近的共同的父辈节点 export function calcStartUpdateVNode(treeRoot: VNode) { const toUpdateNodes = Array.from(treeRoot.toUpdateNodes); @@ -292,27 +313,6 @@ export function launchUpdateFromVNode(vNode: VNode) { } } -// 判断数组中节点的path的idx元素是否都相等 -function isEqualByIndex(idx: number, nodes: Array) { - let val = nodes[0].path[idx]; - for (let i = 1; i < nodes.length; i++) { - let node = nodes[i]; - if (val !== node.path[idx]) { - return false; - } - } - - return true; -} - -function getChildByIndex(vNode: VNode, idx: number) { - let node = vNode.child; - for (let i = 0; i < idx; i++) { - node = node.next; - } - return node; -} - export function setBuildResultError() { if (getBuildResult() !== BuildCompleted) { setBuildResult(BuildErrored); diff --git a/libs/horizon/src/renderer/diff/DiffTools.ts b/libs/horizon/src/renderer/diff/DiffTools.ts index 0b14f611..9b152d92 100644 --- a/libs/horizon/src/renderer/diff/DiffTools.ts +++ b/libs/horizon/src/renderer/diff/DiffTools.ts @@ -2,9 +2,8 @@ import type { VNode, JSXElement } from '../Types'; // 当前vNode和element是同样的类型 // LazyComponent 会修改type的类型,所以特殊处理这种类型 -export const isSameType = (vNode: VNode, ele: JSXElement) => { - return vNode.type === ele.type || (vNode.isLazyComponent && vNode.lazyType === ele.type); -}; +export const isSameType = (vNode: VNode, ele: JSXElement) => + vNode.type === ele.type || (vNode.isLazyComponent && vNode.lazyType === ele.type); export function isTextType(newChild: any) { return typeof newChild === 'string' || typeof newChild === 'number'; diff --git a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts index b1f1203b..09624161 100644 --- a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts +++ b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts @@ -17,7 +17,7 @@ enum DiffCategory { TEXT_NODE = 'TEXT_NODE', OBJECT_NODE = 'OBJECT_NODE', ARR_NODE = 'ARR_NODE', -}; +} // 检查是不是被 FRAGMENT 包裹 function isNoKeyFragment(child: any) { @@ -31,7 +31,7 @@ function deleteVNode(parentNode: VNode, delVNode: VNode): void { } // 清除多个节点 -function deleteVNodes(parentVNode: VNode, startDelVNode: VNode, endVNode?: VNode): void { +function deleteVNodes(parentVNode: VNode, startDelVNode: VNode | null, endVNode?: VNode): void { let node = startDelVNode; while (node !== null) { @@ -67,7 +67,7 @@ function checkCanReuseNode(oldNode: VNode | null, newChild: any): boolean { return false; } -function getNodeType(newChild: any): string { +function getNodeType(newChild: any): string | null { if (newChild === null) { return null; } @@ -186,9 +186,7 @@ function transLeftChildrenToMap( travelChildren(startChild, (node) => { leftChildrenMap.set(node.key !== null ? node.key : node.eIndex, node); - }, (node) => { - return node === rightEndVNode; - }); + }, node => node === rightEndVNode); return leftChildrenMap; } @@ -208,6 +206,11 @@ function getOldNodeFromMap(nodeMap: Map, newIdx: number, return null; } +function setCIndex(vNode: VNode, idx: number) { + vNode.cIndex = idx; + updateVNodePath(vNode); +} + // diff数组类型的节点,核心算法 function diffArrayNodes( parentNode: VNode, @@ -277,7 +280,7 @@ function diffArrayNodes( // 从后往前,新资源的位置还没有到最末端,旧的vNode也还没遍历完,则可以考虑从后往前开始 if (rightIdx > leftIdx && oldNode !== null) { const rightRemainingOldChildren = transRightChildrenToArray(oldNode); - let rightOldIndex = rightRemainingOldChildren.length - 1; + let rightOldIndex: number | null = rightRemainingOldChildren.length - 1; // 2. 从右侧开始比对currentVNode和newChildren,若不能复用则跳出循环 for (; rightIdx > leftIdx; rightIdx--) { @@ -377,7 +380,7 @@ function diffArrayNodes( if (oldNodeFromMap !== null) { let eIndex = newNode.eIndex; eIndexes.push(eIndex); - const last = eIndexes[result[result.length - 1]]; + const last: number | undefined = eIndexes[result[result.length - 1]]; if (eIndex > last || last === undefined) { // 大的 eIndex直接放在最后 preIndex[i] = result[result.length - 1]; result.push(i); @@ -455,11 +458,6 @@ function setVNodesCIndex(startChild: VNode, startIdx: number) { } } -function setCIndex(vNode: VNode, idx: number) { - vNode.cIndex = idx; - updateVNodePath(vNode); -} - // 新节点是数组类型 function diffArrayNodesHandler( parentNode: VNode, diff --git a/libs/horizon/src/renderer/hooks/UseReducerHook.ts b/libs/horizon/src/renderer/hooks/UseReducerHook.ts index 58fbb7f6..52399713 100644 --- a/libs/horizon/src/renderer/hooks/UseReducerHook.ts +++ b/libs/horizon/src/renderer/hooks/UseReducerHook.ts @@ -14,7 +14,7 @@ import { getHookStage, HookStage } from './HookStage'; import type { VNode } from '../Types'; export function useReducerImpl(reducer: (S, A) => - S, initArg: P, init?: (P) => S, isUseState?: boolean): [S, Trigger] { + S, initArg: P, init?: (P) => S, isUseState?: boolean): [S, Trigger] | void { const stage = getHookStage(); if (stage === null) { throwNotInFuncError(); diff --git a/libs/horizon/src/renderer/render/Fragment.ts b/libs/horizon/src/renderer/render/Fragment.ts index a64c2532..27c2b5c1 100644 --- a/libs/horizon/src/renderer/render/Fragment.ts +++ b/libs/horizon/src/renderer/render/Fragment.ts @@ -1,10 +1,6 @@ import type {VNode} from '../Types'; import {createVNodeChildren} from './BaseComponent'; -export function captureRender(processing: VNode): VNode | null { - return captureFragment(processing); -} - export function bubbleRender() {} function captureFragment(processing: VNode) { @@ -12,3 +8,7 @@ function captureFragment(processing: VNode) { processing.child = createVNodeChildren(processing, newElement); return processing.child; } + +export function captureRender(processing: VNode): VNode | null { + return captureFragment(processing); +} diff --git a/libs/horizon/src/renderer/render/MemoComponent.ts b/libs/horizon/src/renderer/render/MemoComponent.ts index fd273249..d4a10d22 100644 --- a/libs/horizon/src/renderer/render/MemoComponent.ts +++ b/libs/horizon/src/renderer/render/MemoComponent.ts @@ -10,10 +10,6 @@ import { } from '../../external/JSXElementType'; import {Fragment} from '../vnode/VNodeTags'; -export function captureRender(processing: VNode, shouldUpdate: boolean): VNode | null { - return captureMemoComponent(processing, shouldUpdate); -} - export function bubbleRender() {} export function captureMemoComponent( @@ -59,3 +55,7 @@ export function captureMemoComponent( return newChild; } + +export function captureRender(processing: VNode, shouldUpdate: boolean): VNode | null { + return captureMemoComponent(processing, shouldUpdate); +} diff --git a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts index 68503458..9dafedd5 100644 --- a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts +++ b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts @@ -225,10 +225,10 @@ function unmountVNode(vNode: VNode): void { function unmountNestedVNodes(vNode: VNode): void { travelVNodeTree(vNode, (node) => { unmountVNode(node); - }, (node) => { + }, node => // 如果是DomPortal,不需要遍历child - return node.tag === DomPortal; - }); + node.tag === DomPortal + ); } function submitAddition(vNode: VNode): void { @@ -296,10 +296,9 @@ function unmountDomComponents(vNode: VNode): void { } else { unmountVNode(node); } - }, (node) => { + }, node => // 如果是dom不用再遍历child - return node.tag === DomComponent || node.tag === DomText; - }, null, (node) => { + node.tag === DomComponent || node.tag === DomText, null, (node) => { if (node.tag === DomPortal) { // 当离开portal,需要重新设置parent currentParentIsValid = false; diff --git a/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts b/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts index 48b3fc72..31760489 100644 --- a/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts +++ b/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts @@ -12,6 +12,16 @@ let callingQueueTask: any | null = null; // 防止重入 let isCallingRenderQueue = false; +export function callRenderQueueImmediate() { + if (callingQueueTask !== null) { + // 取消异步调度 + cancelTask(callingQueueTask); + callingQueueTask = null; + } + + callRenderQueue(); +} + // 执行render回调 function callRenderQueue() { if (!isCallingRenderQueue && renderQueue !== null) { @@ -54,13 +64,3 @@ export function pushRenderCallback(callback: RenderCallback) { // 返回一个空对象,用于区别null return {}; } - -export function callRenderQueueImmediate() { - if (callingQueueTask !== null) { - // 取消异步调度 - cancelTask(callingQueueTask); - callingQueueTask = null; - } - - callRenderQueue(); -} diff --git a/libs/horizon/src/renderer/utils/compare.ts b/libs/horizon/src/renderer/utils/compare.ts index 3849ec85..e6fb69a1 100644 --- a/libs/horizon/src/renderer/utils/compare.ts +++ b/libs/horizon/src/renderer/utils/compare.ts @@ -42,9 +42,9 @@ export function shallowCompare(paramX: any, paramY: any): boolean { return false; } - return keysX.every((key, i) => { - return Object.prototype.hasOwnProperty.call(paramY, key) && isSame(paramX[key], paramY[keysX[i]]); - }); + return keysX.every((key, i) => + Object.prototype.hasOwnProperty.call(paramY, key) && isSame(paramX[key], paramY[keysX[i]]) + ); } return false;