Match-id-4d95f32e780a098aa863161118eff06d7a040b5c

This commit is contained in:
* 2022-08-15 20:50:28 +08:00
commit 5484fa748a
20 changed files with 353 additions and 250 deletions

37
.cloudbuild/build.yml Normal file
View File

@ -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

60
.cloudbuild/release.js Normal file
View File

@ -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();

37
.cloudbuild/test.yml Normal file
View File

@ -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

View File

@ -1,3 +1,6 @@
## 0.0.13 (2022-08-02)
- **horizonX**: 修复redux兼容器bug
## 0.0.12 (2022-07-25) ## 0.0.12 (2022-07-25)
- 修复IE兼容性问题IE环境下Event只读导致合成事件逻辑报错 - 修复IE兼容性问题IE环境下Event只读导致合成事件逻辑报错

View File

@ -1,38 +1,34 @@
module.exports = { module.exports = {
presets: [ presets: ['@babel/preset-typescript', ['@babel/preset-env', { targets: { node: 'current' } }]],
'@babel/react',
'@babel/preset-typescript',
['@babel/preset-env', { targets: { node: 'current' } }]
],
plugins: [ plugins: [
['@babel/plugin-proposal-class-properties', { loose: true }], '@babel/plugin-syntax-jsx',
[ [
'@babel/plugin-proposal-object-rest-spread', '@babel/plugin-transform-react-jsx',
{ loose: true, useBuiltIns: true }, {
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-object-assign',
'@babel/plugin-transform-literals',
'@babel/plugin-transform-arrow-functions',
'@babel/plugin-transform-block-scoped-functions',
'@babel/plugin-transform-object-super', '@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-shorthand-properties',
'@babel/plugin-transform-computed-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-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-block-scoping', { throwIfClosureRequired: false }],
'@babel/plugin-transform-classes', ['@babel/plugin-transform-destructuring', { loose: true, useBuiltIns: true }],
'@babel/plugin-transform-runtime', '@babel/plugin-transform-runtime',
'@babel/plugin-proposal-nullish-coalescing-operator', '@babel/plugin-proposal-nullish-coalescing-operator',
'@babel/plugin-proposal-optional-chaining', '@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'
}],
], ],
}; };

View File

@ -4,7 +4,7 @@
"keywords": [ "keywords": [
"horizon" "horizon"
], ],
"version": "0.0.12", "version": "0.0.13",
"homepage": "", "homepage": "",
"bugs": "", "bugs": "",
"main": "index.js", "main": "index.js",

View File

@ -1,7 +1,7 @@
import {throwIfTrue} from '../renderer/utils/throwIfTrue'; import { throwIfTrue } from '../renderer/utils/throwIfTrue';
import {TYPE_COMMON_ELEMENT, TYPE_PORTAL} from './JSXElementType'; import { TYPE_COMMON_ELEMENT, TYPE_PORTAL } from './JSXElementType';
import {isValidElement, JSXElement} from './JSXElement'; import { isValidElement, JSXElement } from './JSXElement';
// 生成key // 生成key
function getItemKey(item: any, index: number): string { function getItemKey(item: any, index: number): string {
@ -12,12 +12,7 @@ function getItemKey(item: any, index: number): string {
return '.' + index.toString(36); return '.' + index.toString(36);
} }
function mapChildrenToArray( function mapChildrenToArray(children: any, arr: Array<any>, prefix: string, callback?: Function): number | void {
children: any,
arr: Array<any>,
prefix: string,
callback?: Function,
): number | void {
const type = typeof children; const type = typeof children;
switch (type) { switch (type) {
// 继承原有规格undefined和boolean类型按照null处理 // 继承原有规格undefined和boolean类型按照null处理
@ -36,44 +31,27 @@ function mapChildrenToArray(
} }
const vtype = children.vtype; const vtype = children.vtype;
if (vtype === TYPE_COMMON_ELEMENT || vtype === TYPE_PORTAL) { if (vtype === TYPE_COMMON_ELEMENT || vtype === TYPE_PORTAL) {
callMapFun(children, arr, prefix, callback) ; callMapFun(children, arr, prefix, callback);
return; return;
} }
if (Array.isArray(children)) { if (Array.isArray(children)) {
processArrayChildren(children, arr, prefix, callback); processArrayChildren(children, arr, prefix, callback);
return; return;
} }
throw new Error( throw new Error('Object is invalid as a Horizon child. ');
'Object is invalid as a Horizon child. '
);
// No Default // No Default
} }
} }
function processArrayChildren( function processArrayChildren(children: any, arr: Array<any>, prefix: string, callback: Function) {
children: any,
arr: Array<any>,
prefix: string,
callback: Function,
) {
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
const childItem = children[i]; const childItem = children[i];
const nextPrefix = prefix + getItemKey(childItem, i); const nextPrefix = prefix + getItemKey(childItem, i);
mapChildrenToArray( mapChildrenToArray(childItem, arr, nextPrefix, callback);
childItem,
arr,
nextPrefix,
callback,
);
} }
} }
function callMapFun( function callMapFun(children: any, arr: Array<any>, prefix: string, callback: Function) {
children: any,
arr: Array<any>,
prefix: string,
callback: Function,
) {
let mappedChild = callback(children); let mappedChild = callback(children);
if (Array.isArray(mappedChild)) { if (Array.isArray(mappedChild)) {
// 维持原有规格如果callback返回结果是数组处理函数修改为返回数组item // 维持原有规格如果callback返回结果是数组处理函数修改为返回数组item
@ -83,9 +61,8 @@ function callMapFun(
if (isValidElement(mappedChild)) { if (isValidElement(mappedChild)) {
const childKey = prefix === '' ? getItemKey(children, 0) : ''; const childKey = prefix === '' ? getItemKey(children, 0) : '';
const mappedKey = getItemKey(mappedChild, 0); const mappedKey = getItemKey(mappedChild, 0);
const newKey = prefix + childKey + (mappedChild.key && mappedKey !== getItemKey(children, 0) const newKey =
? '.$' + mappedChild.key prefix + childKey + (mappedChild.key && mappedKey !== getItemKey(children, 0) ? '.$' + mappedChild.key : '');
: '');
// 返回一个修改key的children // 返回一个修改key的children
mappedChild = JSXElement( mappedChild = JSXElement(
mappedChild.type, mappedChild.type,
@ -93,6 +70,7 @@ function callMapFun(
mappedChild.ref, mappedChild.ref,
mappedChild.belongClassVNode, mappedChild.belongClassVNode,
mappedChild.props, mappedChild.props,
mappedChild.src
); );
} }
arr.push(mappedChild); arr.push(mappedChild);
@ -100,11 +78,7 @@ function callMapFun(
} }
// 在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg // 在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg
function mapChildren( function mapChildren(children: any, func: Function, context?: any): Array<any> {
children: any,
func: Function,
context?: any,
): Array<any> {
if (children === null || children === undefined) { if (children === null || children === undefined) {
return children; return children;
} }
@ -121,27 +95,22 @@ const Children = {
}, },
map: mapChildren, map: mapChildren,
// 并非所有元素都会计数,只计数调用callMapFun函数次数 // 并非所有元素都会计数,只计数调用callMapFun函数次数
count: (children) => { count: children => {
let n = 0; let n = 0;
mapChildren(children, () => { mapChildren(children, () => {
n++; n++;
}); });
return n; return n;
}, },
only: (children) => { only: children => {
throwIfTrue( throwIfTrue(!isValidElement(children), 'Horizon.Children.only function received invalid element.');
!isValidElement(children),
'Horizon.Children.only function received invalid element.'
);
return children; return children;
}, },
toArray: (children) => { toArray: children => {
const result = []; const result = [];
mapChildrenToArray(children, result, '', child => child); mapChildrenToArray(children, result, '', child => child);
return result; return result;
}, },
}
export {
Children
}; };
export { Children };

View File

@ -1,6 +1,6 @@
import { TYPE_COMMON_ELEMENT } from './JSXElementType'; import { TYPE_COMMON_ELEMENT } from './JSXElementType';
import { getProcessingClassVNode } from '../renderer/GlobalVar'; import { getProcessingClassVNode } from '../renderer/GlobalVar';
import { Source } from '../renderer/Types';
/** /**
* vtype element * vtype element
@ -9,10 +9,11 @@ import { getProcessingClassVNode } from '../renderer/GlobalVar';
* ref ref属性 * ref ref属性
* props * props
*/ */
export function JSXElement(type, key, ref, vNode, props) { export function JSXElement(type, key, ref, vNode, props, source: Source | null) {
return { return {
// 元素标识符 // 元素标识符
vtype: TYPE_COMMON_ELEMENT, vtype: TYPE_COMMON_ELEMENT,
src: isDev ? source : null,
// 属于元素的内置属性 // 属于元素的内置属性
type: type, type: type,
@ -26,7 +27,8 @@ export function JSXElement(type, key, ref, vNode, props) {
} }
function isValidKey(key) { function isValidKey(key) {
return key !== 'key' && key !== 'ref' && key !== '__source'; const keyArray = ['key', 'ref', '__source'];
return !keyArray.includes(key);
} }
function mergeDefault(sourceObj, defaultObj) { function mergeDefault(sourceObj, defaultObj) {
@ -66,8 +68,15 @@ function buildElement(isClone, type, setting, children) {
if (element && element.defaultProps) { if (element && element.defaultProps) {
mergeDefault(props, 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编译时调用 // 创建Element结构体供JSX编译时调用

View File

@ -1,12 +1,5 @@
import { travelVNodeTree } from '../renderer/vnode/VNodeUtils'; import { travelVNodeTree } from '../renderer/vnode/VNodeUtils';
import { import { Hook, Reducer, Ref, Effect, CallBack, Memo } from '../renderer/hooks/HookType';
Hook,
Reducer,
Ref,
Effect,
CallBack,
Memo
} from '../renderer/hooks/HookType';
import { VNode } from '../renderer/vnode/VNode'; import { VNode } from '../renderer/vnode/VNode';
import { launchUpdateFromVNode } from '../renderer/TreeBuilder'; import { launchUpdateFromVNode } from '../renderer/TreeBuilder';
import { DomComponent } from '../renderer/vnode/VNodeTags'; import { DomComponent } from '../renderer/vnode/VNodeTags';
@ -26,7 +19,7 @@ const HookName = {
MemoHook: 'Memo', MemoHook: 'Memo',
RefHook: 'Ref', RefHook: 'Ref',
ReducerHook: 'Reducer', ReducerHook: 'Reducer',
CallbackHook: 'Callback' CallbackHook: 'Callback',
}; };
export const helper = { export const helper = {
@ -45,7 +38,8 @@ export const helper = {
} else if (isRefHook(state)) { } else if (isRefHook(state)) {
return { name: HookName.RefHook, hIndex, value: (state as Ref<any>).current }; return { name: HookName.RefHook, hIndex, value: (state as Ref<any>).current };
} else if (isEffectHook(state)) { } 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 }; return { name, hIndex, value: (state as Effect).effect };
} else if (isCallbackHook(state)) { } else if (isCallbackHook(state)) {
return { name: HookName.CallbackHook, hIndex, value: (state as CallBack<any>).func }; return { name: HookName.CallbackHook, hIndex, value: (state as CallBack<any>).func };
@ -86,7 +80,7 @@ export const helper = {
} }
if (hooks && hooks.length !== 0) { if (hooks && hooks.length !== 0) {
const logHookInfo: any[] = []; const logHookInfo: any[] = [];
hooks.forEach((hook) => { hooks.forEach(hook => {
const state = hook.state as Reducer<any, any>; const state = hook.state as Reducer<any, any>;
if (state.trigger && state.isUseState) { if (state.trigger && state.isUseState) {
logHookInfo.push(state.stateValue); logHookInfo.push(state.stateValue);
@ -94,20 +88,26 @@ export const helper = {
}); });
info['Hooks'] = logHookInfo; info['Hooks'] = logHookInfo;
} }
travelVNodeTree(vNode, (node: VNode) => { travelVNodeTree(
if (node.tag === DomComponent) { vNode,
// 找到组件的第一个dom元素返回它所在父节点的全部子节点 (node: VNode) => {
const dom = node.realNode; if (node.tag === DomComponent) {
info['Nodes'] = dom?.parentNode?.childNodes; // 找到组件的第一个dom元素返回它所在父节点的全部子节点
return true; const dom = node.realNode;
} info['Nodes'] = dom?.parentNode?.childNodes;
return false; return true;
}, null, vNode, null); }
return false;
},
null,
vNode,
null
);
return info; return info;
}, },
getElementTag: (element: JSXElement) => { getElementTag: (element: JSXElement) => {
return getElementTag(element); return getElementTag(element);
} },
}; };
export function injectUpdater() { export function injectUpdater() {

View File

@ -15,7 +15,6 @@ function isPromise(obj: any): boolean {
type StoreConfig<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>> = { type StoreConfig<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>> = {
state?: S; state?: S;
// options?: { suppressHooks?: boolean };
actions?: A; actions?: A;
id?: string; id?: string;
computed?: C; computed?: C;
@ -27,18 +26,15 @@ export type ReduxStoreHandler = {
getState: () => any; getState: () => any;
subscribe: (listener: () => void) => () => void; subscribe: (listener: () => void) => () => void;
replaceReducer: (reducer: (state: any, action: { type: string }) => any) => void; replaceReducer: (reducer: (state: any, action: { type: string }) => any) => void;
// _horizonXstore: StoreHandler<any, any, any>;
}; };
type StoreHandler<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>> = { type StoreHandler<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>> = {
$subscribe: (listener: () => void) => void; $subscribe: (listener: () => void) => void;
$unsubscribe: (listener: () => void) => void; $unsubscribe: (listener: () => void) => void;
$s: S; $s: S;
// $config: StoreConfig<S, A, C>;
$queue: QueuedStoreActions<S, A>; $queue: QueuedStoreActions<S, A>;
$a: StoreActions<S, A>; $a: StoreActions<S, A>;
$c: UserComputedValues<S>; $c: UserComputedValues<S>;
// reduxHandler?: ReduxStoreHandler;
} & { [K in keyof S]: S[K] } & } & { [K in keyof S]: S[K] } &
{ [K in keyof A]: Action<A[K], S> } & { [K in keyof A]: Action<A[K], S> } &
{ [K in keyof C]: ReturnType<C[K]> }; { [K in keyof C]: ReturnType<C[K]> };

View File

@ -3,20 +3,16 @@ export { VNode } from './vnode/VNode';
type Trigger<A> = (A) => void; type Trigger<A> = (A) => void;
export type UseStateHookType = { export type UseStateHookType = {
useState<S>( useState<S>(initialState: (() => S) | S): [S, Trigger<((S) => S) | S>];
initialState: (() => S) | S
): [S, Trigger<((S) => S) | S>];
}; };
export type UseReducerHookType = { export type UseReducerHookType = {
useReducer<S, P, A>( useReducer<S, P, A>(reducer: (S, A) => S, initArg: P, init?: (P) => S): [S, Trigger<A>];
reducer: (S, A) => S,
initArg: P, init?: (P) => S,
): [S, Trigger<A>];
}; };
export type UseContextHookType = { useContext<T>(context: ContextType<T>,): T }; export type UseContextHookType = { useContext<T>(context: ContextType<T>): T };
export type JSXElement = { export type JSXElement = {
vtype: any; vtype: any;
src: any;
type: any; type: any;
key: any; key: any;
ref: any; ref: any;
@ -31,8 +27,8 @@ export type ProviderType<T> = {
export type ContextType<T> = { export type ContextType<T> = {
vtype: number; vtype: number;
Consumer: ContextType<T>; Consumer: ContextType<T> | null;
Provider: ProviderType<T>; Provider: ProviderType<T> | null;
value: T; value: T;
}; };
@ -50,7 +46,7 @@ export type RefType = {
export interface PromiseType<R> { export interface PromiseType<R> {
then<U>( then<U>(
onFulfill: (value: R) => void | PromiseType<U> | U, onFulfill: (value: R) => void | PromiseType<U> | U,
onReject: (error: any) => void | PromiseType<U> | U, onReject: (error: any) => void | PromiseType<U> | U
): void | PromiseType<U>; ): void | PromiseType<U>;
} }
@ -61,3 +57,8 @@ export interface SuspenseState {
didCapture: boolean; // suspense是否捕获了异常 didCapture: boolean; // suspense是否捕获了异常
promiseResolved: boolean; // suspense的promise是否resolve promiseResolved: boolean; // suspense的promise是否resolve
} }
export type Source = {
fileName: string;
lineNumber: number;
};

View File

@ -1,20 +1,18 @@
import type { Hook, Reducer, Trigger, Update } from './HookType'; import type { Hook, Reducer, Trigger, Update } from './HookType';
import { import { createHook, getCurrentHook, throwNotInFuncError } from './BaseHook';
createHook, import { launchUpdateFromVNode } from '../TreeBuilder';
getCurrentHook,
throwNotInFuncError
} from './BaseHook';
import {
launchUpdateFromVNode
} from '../TreeBuilder';
import { isSame } from '../utils/compare'; import { isSame } from '../utils/compare';
import { setStateChange } from '../render/FunctionComponent'; import { setStateChange } from '../render/FunctionComponent';
import { getHookStage, HookStage } from './HookStage'; import { getHookStage, HookStage } from './HookStage';
import type { VNode } from '../Types'; import type { VNode } from '../Types';
import {getProcessingVNode} from '../GlobalVar'; import { getProcessingVNode } from '../GlobalVar';
export function useReducerImpl<S, P, A>(reducer: (S, A) => export function useReducerImpl<S, P, A>(
S, initArg: P, init?: (P) => S, isUseState?: boolean): [S, Trigger<A>] | void { reducer: (S, A) => S,
initArg: P,
init?: (P) => S,
isUseState?: boolean
): [S, Trigger<A>] | void {
const stage = getHookStage(); const stage = getHookStage();
if (stage === null) { if (stage === null) {
throwNotInFuncError(); throwNotInFuncError();
@ -53,16 +51,19 @@ function insertUpdate<S, A>(action: A, hook: Hook<S, A>): Update<S, A> {
} }
// setState, setReducer触发函数 // setState, setReducer触发函数
export function TriggerAction<S, A, T>(vNode: VNode, hook: Hook<S, A>, action: A) { export function TriggerAction<S, A>(vNode: VNode, hook: Hook<S, A>, isUseState: boolean, action: A): void {
const newUpdate = insertUpdate(action, hook); const newUpdate = insertUpdate(action, hook);
// 判断是否需要刷新 // 判断是否需要刷新
if (!vNode.shouldUpdate) { if (!vNode.shouldUpdate && isUseState) {
const reducerObj = hook.state as Reducer<S, A>; const { stateValue, reducer } = hook.state as Reducer<S, A>;
const { stateValue, reducer } = reducerObj;
if (reducer === null) {
return;
}
// 在进入render阶段前reducer没有变化可以复用state值提升性能 // 在进入render阶段前reducer没有变化可以复用state值提升性能
newUpdate.state = reducer(stateValue, action); newUpdate.state = reducer(stateValue, action);
// 标记为已经计算过,不需要重新计算了 // 标记为已经计算过,不需要重新计算了
newUpdate.didCalculated = true; newUpdate.didCalculated = true;
@ -87,16 +88,17 @@ export function useReducerForInit<S, A>(reducer, initArg, init, isUseState?: boo
} }
const hook = createHook(); const hook = createHook();
const trigger = TriggerAction.bind(null, getProcessingVNode(), hook, isUseState);
// 为hook.state赋值{状态值, 触发函数, reducer, updates更新数组, 是否是useState} // 为hook.state赋值{状态值, 触发函数, reducer, updates更新数组, 是否是useState}
hook.state = { hook.state = {
stateValue: stateValue, stateValue: stateValue,
trigger: TriggerAction.bind(null, getProcessingVNode(), hook), trigger,
reducer, reducer,
updates: null, updates: null,
isUseState isUseState,
} as Reducer<S, A>; } as Reducer<S, A>;
return [hook.state.stateValue, hook.state.trigger]; return [hook.state.stateValue, trigger];
} }
// 更新hook.state // 更新hook.state
@ -136,5 +138,3 @@ function calculateNewState<S, A>(currentHookUpdates: Array<Update<S, A>>, curren
return state; return state;
} }

View File

@ -1,21 +1,19 @@
import type {VNode} from '../Types'; import type { Source, VNode } from '../Types';
import {mergeDefaultProps} from './LazyComponent'; import { mergeDefaultProps } from './LazyComponent';
import {updateVNode, onlyUpdateChildVNodes, createFragmentVNode, createUndeterminedVNode} from '../vnode/VNodeCreator';
import {shallowCompare} from '../utils/compare';
import { import {
TYPE_FRAGMENT, updateVNode,
TYPE_PROFILER, onlyUpdateChildVNodes,
TYPE_STRICT_MODE, createFragmentVNode,
} from '../../external/JSXElementType'; 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'; import { markVNodePath } from '../utils/vNodePath';
export function bubbleRender() {} export function bubbleRender() {}
export function captureMemoComponent( export function captureMemoComponent(processing: VNode, shouldUpdate: boolean): VNode | null {
processing: VNode,
shouldUpdate: boolean,
): VNode | null {
const Component = processing.type; const Component = processing.type;
// 合并 函数组件或类组件 的defaultProps // 合并 函数组件或类组件 的defaultProps
const newProps = mergeDefaultProps(Component, processing.props); const newProps = mergeDefaultProps(Component, processing.props);
@ -26,7 +24,7 @@ export function captureMemoComponent(
if (type === TYPE_STRICT_MODE || type === TYPE_FRAGMENT || type === TYPE_PROFILER) { if (type === TYPE_STRICT_MODE || type === TYPE_FRAGMENT || type === TYPE_PROFILER) {
newChild = createFragmentVNode(null, newProps.children); newChild = createFragmentVNode(null, newProps.children);
} else { } else {
newChild = createUndeterminedVNode(type, null, newProps); newChild = createUndeterminedVNode(type, null, newProps, processing.src);
} }
newChild.parent = processing; newChild.parent = processing;
newChild.ref = processing.ref; newChild.ref = processing.ref;

View File

@ -17,10 +17,10 @@ import {
Profiler, Profiler,
MemoComponent, MemoComponent,
} from './VNodeTags'; } from './VNodeTags';
import type { VNodeTag } from './VNodeTags'; import type {VNodeTag} from './VNodeTags';
import type { RefType, ContextType, SuspenseState } from '../Types'; import type {RefType, ContextType, SuspenseState, Source} from '../Types';
import type { Hook } from '../hooks/HookType'; import type {Hook} from '../hooks/HookType';
import { InitFlag } from './VNodeFlags'; import {InitFlag} from './VNodeFlags';
export class VNode { export class VNode {
tag: VNodeTag; tag: VNodeTag;
@ -77,8 +77,8 @@ export class VNode {
// 根节点数据 // 根节点数据
toUpdateNodes: Set<VNode> | null; // 保存要更新的节点 toUpdateNodes: Set<VNode> | null; // 保存要更新的节点
delegatedEvents: Set<string> delegatedEvents: Set<string>;
delegatedNativeEvents: Set<string> delegatedNativeEvents: Set<string>;
belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode处理ref的时候使用 belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode处理ref的时候使用
@ -86,6 +86,7 @@ export class VNode {
isStoreChange: boolean; isStoreChange: boolean;
observers: Set<any> | null = null; // 记录这个函数组件/类组件依赖哪些Observer observers: Set<any> | null = null; // 记录这个函数组件/类组件依赖哪些Observer
classComponentWillUnmount: Function | null; // HorizonX会在classComponentWillUnmount中清除对VNode的引入用 classComponentWillUnmount: Function | null; // HorizonX会在classComponentWillUnmount中清除对VNode的引入用
src: Source | null; // 节点所在代码位置
constructor(tag: VNodeTag, props: any, key: null | string, realNode) { constructor(tag: VNodeTag, props: any, key: null | string, realNode) {
this.tag = tag; // 对应组件的类型比如ClassComponent等 this.tag = tag; // 对应组件的类型比如ClassComponent等
@ -116,6 +117,7 @@ export class VNode {
this.isStoreChange = false; this.isStoreChange = false;
this.observers = null; this.observers = null;
this.classComponentWillUnmount = null; this.classComponentWillUnmount = null;
this.src = null;
break; break;
case ClassComponent: case ClassComponent:
this.realNode = null; this.realNode = null;
@ -130,15 +132,18 @@ export class VNode {
this.isStoreChange = false; this.isStoreChange = false;
this.observers = null; this.observers = null;
this.classComponentWillUnmount = null; this.classComponentWillUnmount = null;
this.src = null;
break; break;
case DomPortal: case DomPortal:
this.realNode = null; this.realNode = null;
this.context = null; this.context = null;
this.src = null;
break; break;
case DomComponent: case DomComponent:
this.realNode = null; this.realNode = null;
this.changeList = null; this.changeList = null;
this.context = null; this.context = null;
this.src = null;
break; break;
case DomText: case DomText:
this.realNode = null; this.realNode = null;
@ -150,14 +155,17 @@ export class VNode {
didCapture: false, didCapture: false,
promiseResolved: false, promiseResolved: false,
oldChildStatus: '', oldChildStatus: '',
childStatus: '' childStatus: '',
}; };
this.src = null;
break; break;
case ContextProvider: case ContextProvider:
this.src = null;
this.context = null; this.context = null;
break; break;
case MemoComponent: case MemoComponent:
this.effectList = null; this.effectList = null;
this.src = null;
break; break;
case LazyComponent: case LazyComponent:
this.realNode = null; this.realNode = null;
@ -165,6 +173,7 @@ export class VNode {
this.isLazyComponent = true; this.isLazyComponent = true;
this.lazyType = null; this.lazyType = null;
this.updates = null; this.updates = null;
this.src = null;
break; break;
case Fragment: case Fragment:
break; break;

View File

@ -17,14 +17,17 @@ import {
} from './VNodeTags'; } from './VNodeTags';
import { import {
TYPE_CONTEXT, TYPE_CONTEXT,
TYPE_FORWARD_REF, TYPE_FRAGMENT, TYPE_FORWARD_REF,
TYPE_FRAGMENT,
TYPE_LAZY, TYPE_LAZY,
TYPE_MEMO, TYPE_PROFILER, TYPE_MEMO,
TYPE_PROVIDER, TYPE_STRICT_MODE, TYPE_PROFILER,
TYPE_PROVIDER,
TYPE_STRICT_MODE,
TYPE_SUSPENSE, TYPE_SUSPENSE,
} from '../../external/JSXElementType'; } from '../../external/JSXElementType';
import { VNode } from './VNode'; import { VNode } from './VNode';
import { JSXElement } from '../Types'; import { JSXElement, Source } from '../Types';
import { markVNodePath } from '../utils/vNodePath'; import { markVNodePath } from '../utils/vNodePath';
const typeLazyMap = { const typeLazyMap = {
@ -56,7 +59,7 @@ export function getLazyVNodeTag(lazyComp: any): string {
} else if (lazyComp !== undefined && lazyComp !== null && typeLazyMap[lazyComp.vtype]) { } else if (lazyComp !== undefined && lazyComp !== null && typeLazyMap[lazyComp.vtype]) {
return 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 // 创建processing
@ -102,11 +105,10 @@ export function createPortalVNode(portal) {
return vNode; return vNode;
} }
export function createUndeterminedVNode(type, key, props) { export function createUndeterminedVNode(type, key, props, source: Source | null): VNode {
let vNodeTag = FunctionComponent; let vNodeTag = FunctionComponent;
let isLazy = false; let isLazy = false;
const componentType = typeof type; const componentType = typeof type;
if (componentType === 'function') { if (componentType === 'function') {
if (isClassComponent(type)) { if (isClassComponent(type)) {
vNodeTag = ClassComponent; vNodeTag = ClassComponent;
@ -129,6 +131,8 @@ export function createUndeterminedVNode(type, key, props) {
if (isLazy) { if (isLazy) {
vNode.lazyType = type; vNode.lazyType = type;
} }
vNode.src = isDev ? source : null;
return vNode; return vNode;
} }
@ -181,14 +185,12 @@ export function createVNode(tag: VNodeTag | string, ...secondArg) {
} }
export function createVNodeFromElement(element: JSXElement): VNode { export function createVNodeFromElement(element: JSXElement): VNode {
const type = element.type; const { type, key, props, src } = element;
const key = element.key;
const props = element.props;
if (type === TYPE_STRICT_MODE || type === TYPE_FRAGMENT || type === TYPE_PROFILER) { if (type === TYPE_STRICT_MODE || type === TYPE_FRAGMENT || type === TYPE_PROFILER) {
return createFragmentVNode(key, props.children); return createFragmentVNode(key, props.children);
} else { } 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; return null;
} }

View File

@ -33,12 +33,13 @@
"@babel/plugin-transform-object-assign": "7.16.7", "@babel/plugin-transform-object-assign": "7.16.7",
"@babel/plugin-transform-object-super": "7.16.7", "@babel/plugin-transform-object-super": "7.16.7",
"@babel/plugin-transform-parameters": "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-runtime": "7.16.7",
"@babel/plugin-transform-shorthand-properties": "7.16.7", "@babel/plugin-transform-shorthand-properties": "7.16.7",
"@babel/plugin-transform-spread": "7.16.7", "@babel/plugin-transform-spread": "7.16.7",
"@babel/plugin-transform-template-literals": "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-env": "7.16.7",
"@babel/preset-react": "7.16.7",
"@babel/preset-typescript": "7.16.7", "@babel/preset-typescript": "7.16.7",
"@rollup/plugin-babel": "^5.3.1", "@rollup/plugin-babel": "^5.3.1",
"@rollup/plugin-node-resolve": "^13.3.0", "@rollup/plugin-node-resolve": "^13.3.0",

View File

@ -13,25 +13,25 @@ describe('useReducer Hook Test', () => {
return { return {
...intlCar, ...intlCar,
logo: 'ford', logo: 'ford',
price: 76 price: 76,
}; };
case 'bmw': case 'bmw':
return { return {
...intlCar, ...intlCar,
logo: 'bmw', logo: 'bmw',
price: 100 price: 100,
}; };
case 'benz': case 'benz':
return { return {
...intlCar, ...intlCar,
logo: 'benz', logo: 'benz',
price: 80 price: 80,
}; };
default: default:
return { return {
...intlCar, ...intlCar,
logo: 'audi', logo: 'audi',
price: 88 price: 88,
}; };
} }
}; };
@ -56,4 +56,34 @@ describe('useReducer Hook Test', () => {
expect(container.querySelector('p').innerHTML).toBe('audi'); expect(container.querySelector('p').innerHTML).toBe('audi');
expect(container.querySelector('#senP').innerHTML).toBe('88'); 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 (
<div>
<button ref={btnRef} onClick={() => dispatchLogging()}>
increment
</button>
<div>{data}</div>
</div>
);
};
Horizon.render(<Main />, container);
Horizon.act(() => {
btnRef.current.click();
});
expect(nextId).toBe(2);
});
}); });

View File

@ -1,5 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Dom Textarea should not incur unnecessary DOM mutations 1`] = `<textarea />`;
exports[`Dom Textarea should not incur unnecessary DOM mutations 2`] = `<textarea />`;

View File

@ -1,8 +1,17 @@
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';
describe('Keyboard Event', () => { describe('Keyboard Event', () => {
const LogUtils = getLogUtils(); const LogUtils = getLogUtils();
const getKeyboardEvent = (type, keyCode, code, charCode) => {
return new KeyboardEvent(type, {
keyCode: keyCode ?? undefined,
code: code ?? undefined,
charCode: charCode ?? undefined,
bubbles: true,
cancelable: true,
});
};
it('keydown,keypress,keyup的keycode,charcode', () => { it('keydown,keypress,keyup的keycode,charcode', () => {
const node = Horizon.render( const node = Horizon.render(
@ -16,26 +25,12 @@ describe('Keyboard Event', () => {
/>, />,
container, container,
); );
node.dispatchEvent( node.dispatchEvent(getKeyboardEvent('keydown', 50, 'Digit2'));
new KeyboardEvent('keydown', { node.dispatchEvent(getKeyboardEvent('keyup', 50, 'Digit2'));
keyCode: 50,
code: 'Digit2',
bubbles: true,
cancelable: true,
}),
);
node.dispatchEvent(
new KeyboardEvent('keyup', {
keyCode: 50,
code: 'Digit2',
bubbles: true,
cancelable: true,
}),
);
expect(LogUtils.getAndClear()).toEqual([ expect(LogUtils.getAndClear()).toEqual([
'onKeyDown: keycode: 50,charcode: 0', 'onKeyDown: keycode: 50,charcode: 0',
'onKeyUp: keycode: 50,charcode: 0' 'onKeyUp: keycode: 50,charcode: 0',
]); ]);
}); });
@ -48,17 +43,10 @@ describe('Keyboard Event', () => {
/>, />,
container, container,
); );
node.dispatchEvent( node.dispatchEvent(getKeyboardEvent('keypress', undefined, 'Digit2', 50));
new KeyboardEvent('keypress', {
charCode: 50,
code: 'Digit2',
bubbles: true,
cancelable: true,
}),
);
expect(LogUtils.getAndClear()).toEqual([ expect(LogUtils.getAndClear()).toEqual([
'onKeyPress: keycode: 0,charcode: 50' 'onKeyPress: keycode: 0,charcode: 50',
]); ]);
}); });
@ -71,15 +59,9 @@ describe('Keyboard Event', () => {
/>, />,
container, container,
); );
node.dispatchEvent( node.dispatchEvent(getKeyboardEvent('keypress', undefined, undefined, 13));
new KeyboardEvent('keypress', {
charCode: 13,
bubbles: true,
cancelable: true,
}),
);
expect(LogUtils.getAndClear()).toEqual([ expect(LogUtils.getAndClear()).toEqual([
'onKeyPress: keycode: 0,charcode: 13' 'onKeyPress: keycode: 0,charcode: 13',
]); ]);
}); });
@ -98,22 +80,8 @@ describe('Keyboard Event', () => {
/>, />,
container, container,
); );
node.dispatchEvent( node.dispatchEvent(getKeyboardEvent('keydown', undefined, 'Digit2'));
new KeyboardEvent('keydown', { node.dispatchEvent(getKeyboardEvent('keypress', undefined, 'Digit2', 50));
code: 'Digit2',
bubbles: true,
cancelable: true,
}),
);
node.dispatchEvent(
new KeyboardEvent('keypress', {
keyCode: 50,
code: 'Digit2',
bubbles: true,
cancelable: true,
}),
);
node.dispatchEvent( node.dispatchEvent(
new KeyboardEvent('keyup', { new KeyboardEvent('keyup', {
@ -126,7 +94,7 @@ describe('Keyboard Event', () => {
expect(LogUtils.getAndClear()).toEqual([ expect(LogUtils.getAndClear()).toEqual([
'onKeyDown: code: Digit2', 'onKeyDown: code: Digit2',
'onKeyPress: code: Digit2', 'onKeyPress: code: Digit2',
'onKeyUp: code: Digit2' 'onKeyUp: code: Digit2',
]); ]);
}); });
@ -149,32 +117,14 @@ describe('Keyboard Event', () => {
/>, />,
container, container,
); );
div.dispatchEvent(getKeyboardEvent('keydown', 40));
div.dispatchEvent(getKeyboardEvent('keyup', 40));
div.dispatchEvent(getKeyboardEvent('keypress', 40));
div.dispatchEvent(
new KeyboardEvent('keydown', {
keyCode: 40,
bubbles: true,
cancelable: true,
}),
);
div.dispatchEvent(
new KeyboardEvent('keyup', {
keyCode: 40,
bubbles: true,
cancelable: true,
}),
);
div.dispatchEvent(
new KeyboardEvent('keypress', {
charCode: 40,
bubbles: true,
cancelable: true,
}),
);
expect(LogUtils.getAndClear()).toEqual([ expect(LogUtils.getAndClear()).toEqual([
'keydown handle', 'keydown handle',
'keyup handle', 'keyup handle',
'keypress handle' 'keypress handle',
]); ]);
}); });
}); });

View File

@ -0,0 +1,11 @@
export const ActionType = {
Pending: 'PENDING',
Fulfilled: 'FULFILLED',
Rejected: 'REJECTED',
};
export const promise = store => next => action => {
//let result = next(action);
store._horizonXstore.$queue.dispatch(action);
return result;
};