diff --git a/.cloudbuild/build.yml b/.cloudbuild/build.yml new file mode 100644 index 00000000..b6fcaac9 --- /dev/null +++ b/.cloudbuild/build.yml @@ -0,0 +1,37 @@ +version: '2.0' +notifications: + notifier: + espace: + 'on': false + email: + 'on': false +buildspace: + log_collect: + - config/CI/build/logs + fixed: true +env: + resource: + type: docker + image: kweecr04.his.huawei.com:80/ecr-build-arm-gzkunpeng/euleros_v2r7spc522_x64_opmt_cs5.0_sz:v5.0 + class: 4U8G + mode: toolbox + cache: + - type: workspace +steps: + PRE_BUILD: + - checkout: + path: horizon-core + - gitlab: + url: https://szv-y.codehub.huawei.com/CloudSOP/CloudSOP-CI.git + branch: $branch + path: CI + BUILD: + - build_execute: + command: | + npm install yarn -g + yarn config set strict-ssl false + cd horizon-core + yarn + yarn run test + yarn run build + node .cloudbuild/release.js diff --git a/.cloudbuild/release.js b/.cloudbuild/release.js new file mode 100644 index 00000000..be212829 --- /dev/null +++ b/.cloudbuild/release.js @@ -0,0 +1,60 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved. + */ + +const fs = require('fs'); +const path = require('path'); +const childProcess = require('child_process'); + +const version = process.env.releaseVersion; +const DIST_PATH = path.resolve(__dirname, '../build/horizon'); + +const NPMRC = `registry=https://cmc.centralrepo.rnd.huawei.com/npm +@cloudsop:registry=https://cmc.centralrepo.rnd.huawei.com/artifactory/product_npm +_auth = Y2xvdWRzb3BhcnRpZmFjdG9yeTpDbG91ZHNvcDY2NiEhIQ +always-auth = true +email = cloudsop@huawei.com +`; +if (!version) { + process.exit(); +} +if (!/\d+\.\d+\.\d+/.test(version)) { + console.log('请输入正确版本号'); + process.exit(); +} + +const exec = (cmd, cwd) => { + return new Promise((resolve, reject) => { + childProcess.exec( + cmd, + { + cwd, + }, + function (error, stdout, stderr) { + if (error) { + error && console.log(`Error: ${error}`); + reject(error); + } else { + stdout && console.log(`STDOUT: ${stdout}`); + resolve(stdout); + } + } + ); + }); +}; + +const main = async () => { + try { + console.log(`==== Horizon Upgrade ${version} ====`); + await exec(`npm version ${version}`, DIST_PATH); + fs.writeFileSync(path.resolve(DIST_PATH, '.npmrc'), NPMRC); + + console.log('==== Publish new version===='); + await exec('npm publish', DIST_PATH); + process.exit(); + } catch (err) { + console.error(err); + process.exit(1); + } +}; +main(); diff --git a/.cloudbuild/test.yml b/.cloudbuild/test.yml new file mode 100644 index 00000000..0593afd8 --- /dev/null +++ b/.cloudbuild/test.yml @@ -0,0 +1,37 @@ +version: '2.0' +notifications: + notifier: + espace: + 'on': false + email: + 'on': false +buildspace: + log_collect: + - config/CI/build/logs + fixed: true +env: + resource: + type: docker + image: kweecr04.his.huawei.com:80/ecr-build-arm-gzkunpeng/euleros_v2r7spc522_x64_opmt_cs5.0_sz:v5.0 + class: 4U8G + mode: toolbox + cache: + - type: workspace +steps: + PRE_BUILD: + - checkout: + path: horizon-core + - gitlab: + url: https://szv-open.codehub.huawei.com/innersource/shanhai/wutong/react/horizon-test.git + branch: one_tree_dev + path: horizon-test + BUILD: + - build_execute: + command: | + npm install yarn -g + yarn config set strict-ssl false + cd horizon-core + yarn + cd ../horizon-test + yarn + yarn run test diff --git a/CHANGELOG.md b/CHANGELOG.md index f28885cb..3e4be6ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.0.13 (2022-08-02) +- **horizonX**: 修复redux兼容器bug + ## 0.0.12 (2022-07-25) - 修复IE兼容性问题,IE环境下Event只读,导致合成事件逻辑报错 diff --git a/babel.config.js b/babel.config.js index 9f82d978..dd703552 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,38 +1,34 @@ module.exports = { - presets: [ - '@babel/react', - '@babel/preset-typescript', - ['@babel/preset-env', { targets: { node: 'current' } }] - ], + presets: ['@babel/preset-typescript', ['@babel/preset-env', { targets: { node: 'current' } }]], plugins: [ - ['@babel/plugin-proposal-class-properties', { loose: true }], + '@babel/plugin-syntax-jsx', [ - '@babel/plugin-proposal-object-rest-spread', - { loose: true, useBuiltIns: true }, + '@babel/plugin-transform-react-jsx', + { + pragma: 'Horizon.createElement', + pragmaFrag: 'Horizon.Fragment', + }, ], - ['@babel/plugin-transform-template-literals', { loose: true }], + ['@babel/plugin-proposal-class-properties', { loose: true }], + ['@babel/plugin-proposal-private-methods', { loose: true }], + ['@babel/plugin-proposal-private-property-in-object', { loose: true }], '@babel/plugin-transform-object-assign', - '@babel/plugin-transform-literals', - '@babel/plugin-transform-arrow-functions', - '@babel/plugin-transform-block-scoped-functions', '@babel/plugin-transform-object-super', + ['@babel/plugin-proposal-object-rest-spread', { loose: true, useBuiltIns: true }], + ['@babel/plugin-transform-template-literals', { loose: true }], + '@babel/plugin-transform-arrow-functions', + '@babel/plugin-transform-literals', + '@babel/plugin-transform-for-of', + '@babel/plugin-transform-block-scoped-functions', + '@babel/plugin-transform-classes', '@babel/plugin-transform-shorthand-properties', '@babel/plugin-transform-computed-properties', - '@babel/plugin-transform-for-of', - ['@babel/plugin-transform-spread', { loose: true, useBuiltIns: true }], '@babel/plugin-transform-parameters', - ['@babel/plugin-transform-destructuring', { loose: true, useBuiltIns: true }], + ['@babel/plugin-transform-spread', { loose: true, useBuiltIns: true }], ['@babel/plugin-transform-block-scoping', { throwIfClosureRequired: false }], - '@babel/plugin-transform-classes', + ['@babel/plugin-transform-destructuring', { loose: true, useBuiltIns: true }], '@babel/plugin-transform-runtime', '@babel/plugin-proposal-nullish-coalescing-operator', '@babel/plugin-proposal-optional-chaining', - ['@babel/plugin-proposal-private-methods', { 'loose': true }], - ['@babel/plugin-proposal-private-property-in-object', { 'loose': true }], - '@babel/plugin-syntax-jsx', - ['@babel/plugin-transform-react-jsx', { - pragma: 'Horizon.createElement', - pragmaFrag: 'Horizon.Fragment' - }], ], }; diff --git a/libs/horizon/package.json b/libs/horizon/package.json index 33a61260..0eff3ec8 100644 --- a/libs/horizon/package.json +++ b/libs/horizon/package.json @@ -4,7 +4,7 @@ "keywords": [ "horizon" ], - "version": "0.0.12", + "version": "0.0.13", "homepage": "", "bugs": "", "main": "index.js", diff --git a/libs/horizon/src/external/ChildrenUtil.ts b/libs/horizon/src/external/ChildrenUtil.ts index d1a928fb..b034107a 100644 --- a/libs/horizon/src/external/ChildrenUtil.ts +++ b/libs/horizon/src/external/ChildrenUtil.ts @@ -1,7 +1,7 @@ -import {throwIfTrue} from '../renderer/utils/throwIfTrue'; -import {TYPE_COMMON_ELEMENT, TYPE_PORTAL} from './JSXElementType'; +import { throwIfTrue } from '../renderer/utils/throwIfTrue'; +import { TYPE_COMMON_ELEMENT, TYPE_PORTAL } from './JSXElementType'; -import {isValidElement, JSXElement} from './JSXElement'; +import { isValidElement, JSXElement } from './JSXElement'; // 生成key function getItemKey(item: any, index: number): string { @@ -12,12 +12,7 @@ function getItemKey(item: any, index: number): string { return '.' + index.toString(36); } -function mapChildrenToArray( - children: any, - arr: Array, - prefix: string, - callback?: Function, -): number | void { +function mapChildrenToArray(children: any, arr: Array, prefix: string, callback?: Function): number | void { const type = typeof children; switch (type) { // 继承原有规格,undefined和boolean类型按照null处理 @@ -36,44 +31,27 @@ function mapChildrenToArray( } const vtype = children.vtype; if (vtype === TYPE_COMMON_ELEMENT || vtype === TYPE_PORTAL) { - callMapFun(children, arr, prefix, callback) ; + 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. ' - ); + throw new Error('Object is invalid as a Horizon child. '); // No Default } } -function processArrayChildren( - children: any, - arr: Array, - prefix: string, - callback: Function, -) { +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, - ); + mapChildrenToArray(childItem, arr, nextPrefix, callback); } } -function callMapFun( - children: any, - arr: Array, - prefix: string, - callback: Function, -) { +function callMapFun(children: any, arr: Array, prefix: string, callback: Function) { let mappedChild = callback(children); if (Array.isArray(mappedChild)) { // 维持原有规格,如果callback返回结果是数组,处理函数修改为返回数组item @@ -83,9 +61,8 @@ function callMapFun( 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 - : ''); + const newKey = + prefix + childKey + (mappedChild.key && mappedKey !== getItemKey(children, 0) ? '.$' + mappedChild.key : ''); // 返回一个修改key的children mappedChild = JSXElement( mappedChild.type, @@ -93,6 +70,7 @@ function callMapFun( mappedChild.ref, mappedChild.belongClassVNode, mappedChild.props, + mappedChild.src ); } arr.push(mappedChild); @@ -100,11 +78,7 @@ function callMapFun( } // 在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg -function mapChildren( - children: any, - func: Function, - context?: any, -): Array { +function mapChildren(children: any, func: Function, context?: any): Array { if (children === null || children === undefined) { return children; } @@ -121,27 +95,22 @@ const Children = { }, map: mapChildren, // 并非所有元素都会计数,只计数调用callMapFun函数次数 - count: (children) => { + count: children => { let n = 0; mapChildren(children, () => { n++; }); return n; }, - only: (children) => { - throwIfTrue( - !isValidElement(children), - 'Horizon.Children.only function received invalid element.' - ); + only: children => { + throwIfTrue(!isValidElement(children), 'Horizon.Children.only function received invalid element.'); return children; }, - toArray: (children) => { + toArray: children => { const result = []; mapChildrenToArray(children, result, '', child => child); return result; }, -} - -export { - Children }; + +export { Children }; diff --git a/libs/horizon/src/external/JSXElement.ts b/libs/horizon/src/external/JSXElement.ts index d805b843..f44219ee 100644 --- a/libs/horizon/src/external/JSXElement.ts +++ b/libs/horizon/src/external/JSXElement.ts @@ -1,6 +1,6 @@ import { TYPE_COMMON_ELEMENT } from './JSXElementType'; import { getProcessingClassVNode } from '../renderer/GlobalVar'; - +import { Source } from '../renderer/Types'; /** * vtype 节点的类型,这里固定是element @@ -9,10 +9,11 @@ import { getProcessingClassVNode } from '../renderer/GlobalVar'; * ref ref属性 * props 其他常规属性 */ -export function JSXElement(type, key, ref, vNode, props) { +export function JSXElement(type, key, ref, vNode, props, source: Source | null) { return { // 元素标识符 vtype: TYPE_COMMON_ELEMENT, + src: isDev ? source : null, // 属于元素的内置属性 type: type, @@ -26,7 +27,8 @@ export function JSXElement(type, key, ref, vNode, props) { } function isValidKey(key) { - return key !== 'key' && key !== 'ref' && key !== '__source'; + const keyArray = ['key', 'ref', '__source']; + return !keyArray.includes(key); } function mergeDefault(sourceObj, defaultObj) { @@ -66,8 +68,15 @@ function buildElement(isClone, type, setting, children) { if (element && element.defaultProps) { mergeDefault(props, element.defaultProps); } + let src: Source | null = null; + if (setting?.__source) { + src = { + fileName: setting.__source.fileName, + lineNumber: setting.__source.lineNumber, + }; + } - return JSXElement(element, key, ref, vNode, props); + return JSXElement(element, key, ref, vNode, props, src); } // 创建Element结构体,供JSX编译时调用 diff --git a/libs/horizon/src/external/devtools.ts b/libs/horizon/src/external/devtools.ts index e1edf1ff..0cc4d530 100644 --- a/libs/horizon/src/external/devtools.ts +++ b/libs/horizon/src/external/devtools.ts @@ -1,12 +1,5 @@ import { travelVNodeTree } from '../renderer/vnode/VNodeUtils'; -import { - Hook, - Reducer, - Ref, - Effect, - CallBack, - Memo -} from '../renderer/hooks/HookType'; +import { Hook, Reducer, Ref, Effect, CallBack, Memo } from '../renderer/hooks/HookType'; import { VNode } from '../renderer/vnode/VNode'; import { launchUpdateFromVNode } from '../renderer/TreeBuilder'; import { DomComponent } from '../renderer/vnode/VNodeTags'; @@ -26,7 +19,7 @@ const HookName = { MemoHook: 'Memo', RefHook: 'Ref', ReducerHook: 'Reducer', - CallbackHook: 'Callback' + CallbackHook: 'Callback', }; export const helper = { @@ -45,7 +38,8 @@ export const helper = { } else if (isRefHook(state)) { return { name: HookName.RefHook, hIndex, value: (state as Ref).current }; } else if (isEffectHook(state)) { - const name = state.effectConstant == EffectConstant.LayoutEffect ? HookName.LayoutEffectHook : HookName.EffectHook; + const name = + state.effectConstant == EffectConstant.LayoutEffect ? HookName.LayoutEffectHook : HookName.EffectHook; return { name, hIndex, value: (state as Effect).effect }; } else if (isCallbackHook(state)) { return { name: HookName.CallbackHook, hIndex, value: (state as CallBack).func }; @@ -86,7 +80,7 @@ export const helper = { } if (hooks && hooks.length !== 0) { const logHookInfo: any[] = []; - hooks.forEach((hook) => { + hooks.forEach(hook => { const state = hook.state as Reducer; if (state.trigger && state.isUseState) { logHookInfo.push(state.stateValue); @@ -94,20 +88,26 @@ export const helper = { }); info['Hooks'] = logHookInfo; } - travelVNodeTree(vNode, (node: VNode) => { - if (node.tag === DomComponent) { - // 找到组件的第一个dom元素,返回它所在父节点的全部子节点 - const dom = node.realNode; - info['Nodes'] = dom?.parentNode?.childNodes; - return true; - } - return false; - }, null, vNode, null); + travelVNodeTree( + vNode, + (node: VNode) => { + if (node.tag === DomComponent) { + // 找到组件的第一个dom元素,返回它所在父节点的全部子节点 + const dom = node.realNode; + info['Nodes'] = dom?.parentNode?.childNodes; + return true; + } + return false; + }, + null, + vNode, + null + ); return info; }, getElementTag: (element: JSXElement) => { return getElementTag(element); - } + }, }; export function injectUpdater() { diff --git a/libs/horizon/src/horizonx/store/StoreHandler.ts b/libs/horizon/src/horizonx/store/StoreHandler.ts index e82a4a96..bdbf8f77 100644 --- a/libs/horizon/src/horizonx/store/StoreHandler.ts +++ b/libs/horizon/src/horizonx/store/StoreHandler.ts @@ -15,7 +15,6 @@ function isPromise(obj: any): boolean { type StoreConfig, C extends UserComputedValues> = { state?: S; - // options?: { suppressHooks?: boolean }; actions?: A; id?: string; computed?: C; @@ -27,18 +26,15 @@ export type ReduxStoreHandler = { getState: () => any; subscribe: (listener: () => void) => () => void; replaceReducer: (reducer: (state: any, action: { type: string }) => any) => void; - // _horizonXstore: StoreHandler; }; type StoreHandler, C extends UserComputedValues> = { $subscribe: (listener: () => void) => void; $unsubscribe: (listener: () => void) => void; $s: S; - // $config: StoreConfig; $queue: QueuedStoreActions; $a: StoreActions; $c: UserComputedValues; - // reduxHandler?: ReduxStoreHandler; } & { [K in keyof S]: S[K] } & { [K in keyof A]: Action } & { [K in keyof C]: ReturnType }; diff --git a/libs/horizon/src/renderer/Types.ts b/libs/horizon/src/renderer/Types.ts index 6e1572fe..45f76fa9 100644 --- a/libs/horizon/src/renderer/Types.ts +++ b/libs/horizon/src/renderer/Types.ts @@ -3,20 +3,16 @@ export { VNode } from './vnode/VNode'; type Trigger = (A) => void; export type UseStateHookType = { - useState( - initialState: (() => S) | S - ): [S, Trigger<((S) => S) | S>]; + useState(initialState: (() => S) | S): [S, Trigger<((S) => S) | S>]; }; export type UseReducerHookType = { - useReducer( - reducer: (S, A) => S, - initArg: P, init?: (P) => S, - ): [S, Trigger]; + useReducer(reducer: (S, A) => S, initArg: P, init?: (P) => S): [S, Trigger]; }; -export type UseContextHookType = { useContext(context: ContextType,): T }; +export type UseContextHookType = { useContext(context: ContextType): T }; export type JSXElement = { vtype: any; + src: any; type: any; key: any; ref: any; @@ -31,8 +27,8 @@ export type ProviderType = { export type ContextType = { vtype: number; - Consumer: ContextType; - Provider: ProviderType; + Consumer: ContextType | null; + Provider: ProviderType | null; value: T; }; @@ -50,7 +46,7 @@ export type RefType = { export interface PromiseType { then( onFulfill: (value: R) => void | PromiseType | U, - onReject: (error: any) => void | PromiseType | U, + onReject: (error: any) => void | PromiseType | U ): void | PromiseType; } @@ -61,3 +57,8 @@ export interface SuspenseState { didCapture: boolean; // suspense是否捕获了异常 promiseResolved: boolean; // suspense的promise是否resolve } + +export type Source = { + fileName: string; + lineNumber: number; +}; diff --git a/libs/horizon/src/renderer/hooks/UseReducerHook.ts b/libs/horizon/src/renderer/hooks/UseReducerHook.ts index c40da904..9dd38319 100644 --- a/libs/horizon/src/renderer/hooks/UseReducerHook.ts +++ b/libs/horizon/src/renderer/hooks/UseReducerHook.ts @@ -1,20 +1,18 @@ import type { Hook, Reducer, Trigger, Update } from './HookType'; -import { - createHook, - getCurrentHook, - throwNotInFuncError -} from './BaseHook'; -import { - launchUpdateFromVNode -} from '../TreeBuilder'; +import { createHook, getCurrentHook, throwNotInFuncError } from './BaseHook'; +import { launchUpdateFromVNode } from '../TreeBuilder'; import { isSame } from '../utils/compare'; import { setStateChange } from '../render/FunctionComponent'; import { getHookStage, HookStage } from './HookStage'; import type { VNode } from '../Types'; -import {getProcessingVNode} from '../GlobalVar'; +import { getProcessingVNode } from '../GlobalVar'; -export function useReducerImpl(reducer: (S, A) => - S, initArg: P, init?: (P) => S, isUseState?: boolean): [S, Trigger] | void { +export function useReducerImpl( + reducer: (S, A) => S, + initArg: P, + init?: (P) => S, + isUseState?: boolean +): [S, Trigger] | void { const stage = getHookStage(); if (stage === null) { throwNotInFuncError(); @@ -53,16 +51,19 @@ function insertUpdate(action: A, hook: Hook): Update { } // setState, setReducer触发函数 -export function TriggerAction(vNode: VNode, hook: Hook, action: A) { +export function TriggerAction(vNode: VNode, hook: Hook, isUseState: boolean, action: A): void { const newUpdate = insertUpdate(action, hook); // 判断是否需要刷新 - if (!vNode.shouldUpdate) { - const reducerObj = hook.state as Reducer; - const { stateValue, reducer } = reducerObj; + if (!vNode.shouldUpdate && isUseState) { + const { stateValue, reducer } = hook.state as Reducer; + if (reducer === null) { + return; + } // 在进入render阶段前reducer没有变化,可以复用state值,提升性能 newUpdate.state = reducer(stateValue, action); + // 标记为已经计算过,不需要重新计算了 newUpdate.didCalculated = true; @@ -87,16 +88,17 @@ export function useReducerForInit(reducer, initArg, init, isUseState?: boo } const hook = createHook(); + const trigger = TriggerAction.bind(null, getProcessingVNode(), hook, isUseState); // 为hook.state赋值{状态值, 触发函数, reducer, updates更新数组, 是否是useState} hook.state = { stateValue: stateValue, - trigger: TriggerAction.bind(null, getProcessingVNode(), hook), + trigger, reducer, updates: null, - isUseState + isUseState, } as Reducer; - return [hook.state.stateValue, hook.state.trigger]; + return [hook.state.stateValue, trigger]; } // 更新hook.state @@ -136,5 +138,3 @@ function calculateNewState(currentHookUpdates: Array>, curren return state; } - - diff --git a/libs/horizon/src/renderer/render/MemoComponent.ts b/libs/horizon/src/renderer/render/MemoComponent.ts index 99a34364..270346e7 100644 --- a/libs/horizon/src/renderer/render/MemoComponent.ts +++ b/libs/horizon/src/renderer/render/MemoComponent.ts @@ -1,21 +1,19 @@ -import type {VNode} from '../Types'; +import type { Source, VNode } from '../Types'; -import {mergeDefaultProps} from './LazyComponent'; -import {updateVNode, onlyUpdateChildVNodes, createFragmentVNode, createUndeterminedVNode} from '../vnode/VNodeCreator'; -import {shallowCompare} from '../utils/compare'; +import { mergeDefaultProps } from './LazyComponent'; import { - TYPE_FRAGMENT, - TYPE_PROFILER, - TYPE_STRICT_MODE, -} from '../../external/JSXElementType'; + updateVNode, + onlyUpdateChildVNodes, + createFragmentVNode, + createUndeterminedVNode, +} from '../vnode/VNodeCreator'; +import { shallowCompare } from '../utils/compare'; +import { TYPE_FRAGMENT, TYPE_PROFILER, TYPE_STRICT_MODE } from '../../external/JSXElementType'; import { markVNodePath } from '../utils/vNodePath'; export function bubbleRender() {} -export function captureMemoComponent( - processing: VNode, - shouldUpdate: boolean, -): VNode | null { +export function captureMemoComponent(processing: VNode, shouldUpdate: boolean): VNode | null { const Component = processing.type; // 合并 函数组件或类组件 的defaultProps const newProps = mergeDefaultProps(Component, processing.props); @@ -26,7 +24,7 @@ export function captureMemoComponent( if (type === TYPE_STRICT_MODE || type === TYPE_FRAGMENT || type === TYPE_PROFILER) { newChild = createFragmentVNode(null, newProps.children); } else { - newChild = createUndeterminedVNode(type, null, newProps); + newChild = createUndeterminedVNode(type, null, newProps, processing.src); } newChild.parent = processing; newChild.ref = processing.ref; diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 7c360e47..80baedc9 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -17,10 +17,10 @@ import { Profiler, MemoComponent, } from './VNodeTags'; -import type { VNodeTag } from './VNodeTags'; -import type { RefType, ContextType, SuspenseState } from '../Types'; -import type { Hook } from '../hooks/HookType'; -import { InitFlag } from './VNodeFlags'; +import type {VNodeTag} from './VNodeTags'; +import type {RefType, ContextType, SuspenseState, Source} from '../Types'; +import type {Hook} from '../hooks/HookType'; +import {InitFlag} from './VNodeFlags'; export class VNode { tag: VNodeTag; @@ -77,8 +77,8 @@ export class VNode { // 根节点数据 toUpdateNodes: Set | null; // 保存要更新的节点 - delegatedEvents: Set - delegatedNativeEvents: Set + delegatedEvents: Set; + delegatedNativeEvents: Set; belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用 @@ -86,6 +86,7 @@ export class VNode { isStoreChange: boolean; observers: Set | null = null; // 记录这个函数组件/类组件依赖哪些Observer classComponentWillUnmount: Function | null; // HorizonX会在classComponentWillUnmount中清除对VNode的引入用 + src: Source | null; // 节点所在代码位置 constructor(tag: VNodeTag, props: any, key: null | string, realNode) { this.tag = tag; // 对应组件的类型,比如ClassComponent等 @@ -116,6 +117,7 @@ export class VNode { this.isStoreChange = false; this.observers = null; this.classComponentWillUnmount = null; + this.src = null; break; case ClassComponent: this.realNode = null; @@ -130,15 +132,18 @@ export class VNode { this.isStoreChange = false; this.observers = null; this.classComponentWillUnmount = null; + this.src = null; break; case DomPortal: this.realNode = null; this.context = null; + this.src = null; break; case DomComponent: this.realNode = null; this.changeList = null; this.context = null; + this.src = null; break; case DomText: this.realNode = null; @@ -150,14 +155,17 @@ export class VNode { didCapture: false, promiseResolved: false, oldChildStatus: '', - childStatus: '' + childStatus: '', }; + this.src = null; break; case ContextProvider: + this.src = null; this.context = null; break; case MemoComponent: this.effectList = null; + this.src = null; break; case LazyComponent: this.realNode = null; @@ -165,6 +173,7 @@ export class VNode { this.isLazyComponent = true; this.lazyType = null; this.updates = null; + this.src = null; break; case Fragment: break; diff --git a/libs/horizon/src/renderer/vnode/VNodeCreator.ts b/libs/horizon/src/renderer/vnode/VNodeCreator.ts index c5b4c306..79496323 100644 --- a/libs/horizon/src/renderer/vnode/VNodeCreator.ts +++ b/libs/horizon/src/renderer/vnode/VNodeCreator.ts @@ -17,14 +17,17 @@ import { } from './VNodeTags'; import { TYPE_CONTEXT, - TYPE_FORWARD_REF, TYPE_FRAGMENT, + TYPE_FORWARD_REF, + TYPE_FRAGMENT, TYPE_LAZY, - TYPE_MEMO, TYPE_PROFILER, - TYPE_PROVIDER, TYPE_STRICT_MODE, + TYPE_MEMO, + TYPE_PROFILER, + TYPE_PROVIDER, + TYPE_STRICT_MODE, TYPE_SUSPENSE, } from '../../external/JSXElementType'; import { VNode } from './VNode'; -import { JSXElement } from '../Types'; +import { JSXElement, Source } from '../Types'; import { markVNodePath } from '../utils/vNodePath'; const typeLazyMap = { @@ -56,7 +59,7 @@ export function getLazyVNodeTag(lazyComp: any): string { } else if (lazyComp !== undefined && lazyComp !== null && typeLazyMap[lazyComp.vtype]) { return typeLazyMap[lazyComp.vtype]; } - throw Error('Horizon can\'t resolve the content of lazy'); + throw Error("Horizon can't resolve the content of lazy"); } // 创建processing @@ -102,11 +105,10 @@ export function createPortalVNode(portal) { return vNode; } -export function createUndeterminedVNode(type, key, props) { +export function createUndeterminedVNode(type, key, props, source: Source | null): VNode { let vNodeTag = FunctionComponent; let isLazy = false; const componentType = typeof type; - if (componentType === 'function') { if (isClassComponent(type)) { vNodeTag = ClassComponent; @@ -129,6 +131,8 @@ export function createUndeterminedVNode(type, key, props) { if (isLazy) { vNode.lazyType = type; } + + vNode.src = isDev ? source : null; return vNode; } @@ -181,14 +185,12 @@ export function createVNode(tag: VNodeTag | string, ...secondArg) { } export function createVNodeFromElement(element: JSXElement): VNode { - const type = element.type; - const key = element.key; - const props = element.props; + const { type, key, props, src } = element; if (type === TYPE_STRICT_MODE || type === TYPE_FRAGMENT || type === TYPE_PROFILER) { return createFragmentVNode(key, props.children); } else { - return createUndeterminedVNode(type, key, props); + return createUndeterminedVNode(type, key, props, src); } } @@ -241,4 +243,3 @@ export function onlyUpdateChildVNodes(processing: VNode): VNode | null { // 子树无需工作 return null; } - diff --git a/package.json b/package.json index fe9836de..500c07f5 100644 --- a/package.json +++ b/package.json @@ -33,12 +33,13 @@ "@babel/plugin-transform-object-assign": "7.16.7", "@babel/plugin-transform-object-super": "7.16.7", "@babel/plugin-transform-parameters": "7.16.7", + "@babel/plugin-transform-react-jsx": "7.16.7", "@babel/plugin-transform-runtime": "7.16.7", "@babel/plugin-transform-shorthand-properties": "7.16.7", "@babel/plugin-transform-spread": "7.16.7", "@babel/plugin-transform-template-literals": "7.16.7", + "@babel/plugin-transform-react-jsx-source": "^7.16.7", "@babel/preset-env": "7.16.7", - "@babel/preset-react": "7.16.7", "@babel/preset-typescript": "7.16.7", "@rollup/plugin-babel": "^5.3.1", "@rollup/plugin-node-resolve": "^13.3.0", diff --git a/scripts/__tests__/ComponentTest/HookTest/UseReducer.test.js b/scripts/__tests__/ComponentTest/HookTest/UseReducer.test.js index 5923d9ad..5e7f0876 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseReducer.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseReducer.test.js @@ -13,25 +13,25 @@ describe('useReducer Hook Test', () => { return { ...intlCar, logo: 'ford', - price: 76 + price: 76, }; case 'bmw': return { ...intlCar, logo: 'bmw', - price: 100 + price: 100, }; case 'benz': return { ...intlCar, logo: 'benz', - price: 80 + price: 80, }; default: return { ...intlCar, logo: 'audi', - price: 88 + price: 88, }; } }; @@ -56,4 +56,34 @@ describe('useReducer Hook Test', () => { expect(container.querySelector('p').innerHTML).toBe('audi'); expect(container.querySelector('#senP').innerHTML).toBe('88'); }); + + it('dispatch只触发一次', () => { + let nextId = 1; + const reducer = () => { + return { data: nextId++ }; + }; + const btnRef = Horizon.createRef(); + const Main = () => { + const [{ data }, dispatch] = useReducer(reducer, { data: 0 }); + const dispatchLogging = () => { + console.log('dispatch is called once'); + dispatch(); + }; + + return ( +
+ +
{data}
+
+ ); + }; + + Horizon.render(
, container); + Horizon.act(() => { + btnRef.current.click(); + }); + expect(nextId).toBe(2); + }); }); diff --git a/scripts/__tests__/DomTest/__snapshots__/DomTextarea.test.js.snap b/scripts/__tests__/DomTest/__snapshots__/DomTextarea.test.js.snap deleted file mode 100644 index c2975807..00000000 --- a/scripts/__tests__/DomTest/__snapshots__/DomTextarea.test.js.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Dom Textarea should not incur unnecessary DOM mutations 1`] = `