Match-id-86ccf4dae9f9c5614127dc3e5ffc401effbf9913

This commit is contained in:
* 2023-07-26 15:43:17 +08:00
commit 65df4422f3
203 changed files with 4734 additions and 2307 deletions

View File

@ -1 +1 @@
{"fileVersion":"1","name":"Horizon","serviceId":"067e5bef6cd240ae9460229d107a99a6","description":"","version":"1.0.0","type":"microService","processes":{"Horizon":{"subscribes":[]}}}
{"fileVersion":"1","name":"Inula","serviceId":"067e5bef6cd240ae9460229d107a99a6","description":"","version":"1.0.0","type":"microService","processes":{"Inula":{"subscribes":[]}}}

View File

@ -20,8 +20,8 @@ module.exports = {
[
'@babel/plugin-transform-react-jsx',
{
pragma: 'Horizon.createElement',
pragmaFrag: 'Horizon.Fragment',
pragma: 'Inula.createElement',
pragmaFrag: 'Inula.Fragment',
},
],
['@babel/plugin-proposal-class-properties', { loose: true }],

View File

@ -26,8 +26,10 @@ module.exports = {
testEnvironment: 'jest-environment-jsdom-sixteen',
testMatch: [
// '<rootDir>/scripts/__tests__/InulaXTest/edgeCases/deepVariableObserver.test.tsx',
// '<rootDir>/scripts/__tests__/InulaXTest/StateManager/StateMap.test.tsx',
'<rootDir>/scripts/__tests__/**/*.test.js',
'<rootDir>/scripts/__tests__/**/*.test.tsx'
'<rootDir>/scripts/__tests__/**/*.test.tsx',
],
timers: 'fake',

View File

@ -1 +0,0 @@
# `horizon`

View File

@ -1,10 +0,0 @@
export const INITIALIZED = 'horizonx store initialized';
export const STATE_CHANGE = 'horizonx state change';
export const SUBSCRIBED = 'horizonx subscribed';
export const UNSUBSCRIBED = 'horizonx unsubscribed';
export const ACTION = 'horizonx action';
export const ACTION_QUEUED = 'horizonx action queued';
export const QUEUE_PENDING = 'horizonx queue pending';
export const QUEUE_FINISHED = 'horizonx queue finished';
export const RENDER_TRIGGERED = 'horizonx render triggered';
export const OBSERVED_COMPONENTS = 'horizonx observed components';

View File

@ -1,120 +0,0 @@
import { getStore, getAllStores } from '../store/StoreHandler';
import { OBSERVED_COMPONENTS } from './constants';
const sessionId = Date.now();
// this function is used to detect devtool connection
export function isPanelActive() {
return window['__HORIZON_DEV_HOOK__'];
}
// serializes store and creates expanded object with baked-in containing current computed values
function makeStoreSnapshot({ type, data }) {
const expanded = {};
Object.keys(data.store.$c).forEach(key => {
expanded[key] = data.store[key];
});
data.store.expanded = expanded;
const snapshot = makeProxySnapshot({
data,
type,
sessionId,
});
return snapshot;
}
// safely serializes variables containing values wrapped in Proxy object
function makeProxySnapshot(obj) {
let clone;
try {
if (!obj) {
return obj;
}
if (obj.nativeEvent) return obj.type + 'Event';
if (typeof obj === 'function') {
return obj.toString();
}
if (Array.isArray(obj)) {
clone = [];
obj.forEach(item => clone.push(makeProxySnapshot(item)));
return clone;
} else if (typeof obj === 'object') {
clone = {};
Object.entries(obj).forEach(([id, value]) => (clone[id] = makeProxySnapshot(value)));
return clone;
}
return obj;
} catch (err) {
throw console.log('cannot serialize object. ' + err);
}
}
export const devtools = {
// returns vNode id from horizon devtools
getVNodeId: vNode => {
if (!isPanelActive()) return;
window['__HORIZON_DEV_HOOK__'].send(); // update list first
return window['__HORIZON_DEV_HOOK__'].getVnodeId(vNode);
},
// sends horizonx devtool message to extension
emit: (type, data) => {
if (!isPanelActive()) return;
window.postMessage({
type: 'HORIZON_DEV_TOOLS',
payload: makeStoreSnapshot({ type, data }),
from: 'dev tool hook',
});
},
};
// collects components that are dependant on horizonx store and their ids
function getAffectedComponents() {
const allStores = getAllStores();
const keys = Object.keys(allStores);
let res = {};
keys.forEach(key => {
const subRes = new Set();
const process = Array.from(allStores[key].$config.state._horizonObserver.keyVNodes.values());
while (process.length) {
let pivot = process.shift();
if (pivot?.tag) subRes.add(pivot);
if (pivot?.toString() === '[object Set]') Array.from(pivot).forEach(item => process.push(item));
}
res[key] = Array.from(subRes).map(vnode => {
return {
name: vnode?.type
.toString()
.replace(/\{.*\}/, '{...}')
.replace('function ', ''),
nodeId: window.__HORIZON_DEV_HOOK__.getVnodeId(vnode),
};
});
});
return res;
}
// listens to messages from background
window.addEventListener('message', messageEvent => {
if (messageEvent?.data?.payload?.type === 'horizonx request observed components') {
// get observed components
setTimeout(() => {
window.postMessage({
type: 'HORIZON_DEV_TOOLS',
payload: { type: OBSERVED_COMPONENTS, data: getAffectedComponents() },
from: 'dev tool hook',
});
}, 100);
}
// executes store action
if (messageEvent.data?.payload?.type === 'horizonx executue action') {
const data = messageEvent.data.payload.data;
const store = getStore(data.storeId);
if (!store?.[data.action]) return;
const action = store[data.action];
const params = data.params;
action(...params);
}
});

View File

@ -1,85 +0,0 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* InulaJS is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { getObserver } from '../ProxyHandler';
import { isSame, isValidIntegerKey } from '../../CommonUtils';
import { get as objectGet } from './ObjectProxyHandler';
import { resolveMutation } from '../../CommonUtils';
import { isPanelActive } from '../../devtools';
export function createArrayProxy(rawObj: any[]): any[] {
const handle = {
get,
set,
};
return new Proxy(rawObj, handle);
}
function get(rawObj: any[], key: string, receiver: any) {
if (key === 'watch') {
const observer = getObserver(rawObj);
return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => {
if (!observer.watchers[prop]) {
observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[];
}
observer.watchers[prop].push(handler);
return () => {
observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler);
};
};
}
if (isValidIntegerKey(key) || key === 'length') {
return objectGet(rawObj, key, receiver);
}
return Reflect.get(rawObj, key, receiver);
}
function set(rawObj: any[], key: string, value: any, receiver: any) {
const oldValue = rawObj[key];
const oldLength = rawObj.length;
const newValue = value;
const oldArray = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
const ret = Reflect.set(rawObj, key, newValue, receiver);
const newLength = rawObj.length;
const observer = getObserver(rawObj);
const mutation = isPanelActive() ? resolveMutation(oldArray, rawObj) : { mutation: true, from: [], to: rawObj };
if (!isSame(newValue, oldValue)) {
// 值不一样,触发监听器
if (observer.watchers?.[key]) {
observer.watchers[key].forEach(cb => {
cb(key, oldValue, newValue, mutation);
});
}
// 触发属性变化
observer.setProp(key, mutation);
}
if (oldLength !== newLength) {
// 触发数组的大小变化
observer.setProp('length', mutation);
}
return ret;
}

View File

@ -1,235 +0,0 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* InulaJS is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
import { isMap, isWeakMap, isSame } from '../../CommonUtils';
import { resolveMutation } from '../../CommonUtils';
import { isPanelActive } from '../../devtools';
const COLLECTION_CHANGE = '_collectionChange';
const handler = {
get,
set,
add,
delete: deleteFun,
clear,
has,
entries,
forEach,
keys,
values,
// 判断Symbol类型兼容IE
[typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']: forOf,
};
export function createCollectionProxy(rawObj: Object, hookObserver = true): Object {
const boundHandler = {};
Object.entries(handler).forEach(([id, val]) => {
boundHandler[id] = (...args: any[]) => {
return (val as any)(...args, hookObserver);
};
});
return new Proxy(rawObj, { ...boundHandler });
}
function get(rawObj: { size: number }, key: any, receiver: any): any {
if (key === 'size') {
return size(rawObj);
} else if (key === 'get') {
return getFun.bind(null, rawObj);
} else if (Object.prototype.hasOwnProperty.call(handler, key)) {
const value = Reflect.get(handler, key, receiver);
return value.bind(null, rawObj);
} else if (key === 'watch') {
const observer = getObserver(rawObj);
return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => {
if (!observer.watchers[prop]) {
observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[];
}
observer.watchers[prop].push(handler);
return () => {
observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler);
};
};
}
return Reflect.get(rawObj, key, receiver);
}
function getFun(rawObj: { get: (key: any) => any }, key: any) {
const observer = getObserver(rawObj);
observer.useProp(key);
const value = rawObj.get(key);
// 对于value也需要进一步代理
const valProxy = createProxy(value, hookObserverMap.get(rawObj));
return valProxy;
}
// Map的set方法
function set(
rawObj: { get: (key: any) => any; set: (key: any, value: any) => any; has: (key: any) => boolean },
key: any,
value: any
) {
const oldValue = rawObj.get(key);
const newValue = value;
rawObj.set(key, newValue);
const valChange = !isSame(newValue, oldValue);
const observer = getObserver(rawObj);
const mutation = isPanelActive() ? resolveMutation(oldValue, rawObj) : { mutation: true, from: null, to: rawObj };
if (valChange || !rawObj.has(key)) {
observer.setProp(COLLECTION_CHANGE, mutation);
}
if (valChange) {
if (observer.watchers?.[key]) {
observer.watchers[key].forEach(cb => {
cb(key, oldValue, newValue, mutation);
});
}
observer.setProp(key, mutation);
}
return rawObj;
}
// Set的add方法
function add(rawObj: { add: (any) => void; set: (string, any) => any; has: (any) => boolean }, value: any): Object {
const oldCollection = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
if (!rawObj.has(value)) {
rawObj.add(value);
const observer = getObserver(rawObj);
const mutation = isPanelActive()
? resolveMutation(oldCollection, rawObj)
: { mutation: true, from: null, to: rawObj };
observer.setProp(value, mutation);
observer.setProp(COLLECTION_CHANGE, mutation);
}
return rawObj;
}
function has(rawObj: { has: (string) => boolean }, key: any): boolean {
const observer = getObserver(rawObj);
observer.useProp(key);
return rawObj.has(key);
}
function clear(rawObj: { size: number; clear: () => void }) {
const oldSize = rawObj.size;
rawObj.clear();
if (oldSize > 0) {
const observer = getObserver(rawObj);
observer.allChange();
}
}
function deleteFun(rawObj: { has: (key: any) => boolean; delete: (key: any) => void }, key: any) {
const oldCollection = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
if (rawObj.has(key)) {
rawObj.delete(key);
const observer = getObserver(rawObj);
const mutation = isPanelActive()
? resolveMutation(oldCollection, rawObj)
: { mutation: true, from: null, to: rawObj };
observer.setProp(key, mutation);
observer.setProp(COLLECTION_CHANGE, mutation);
return true;
}
return false;
}
function size(rawObj: { size: number }) {
const observer = getObserver(rawObj);
observer.useProp(COLLECTION_CHANGE);
return rawObj.size;
}
function keys(rawObj: { keys: () => { next: () => { value: any; done: boolean } } }) {
return wrapIterator(rawObj, rawObj.keys());
}
function values(rawObj: { values: () => { next: () => { value: any; done: boolean } } }) {
return wrapIterator(rawObj, rawObj.values());
}
function entries(rawObj: { entries: () => { next: () => { value: any; done: boolean } } }) {
return wrapIterator(rawObj, rawObj.entries(), true);
}
function forOf(rawObj: {
entries: () => { next: () => { value: any; done: boolean } };
values: () => { next: () => { value: any; done: boolean } };
}) {
const isMapType = isMap(rawObj) || isWeakMap(rawObj);
const iterator = isMapType ? rawObj.entries() : rawObj.values();
return wrapIterator(rawObj, iterator, isMapType);
}
function forEach(
rawObj: { forEach: (callback: (value: any, key: any) => void) => void },
callback: (valProxy: any, keyProxy: any, rawObj: any) => void
) {
const observer = getObserver(rawObj);
observer.useProp(COLLECTION_CHANGE);
rawObj.forEach((value, key) => {
const valProxy = createProxy(value, hookObserverMap.get(rawObj));
const keyProxy = createProxy(key, hookObserverMap.get(rawObj));
// 最后一个参数要返回代理对象
return callback(valProxy, keyProxy, rawObj);
});
}
function wrapIterator(rawObj: Object, rawIt: { next: () => { value: any; done: boolean } }, isPair = false) {
const observer = getObserver(rawObj);
const hookObserver = hookObserverMap.get(rawObj);
observer.useProp(COLLECTION_CHANGE);
return {
next() {
const { value, done } = rawIt.next();
if (done) {
return { value: createProxy(value, hookObserver), done };
}
observer.useProp(COLLECTION_CHANGE);
let newVal;
if (isPair) {
newVal = [createProxy(value[0], hookObserver), createProxy(value[1], hookObserver)];
} else {
newVal = createProxy(value, hookObserver);
}
return { value: newVal, done };
},
// 判断Symbol类型兼容IE
[typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']() {
return this;
},
};
}

View File

@ -1,95 +0,0 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* InulaJS is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { isSame, resolveMutation } from '../../CommonUtils';
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
import { OBSERVER_KEY } from '../../Constants';
import { isPanelActive } from '../../devtools';
export function createObjectProxy<T extends object>(rawObj: T, singleLevel = false): ProxyHandler<T> {
const proxy = new Proxy(rawObj, {
get: (...args) => get(...args, singleLevel),
set,
});
return proxy;
}
export function get(rawObj: object, key: string | symbol, receiver: any, singleLevel = false): any {
// The observer object of symbol ('_horizonObserver') cannot be accessed from Proxy to prevent errors caused by clonedeep.
if (key === OBSERVER_KEY) {
return undefined;
}
const observer = getObserver(rawObj);
if (key === 'watch') {
return (prop, handler: (key: string, oldValue: any, newValue: any) => void) => {
if (!observer.watchers[prop]) {
observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[];
}
observer.watchers[prop].push(handler);
return () => {
observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler);
};
};
}
if (key === 'addListener') {
return observer.addListener.bind(observer);
}
if (key === 'removeListener') {
return observer.removeListener.bind(observer);
}
observer.useProp(key);
const value = Reflect.get(rawObj, key, receiver);
// 对于prototype不做代理
if (key !== 'prototype') {
// 对于value也需要进一步代理
const valProxy = singleLevel ? value : createProxy(value, hookObserverMap.get(rawObj));
return valProxy;
}
return value;
}
export function set(rawObj: object, key: string, value: any, receiver: any): boolean {
const oldObject = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
const observer = getObserver(rawObj);
if (value && key == 'removeListener') {
observer.removeListener(value);
}
const oldValue = rawObj[key];
const newValue = value;
const ret = Reflect.set(rawObj, key, newValue, receiver);
const mutation = isPanelActive() ? resolveMutation(oldObject, rawObj) : { mutation: true, from: null, to: rawObj };
if (!isSame(newValue, oldValue)) {
if (observer.watchers?.[key]) {
observer.watchers[key].forEach(cb => {
cb(key, oldValue, newValue, mutation);
});
}
observer.setProp(key, mutation);
}
return ret;
}

1
libs/inula/README.md Normal file
View File

@ -0,0 +1 @@
# `inula`

View File

@ -19,3 +19,5 @@
declare var isDev: boolean;
declare var isTest: boolean;
declare const __VERSION__: string;
declare var setImmediate: Function;
declare var __INULA_DEV_HOOK__: any;

View File

@ -18,6 +18,8 @@ import {
TYPE_PROFILER as Profiler,
TYPE_STRICT_MODE as StrictMode,
TYPE_SUSPENSE as Suspense,
TYPE_FORWARD_REF as ForwardRef,
TYPE_MEMO as Memo,
} from './src/external/JSXElementType';
import { Component, PureComponent } from './src/renderer/components/BaseClassComponent';
@ -42,9 +44,6 @@ import {
useState,
useDebugValue,
} from './src/renderer/hooks/HookExternal';
import { asyncUpdates } from './src/renderer/TreeBuilder';
import { callRenderQueueImmediate } from './src/renderer/taskExecutor/RenderQueue';
import { runAsyncEffects } from './src/renderer/submit/HookEffectHandler';
import {
isContextProvider,
isContextConsumer,
@ -55,17 +54,11 @@ import {
isLazy,
isMemo,
isPortal,
} from './src/external/HorizonIs';
import { createStore, useStore, clearStore } from './src/horizonx/store/StoreHandler';
import * as reduxAdapter from './src/horizonx/adapters/redux';
import { watch } from './src/horizonx/proxy/watch';
// act用于测试作用是如果fun触发了刷新包含了异步刷新可以保证在act后面的代码是在刷新完成后才执行。
const act = fun => {
asyncUpdates(fun);
callRenderQueueImmediate();
runAsyncEffects();
};
} from './src/external/InulaIs';
import { createStore, useStore, clearStore } from './src/inulax/store/StoreHandler';
import * as reduxAdapter from './src/inulax/adapters/redux';
import { watch } from './src/inulax/proxy/watch';
import { act } from './src/external/TestUtil';
import {
render,
@ -75,7 +68,10 @@ import {
unmountComponentAtNode,
} from './src/dom/DOMExternal';
const Horizon = {
import { syncUpdates as flushSync } from './src/renderer/TreeBuilder';
import { toRaw } from './src/inulax/proxy/ProxyHandler';
const Inula = {
Children,
createRef,
Component,
@ -94,10 +90,6 @@ const Horizon = {
useReducer,
useRef,
useState,
Fragment,
Profiler,
StrictMode,
Suspense,
createElement,
cloneElement,
isValidElement,
@ -107,6 +99,7 @@ const Horizon = {
findDOMNode,
unmountComponentAtNode,
act,
flushSync,
createStore,
useStore,
clearStore,
@ -121,6 +114,12 @@ const Horizon = {
isPortal,
isContextProvider,
isContextConsumer,
ForwardRef,
Memo,
Fragment,
Profiler,
StrictMode,
Suspense,
};
export const version = __VERSION__;
@ -143,10 +142,6 @@ export {
useReducer,
useRef,
useState,
Fragment,
Profiler,
StrictMode,
Suspense,
createElement,
cloneElement,
isValidElement,
@ -156,12 +151,14 @@ export {
findDOMNode,
unmountComponentAtNode,
act,
// 状态管理器HorizonX接口
flushSync,
// 状态管理器InulaX接口
createStore,
useStore,
clearStore,
reduxAdapter,
watch,
toRaw,
// 兼容ReactIs
isFragment,
isElement,
@ -172,6 +169,12 @@ export {
isPortal,
isContextProvider,
isContextConsumer,
ForwardRef,
Memo,
Fragment,
Profiler,
StrictMode,
Suspense,
};
export default Horizon;
export default Inula;

View File

@ -1,7 +1,7 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* InulaJS is licensed under Mulan PSL v2.
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
@ -13,11 +13,12 @@
* See the Mulan PSL v2 for more details.
*/
import { TYPE_FORWARD_REF } from '../../external/JSXElementType';
import {
TYPE_FRAGMENT as Fragment,
} from './src/external/JSXElementType';
import { jsx as jsxDEV } from './src/external/JSXElement';
export function forwardRef(render: Function) {
return {
vtype: TYPE_FORWARD_REF,
render,
};
}
export {
jsxDEV,
Fragment
};

View File

@ -1,7 +1,7 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* InulaJS is licensed under Mulan PSL v2.
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
@ -13,13 +13,13 @@
* See the Mulan PSL v2 for more details.
*/
export function isInputElement(dom?: HTMLElement): boolean {
return dom instanceof HTMLInputElement || dom instanceof HTMLTextAreaElement;
}
import {
TYPE_FRAGMENT as Fragment,
} from './src/external/JSXElementType';
import { jsx, jsx as jsxs } from './src/external/JSXElement';
export function setPropertyWritable(obj, propName) {
const desc = Object.getOwnPropertyDescriptor(obj, propName);
if (!desc || !desc.writable) {
Object.defineProperty(obj, propName, { writable: true });
}
}
export {
jsx,
jsxs,
Fragment
};

View File

@ -16,7 +16,7 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/horizon.production.min.js');
module.exports = require('./cjs/inula.production.min.js');
} else {
module.exports = require('./cjs/horizon.development.js');
module.exports = require('./cjs/inula.development.js');
}

View File

@ -1,10 +1,10 @@
{
"name": "@cloudsop/horizon",
"description": "Horizon is a JavaScript framework library.",
"name": "inulajs",
"description": "InulaJS is a JavaScript framework library.",
"keywords": [
"horizon"
"inula"
],
"version": "0.0.33",
"version": "0.0.52",
"homepage": "",
"bugs": "",
"main": "index.js",

View File

@ -19,6 +19,7 @@ import type { Container } from './DOMOperator';
import { isElement } from './utils/Common';
import { findDOMByClassInst } from '../renderer/vnode/VNodeUtils';
import { Callback } from '../renderer/UpdateHandler';
import { listenSimulatedDelegatedEvents } from '../event/EventBinding';
function createRoot(children: any, container: Container, callback?: Callback) {
// 清空容器
@ -31,6 +32,7 @@ function createRoot(children: any, container: Container, callback?: Callback) {
// 调度器创建根节点并给容器dom赋vNode结构体
const treeRoot = createTreeRootVNode(container);
container._treeRoot = treeRoot;
listenSimulatedDelegatedEvents(treeRoot);
// 执行回调
if (typeof callback === 'function') {
@ -71,7 +73,7 @@ function executeRender(children: any, container: Container, callback?: Callback)
}
function findDOMNode(domOrEle?: Element): null | Element | Text {
if (domOrEle == null) {
if (domOrEle === null || domOrEle === undefined) {
return null;
}
@ -101,7 +103,7 @@ function removeRootEventLister(container: Container) {
// 卸载入口
function destroy(container: Container): boolean {
if (container && container._treeRoot) {
if (container._treeRoot) {
syncUpdates(() => {
executeRender(null, container, () => {
removeRootEventLister(container);

View File

@ -22,9 +22,9 @@ import type { Container, Props } from './DOMOperator';
import { DomComponent, DomText, TreeRoot } from '../renderer/vnode/VNodeTags';
const INTERNAL_VNODE = '_horizon_VNode';
const INTERNAL_PROPS = '_horizon_Props';
const INTERNAL_NONDELEGATEEVENTS = '_horizon_NonDelegatedEvents';
const INTERNAL_VNODE = '_inula_VNode';
const INTERNAL_PROPS = '_inula_Props';
const INTERNAL_NONDELEGATEEVENTS = '_inula_NonDelegatedEvents';
// 通过 VNode 实例获取 DOM 节点
export function getDom(vNode: VNode): Element | Text | null {

View File

@ -16,7 +16,7 @@
import { saveVNode, updateVNodeProps } from './DOMInternalKeys';
import { createDom } from './utils/DomCreator';
import { getSelectionInfo, resetSelectionRange, SelectionData } from './SelectionRangeHandler';
import { shouldAutoFocus } from './utils/Common';
import { isDocument, shouldAutoFocus } from './utils/Common';
import { NSS } from './utils/DomCreator';
import { adjustStyleValue } from './DOMPropertiesHandler/StyleHandler';
import type { VNode } from '../renderer/Types';
@ -26,6 +26,7 @@ import { isNativeElement, validateProps } from './validators/ValidateProps';
import { watchValueChange } from './valueHandler/ValueChangeHandler';
import { DomComponent, DomText } from '../renderer/vnode/VNodeTags';
import { updateCommonProp } from './DOMPropertiesHandler/UpdateCommonProp';
import {getCurrentRoot} from '../renderer/RootStack';
export type Props = Record<string, any> & {
autoFocus?: boolean;
@ -45,7 +46,7 @@ function getChildNS(parentNS: string | null, tagName: string): string {
return NSS.html;
}
if (parentNS == null || parentNS === NSS.html) {
if (parentNS === null || parentNS === NSS.html) {
// 没有父命名空间或父命名空间为xhtml
return NSS[tagName] ?? NSS.html;
}
@ -70,7 +71,12 @@ export function resetAfterSubmit(): void {
// 创建 DOM 对象
export function newDom(tagName: string, props: Props, parentNamespace: string, vNode: VNode): Element {
const dom: Element = createDom(tagName, parentNamespace);
// document取值于treeRoot对应的DOM的ownerDocument。
// 解决在iframe中使用top的inula时inula在创建DOM时用到的document并不是iframe的document而是top中的document的问题。
const rootDom = getCurrentRoot().realNode;
const doc = isDocument(rootDom) ? rootDom : rootDom.ownerDocument;
const dom: Element = createDom(tagName, parentNamespace, doc);
// 将 vNode 节点挂到 DOM 对象上
saveVNode(vNode, dom);
// 将属性挂到 DOM 对象上
@ -124,7 +130,8 @@ export function isTextChild(type: string, props: Props): boolean {
return (
props.dangerouslySetInnerHTML &&
typeof props.dangerouslySetInnerHTML === 'object' &&
props.dangerouslySetInnerHTML.__html != null
props.dangerouslySetInnerHTML.__html !== null &&
props.dangerouslySetInnerHTML.__html !== undefined
);
}
}
@ -135,14 +142,14 @@ export function newTextDom(text: string, processing: VNode): Text {
return textNode;
}
// 提交vNode的类型为Component或者Text的更新
// 提交vNode的类型为DomComponent或者DomText的更新
export function submitDomUpdate(tag: string, vNode: VNode) {
const newProps = vNode.props;
const element: Element | null = vNode.realNode;
if (tag === DomComponent) {
// DomComponent类型
if (element != null) {
if (element !== null && element !== undefined) {
const type = vNode.type;
const changeList = vNode.changeList;
vNode.changeList = null;
@ -152,7 +159,14 @@ export function submitDomUpdate(tag: string, vNode: VNode) {
updateVNodeProps(element, newProps);
// 应用diff更新Properties.
// 当一个选中的radio改变名称,浏览器使另一个radio的复选框为false.
if (type === 'input' && newProps.type === 'radio' && newProps.name != null && newProps.checked != null) {
if (
type === 'input'
&& newProps.type === 'radio'
&& newProps.name !== null
&& newProps.name !== undefined
&& newProps.checked !== null
&& newProps.checked !== undefined
) {
updateCommonProp(element, 'checked', newProps.checked, true);
}
const isNativeTag = isNativeElement(type, newProps);
@ -196,7 +210,7 @@ export function hideDom(tag: string, dom: Element | Text) {
}
// 不隐藏元素
export function unHideDom(tag: string, dom: Element | Text, props: Props) {
export function unHideDom(tag: string, dom: Element | Text, props?: Props) {
if (tag === DomComponent) {
dom.style.display = adjustStyleValue('display', props?.style?.display ?? '');
} else if (tag === DomText) {

View File

@ -13,7 +13,7 @@
* See the Mulan PSL v2 for more details.
*/
import { allDelegatedHorizonEvents } from '../../event/EventHub';
import { allDelegatedInulaEvents } from '../../event/EventHub';
import { updateCommonProp } from './UpdateCommonProp';
import { setStyles } from './StyleHandler';
import { lazyDelegateOnRoot, listenNonDelegatedEvent } from '../../event/EventBinding';
@ -35,7 +35,7 @@ export function setDomProps(dom: Element, props: Object, isNativeTag: boolean, i
} else if (isEventProp(propName)) {
// 事件监听属性处理
const currentRoot = getCurrentRoot();
if (!allDelegatedHorizonEvents.has(propName)) {
if (!allDelegatedInulaEvents.has(propName)) {
listenNonDelegatedEvent(propName, dom, propVal);
} else if (currentRoot && !currentRoot.delegatedEvents.has(propName)) {
lazyDelegateOnRoot(currentRoot, propName);
@ -48,7 +48,7 @@ export function setDomProps(dom: Element, props: Object, isNativeTag: boolean, i
}
} else if (propName === 'dangerouslySetInnerHTML') {
dom.innerHTML = propVal.__html;
} else if (!isInit || (isInit && propVal != null)) {
} else if (!isInit || (propVal !== null && propVal !== undefined)) {
updateCommonProp(dom, propName, propVal, isNativeTag);
}
}
@ -70,7 +70,7 @@ export function compareProps(oldProps: Object, newProps: Object): Object {
for (let i = 0; i < oldPropsLength; i++) {
propName = keysOfOldProps[i];
// 新属性中包含该属性或者该属性为空值的属性不需要处理
if (keysOfNewProps.includes(propName) || oldProps[propName] == null) {
if ( oldProps[propName] === null || oldProps[propName] === undefined || keysOfNewProps.includes(propName)) {
continue;
}
@ -84,7 +84,7 @@ export function compareProps(oldProps: Object, newProps: Object): Object {
} else if (propName === 'autoFocus' || propName === 'children' || propName === 'dangerouslySetInnerHTML') {
continue;
} else if (isEventProp(propName)) {
if (!allDelegatedHorizonEvents.has(propName)) {
if (!allDelegatedInulaEvents.has(propName)) {
toUpdateProps[propName] = null;
}
} else {
@ -103,9 +103,13 @@ export function compareProps(oldProps: Object, newProps: Object): Object {
for (let i = 0; i < keysOfNewProps.length; i++) {
propName = keysOfNewProps[i];
newPropValue = newProps[propName];
oldPropValue = oldProps != null ? oldProps[propName] : null;
oldPropValue = oldProps !== null && oldProps !== undefined ? oldProps[propName] : null;
if (newPropValue === oldPropValue || (newPropValue == null && oldPropValue == null)) {
if (
newPropValue === oldPropValue
|| ((newPropValue === null || newPropValue === undefined)
&& (oldPropValue === null || oldPropValue === undefined))
) {
// 新旧属性值未发生变化,或者新旧属性皆为空值,不需要进行处理
continue;
}
@ -140,7 +144,7 @@ export function compareProps(oldProps: Object, newProps: Object): Object {
} else if (propName === 'dangerouslySetInnerHTML') {
newHTML = newPropValue ? newPropValue.__html : undefined;
oldHTML = oldPropValue ? oldPropValue.__html : undefined;
if (newHTML != null) {
if (newHTML !== null && newHTML !== undefined) {
if (oldHTML !== newHTML) {
toUpdateProps[propName] = newPropValue;
}
@ -151,7 +155,7 @@ export function compareProps(oldProps: Object, newProps: Object): Object {
}
} else if (isEventProp(propName)) {
const currentRoot = getCurrentRoot();
if (!allDelegatedHorizonEvents.has(propName)) {
if (!allDelegatedInulaEvents.has(propName)) {
toUpdateProps[propName] = newPropValue;
} else if (currentRoot && !currentRoot.delegatedEvents.has(propName)) {
lazyDelegateOnRoot(currentRoot, propName);

View File

@ -13,6 +13,37 @@
* See the Mulan PSL v2 for more details.
*/
/**
* css
*/
const noUnitCSS = [
'animationIterationCount',
'columnCount',
'columns',
'gridArea',
'fontWeight',
'lineClamp',
'lineHeight',
'opacity',
'order',
'orphans',
'tabSize',
'widows',
'zIndex',
'zoom',
];
const length = noUnitCSS.length;
for (let i = 0; i < length; i++) {
const cssKey = noUnitCSS[i];
const attributeKey = cssKey.charAt(0).toUpperCase() + cssKey.slice(1);
// css 兼容性前缀 webkit: chrome, mo: IE或者Edge, Moz: 火狐
noUnitCSS.push('Webkit' + attributeKey);
noUnitCSS.push('mo' + attributeKey);
noUnitCSS.push('Moz' + attributeKey);
}
function isNeedUnitCSS(styleName: string) {
return !(
noUnitCSS.includes(styleName) ||
@ -36,7 +67,7 @@ export function adjustStyleValue(name, value) {
if (typeof value === 'number' && value !== 0 && isNeedUnitCSS(name)) {
validValue = `${value}px`;
} else if (value === '' || value == null || typeof value === 'boolean') {
} else if (value === '' || value === null || value === undefined || typeof value === 'boolean') {
validValue = '';
}
@ -54,27 +85,12 @@ export function setStyles(dom, styles) {
const style = dom.style;
Object.keys(styles).forEach(name => {
const styleVal = styles[name];
style[name] = adjustStyleValue(name, styleVal);
// 以--开始的样式直接设置即可
if (name.indexOf('--') === 0) {
style.setProperty(name, styleVal);
} else {
// 使用这种赋值方式,浏览器可以将'WebkitLineClamp' 'backgroundColor'分别识别为'-webkit-line-clamp'和'backgroud-color'
style[name] = adjustStyleValue(name, styleVal);
}
});
}
/**
* css
*/
const noUnitCSS = [
'animationIterationCount',
'columnCount',
'columns',
'gridArea',
'fontWeight',
'lineClamp',
'lineHeight',
'opacity',
'order',
'orphans',
'tabSize',
'widows',
'zIndex',
'zoom',
];

View File

@ -30,7 +30,7 @@ function setSelectionRange(dom: HTMLInputElement | HTMLTextAreaElement, range) {
const { start, end } = range;
let realEnd = end;
if (realEnd == null) {
if (realEnd === null || realEnd === undefined) {
realEnd = start;
}

View File

@ -13,14 +13,14 @@
* See the Mulan PSL v2 for more details.
*/
import { HorizonDom } from './Interface';
import { InulaDom } from './Interface';
import { Props } from '../DOMOperator';
/**
* input textarea
* @param doc document
*/
export function getFocusedDom(doc?: Document): HorizonDom | null {
export function getFocusedDom(doc?: Document): InulaDom | null {
const currentDocument = doc ?? document;
return currentDocument.activeElement ?? currentDocument.body;
@ -84,3 +84,7 @@ const types = ['button', 'input', 'select', 'textarea'];
export function shouldAutoFocus(tagName: string, props: Props): boolean {
return types.includes(tagName) ? Boolean(props.autoFocus) : false;
}
export function isNotNull(object: any): boolean {
return object !== null && object !== undefined;
}

View File

@ -20,15 +20,15 @@ export const NSS = {
};
// 创建DOM元素
export function createDom(tagName: string, parentNamespace: string): Element {
export function createDom(tagName: string, parentNamespace: string, doc: Document): Element {
let dom: Element;
const selfNamespace = NSS[tagName] || NSS.html;
const ns = parentNamespace !== NSS.html ? parentNamespace : selfNamespace;
if (ns !== NSS.html) {
dom = document.createElementNS(ns, tagName);
dom = doc.createElementNS(ns, tagName);
} else {
dom = document.createElement(tagName);
dom = doc.createElement(tagName);
}
return dom;
}

View File

@ -17,8 +17,8 @@ export interface Props {
[propName: string]: any;
}
export interface HorizonSelect extends HTMLSelectElement {
export interface InulaSelect extends HTMLSelectElement {
_multiple?: boolean;
}
export type HorizonDom = Element | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
export type InulaDom = Element | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;

View File

@ -73,7 +73,7 @@ export function isInvalidValue(
propDetails: PropDetails | null,
isNativeTag: boolean
): boolean {
if (value == null) {
if (value === null || value === undefined) {
return true;
}
@ -98,13 +98,13 @@ export function validateProps(type, props) {
return;
}
// 非内置的变迁
// 非内置的元素
if (!isNativeElement(type, props)) {
return;
}
// style属性必须是对象
if (props.style != null && typeof props.style !== 'object') {
if (props.style !== null && props.style !== undefined && typeof props.style !== 'object') {
throw new Error('style should be a object.');
}

View File

@ -29,7 +29,7 @@ function getInitValue(dom: HTMLInputElement, props: Props) {
export function getInputPropsWithoutValue(dom: HTMLInputElement, props: Props) {
// checked属于必填属性无法置
let { checked } = props;
if (checked == null) {
if (checked === undefined) {
checked = getInitValue(dom, props).initChecked;
}
@ -45,12 +45,12 @@ export function getInputPropsWithoutValue(dom: HTMLInputElement, props: Props) {
export function updateInputValue(dom: HTMLInputElement, props: Props) {
const { value, checked } = props;
if (value != null) {
if (value !== undefined) {
// 处理 dom.value 逻辑
if (dom.value !== String(value)) {
dom.value = String(value);
}
} else if (checked != null) {
} else if (checked !== undefined) {
updateCommonProp(dom, 'checked', checked, true);
}
}
@ -60,7 +60,7 @@ export function setInitInputValue(dom: HTMLInputElement, props: Props) {
const { value, defaultValue } = props;
const { initValue, initChecked } = getInitValue(dom, props);
if (value != null || defaultValue != null) {
if (value !== undefined || defaultValue !== undefined) {
// value 的使用优先级 value 属性 > defaultValue 属性 > 空字符串
const initValueStr = String(initValue);

View File

@ -13,7 +13,7 @@
* See the Mulan PSL v2 for more details.
*/
import { HorizonSelect, Props } from '../utils/Interface';
import { InulaSelect, Props } from '../utils/Interface';
function updateMultipleValue(options, newValues) {
const newValueSet = new Set();
@ -54,14 +54,14 @@ function updateValue(options, newValues: any, isMultiple: boolean) {
}
}
export function getSelectPropsWithoutValue(dom: HorizonSelect, properties: Object) {
export function getSelectPropsWithoutValue(dom: InulaSelect, properties: Object) {
return {
...properties,
value: undefined,
};
}
export function updateSelectValue(dom: HorizonSelect, props: Props, isInit = false) {
export function updateSelectValue(dom: InulaSelect, props: Props, isInit = false) {
const { value, defaultValue, multiple } = props;
const oldMultiple = dom._multiple !== undefined ? dom._multiple : dom.multiple;
@ -69,18 +69,18 @@ export function updateSelectValue(dom: HorizonSelect, props: Props, isInit = fal
dom._multiple = newMultiple;
// 设置了 value 属性
if (value != null) {
if (value !== null && value !== undefined) {
updateValue(dom.options, value, newMultiple);
} else if (oldMultiple !== newMultiple) {
// 修改了 multiple 属性
// 切换 multiple 之后,如果设置了 defaultValue 需要重新应用
if (defaultValue != null) {
if (defaultValue !== null && defaultValue !== undefined) {
updateValue(dom.options, defaultValue, newMultiple);
} else {
// 恢复到未选定状态
updateValue(dom.options, newMultiple ? [] : '', newMultiple);
}
} else if (isInit && defaultValue != null) {
} else if (isInit && defaultValue !== null && defaultValue !== undefined) {
// 设置了 defaultValue 属性
updateValue(dom.options, defaultValue, newMultiple);
}

View File

@ -19,7 +19,7 @@ import { Props } from '../utils/Interface';
function getInitValue(props: Props) {
const { value } = props;
if (value == null) {
if (value === undefined) {
const { defaultValue, children } = props;
let initValue = defaultValue;
@ -30,7 +30,7 @@ function getInitValue(props: Props) {
}
// defaultValue 属性未配置,置为空字符串
initValue = initValue != null ? initValue : '';
initValue = initValue ?? '';
return initValue;
} else {
return value;

View File

@ -14,7 +14,7 @@
*/
/**
* Horizon的输入框和文本框的change事件在原生的change事件上做了一层处理
* Inula的输入框和文本框的change事件在原生的change事件上做了一层处理
* change事件
*/

View File

@ -18,21 +18,21 @@
*
*/
import { HorizonDom, HorizonSelect, Props } from '../utils/Interface';
import { InulaDom, InulaSelect, Props } from '../utils/Interface';
import { getInputPropsWithoutValue, setInitInputValue, updateInputValue } from './InputValueHandler';
import { getOptionPropsWithoutValue } from './OptionValueHandler';
import { getSelectPropsWithoutValue, updateSelectValue } from './SelectValueHandler';
import { getTextareaPropsWithoutValue, updateTextareaValue } from './TextareaValueHandler';
// 获取元素除了被代理的值以外的属性
function getPropsWithoutValue(type: string, dom: HorizonDom, props: Props) {
function getPropsWithoutValue(type: string, dom: InulaDom, props: Props) {
switch (type) {
case 'input':
return getInputPropsWithoutValue(<HTMLInputElement>dom, props);
case 'option':
return getOptionPropsWithoutValue(dom, props);
case 'select':
return getSelectPropsWithoutValue(<HorizonSelect>dom, props);
return getSelectPropsWithoutValue(<InulaSelect>dom, props);
case 'textarea':
return getTextareaPropsWithoutValue(<HTMLTextAreaElement>dom, props);
default:
@ -41,13 +41,13 @@ function getPropsWithoutValue(type: string, dom: HorizonDom, props: Props) {
}
// 其它属性挂载完成后处理被代理值相关的属性
function setInitValue(type: string, dom: HorizonDom, props: Props) {
function setInitValue(type: string, dom: InulaDom, props: Props) {
switch (type) {
case 'input':
setInitInputValue(<HTMLInputElement>dom, props);
break;
case 'select':
updateSelectValue(<HorizonSelect>dom, props, true);
updateSelectValue(<InulaSelect>dom, props, true);
break;
case 'textarea':
updateTextareaValue(<HTMLTextAreaElement>dom, props, true);
@ -58,13 +58,13 @@ function setInitValue(type: string, dom: HorizonDom, props: Props) {
}
// 更新需要适配的属性
function updateValue(type: string, dom: HorizonDom, props: Props) {
function updateValue(type: string, dom: InulaDom, props: Props) {
switch (type) {
case 'input':
updateInputValue(<HTMLInputElement>dom, props);
break;
case 'select':
updateSelectValue(<HorizonSelect>dom, props);
updateSelectValue(<InulaSelect>dom, props);
break;
case 'textarea':
updateTextareaValue(<HTMLTextAreaElement>dom, props);

View File

@ -16,16 +16,14 @@
/**
*
*/
import { allDelegatedHorizonEvents, allDelegatedNativeEvents } from './EventHub';
import { allDelegatedInulaEvents, simulatedDelegatedEvents } from './EventHub';
import { isDocument } from '../dom/utils/Common';
import { getNearestVNode, getNonDelegatedListenerMap } from '../dom/DOMInternalKeys';
import { asyncUpdates, runDiscreteUpdates } from '../renderer/TreeBuilder';
import { handleEventMain } from './HorizonEventMain';
import { handleEventMain } from './InulaEventMain';
import { decorateNativeEvent } from './EventWrapper';
import { VNode } from '../renderer/vnode/VNode';
const listeningMarker = '_horizonListening' + Math.random().toString(36).slice(4);
// 触发委托事件
function triggerDelegatedEvent(
nativeEvtName: string,
@ -56,37 +54,48 @@ function listenToNativeEvent(nativeEvtName: string, delegatedElement: Element, i
return listener;
}
// 是否捕获事件
function isCaptureEvent(inulaEventName) {
if (inulaEventName === 'onLostPointerCapture' || inulaEventName === 'onGotPointerCapture') {
return false;
}
return inulaEventName.slice(-7) === 'Capture';
}
// 事件懒委托,当用户定义事件后,再进行委托到根节点
export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) {
currentRoot.delegatedEvents.add(eventName);
const isCapture = isCaptureEvent(eventName);
const nativeEvents = allDelegatedHorizonEvents.get(eventName);
const nativeEvents = allDelegatedInulaEvents.get(eventName);
nativeEvents.forEach(nativeEvent => {
const nativeFullName = isCapture ? nativeEvent + 'capture' : nativeEvent;
// 事件存储在DOM节点属性避免多个VNode(root和portal)对应同一个DOM, 造成事件重复监听
let events = currentRoot.realNode.$EV;
if (!events) {
events = (currentRoot.realNode as any).$EV = {};
}
currentRoot.realNode.$EV = currentRoot.realNode.$EV ?? {};
const events = currentRoot.realNode.$EV;
if (!events[nativeFullName]) {
const listener = listenToNativeEvent(nativeEvent, currentRoot.realNode, isCapture);
events[nativeFullName] = listener;
events[nativeFullName] = listenToNativeEvent(nativeEvent, currentRoot.realNode, isCapture);
}
});
}
// 通过horizon事件名获取到native事件名
function getNativeEvtName(horizonEventName, capture) {
// 利用冒泡事件模拟不冒泡事件,需要直接在根节点绑定
export function listenSimulatedDelegatedEvents(root: VNode) {
for (let i = 0; i < simulatedDelegatedEvents.length; i++) {
lazyDelegateOnRoot(root, simulatedDelegatedEvents[i]);
}
}
// 通过inula事件名获取到native事件名
function getNativeEvtName(inulaEventName, capture) {
let nativeName;
if (capture) {
nativeName = horizonEventName.slice(2, -7);
nativeName = inulaEventName.slice(2, -7);
} else {
nativeName = horizonEventName.slice(2);
nativeName = inulaEventName.slice(2);
}
if (!nativeName) {
return '';
@ -94,18 +103,10 @@ function getNativeEvtName(horizonEventName, capture) {
return nativeName.toLowerCase();
}
// 是否捕获事件
function isCaptureEvent(horizonEventName) {
if (horizonEventName === 'onLostPointerCapture' || horizonEventName === 'onGotPointerCapture') {
return false;
}
return horizonEventName.slice(-7) === 'Capture';
}
// 封装监听函数
function getWrapperListener(horizonEventName, nativeEvtName, targetElement, listener) {
function getWrapperListener(inulaEventName, nativeEvtName, targetElement, listener) {
return event => {
const customEvent = decorateNativeEvent(horizonEventName, nativeEvtName, event);
const customEvent = decorateNativeEvent(inulaEventName, nativeEvtName, event);
asyncUpdates(() => {
listener(customEvent);
});
@ -113,16 +114,16 @@ function getWrapperListener(horizonEventName, nativeEvtName, targetElement, list
}
// 非委托事件单独监听到各自dom节点
export function listenNonDelegatedEvent(horizonEventName: string, domElement: Element, listener): void {
const isCapture = isCaptureEvent(horizonEventName);
const nativeEvtName = getNativeEvtName(horizonEventName, isCapture);
export function listenNonDelegatedEvent(inulaEventName: string, domElement: Element, listener): void {
const isCapture = isCaptureEvent(inulaEventName);
const nativeEvtName = getNativeEvtName(inulaEventName, isCapture);
// 先判断是否存在老的监听事件,若存在则移除
const nonDelegatedListenerMap = getNonDelegatedListenerMap(domElement);
const currentListener = nonDelegatedListenerMap.get(horizonEventName);
const currentListener = nonDelegatedListenerMap.get(inulaEventName);
if (currentListener) {
domElement.removeEventListener(nativeEvtName, currentListener);
nonDelegatedListenerMap.delete(horizonEventName);
nonDelegatedListenerMap.delete(inulaEventName);
}
if (typeof listener !== 'function') {
@ -130,8 +131,8 @@ export function listenNonDelegatedEvent(horizonEventName: string, domElement: El
}
// 为了和委托事件对外行为一致将事件对象封装成CustomBaseEvent
const wrapperListener = getWrapperListener(horizonEventName, nativeEvtName, domElement, listener);
const wrapperListener = getWrapperListener(inulaEventName, nativeEvtName, domElement, listener);
// 添加新的监听
nonDelegatedListenerMap.set(horizonEventName, wrapperListener);
nonDelegatedListenerMap.set(inulaEventName, wrapperListener);
domElement.addEventListener(nativeEvtName, wrapperListener, isCapture);
}

View File

@ -13,13 +13,16 @@
* See the Mulan PSL v2 for more details.
*/
// 需要委托的horizon事件和原生事件对应关系
export const allDelegatedHorizonEvents = new Map();
// 需要委托的inula事件和原生事件对应关系
export const allDelegatedInulaEvents = new Map();
// 模拟委托事件,不冒泡事件需要利用其他事件来触发冒泡过程
export const simulatedDelegatedEvents = ['onMouseEnter', 'onMouseLeave'];
// 所有委托的原生事件集合
export const allDelegatedNativeEvents = new Set();
// Horizon事件和原生事件对应关系
export const horizonEventToNativeMap = new Map([
// Inula事件和原生事件对应关系
export const inulaEventToNativeMap = new Map([
['onKeyPress', ['keypress']],
['onTextInput', ['textInput']],
['onClick', ['click']],
@ -49,13 +52,15 @@ export const horizonEventToNativeMap = new Map([
['onCompositionUpdate', ['compositionupdate']],
['onChange', ['change', 'click', 'focusout', 'input']],
['onSelect', ['select']],
['onMouseEnter', ['mouseout', 'mouseover']],
['onMouseLeave', ['mouseout', 'mouseover']],
['onAnimationEnd', ['animationend']],
['onAnimationIteration', ['animationiteration']],
['onAnimationStart', ['animationstart']],
['onTransitionEnd', ['transitionend']],
]);
export const NativeEventToHorizonMap = {
export const NativeEventToInulaMap = {
click: 'click',
wheel: 'wheel',
dblclick: 'doubleClick',
@ -94,17 +99,17 @@ export const EVENT_TYPE_BUBBLE = 'Bubble';
export const EVENT_TYPE_CAPTURE = 'Capture';
export const EVENT_TYPE_ALL = 'All';
horizonEventToNativeMap.forEach((dependencies, horizonEvent) => {
allDelegatedHorizonEvents.set(horizonEvent, dependencies);
allDelegatedHorizonEvents.set(horizonEvent + 'Capture', dependencies);
inulaEventToNativeMap.forEach((dependencies, inulaEvent) => {
allDelegatedInulaEvents.set(inulaEvent, dependencies);
allDelegatedInulaEvents.set(inulaEvent + 'Capture', dependencies);
dependencies.forEach(d => {
allDelegatedNativeEvents.add(d);
});
});
export function transformToHorizonEvent(nativeEvtName: string) {
const name = NativeEventToHorizonMap[nativeEvtName];
export function transformToInulaEvent(nativeEvtName: string) {
const name = NativeEventToInulaMap[nativeEvtName];
// 例dragEnd -> onDragEnd
return !name ? '' : `on${name[0].toUpperCase()}${name.slice(1)}`;
}

View File

@ -37,6 +37,9 @@ export class WrappedEvent {
key: string;
currentTarget: EventTarget | null = null;
target: HTMLElement;
relatedTarget: HTMLElement;
stopPropagation: () => void;
preventDefault: () => void;

View File

@ -14,7 +14,7 @@
*/
import { getVNodeProps } from '../dom/DOMInternalKeys';
import { getDomTag } from '../dom/utils/Common';
import { getDomTag, isNotNull } from '../dom/utils/Common';
import { Props } from '../dom/utils/Interface';
import { updateTextareaValue } from '../dom/valueHandler/TextareaValueHandler';
import { updateInputHandlerIfChanged } from '../dom/valueHandler/ValueChangeHandler';
@ -37,15 +37,26 @@ export function shouldControlValue(): boolean {
return changeEventTargets !== null && changeEventTargets.length > 0;
}
// 从缓存队列中对受控组件进行赋值
export function tryControlValue() {
if (!changeEventTargets) {
return;
function controlInputValue(inputDom: HTMLInputElement, props: Props) {
const { name, type } = props;
// 如果是 radio找出同一form内name相同的Radio更新它们Handler的Value
if (type === 'radio' && isNotNull(name)) {
const radioList = document.querySelectorAll<HTMLInputElement>(`input[type="radio"][name="${name}"]`);
for (let i = 0; i < radioList.length; i++) {
const radio = radioList[i];
if (radio === inputDom) {
continue;
}
if (isNotNull(radio.form) && isNotNull(inputDom.form) && radio.form !== inputDom.form) {
continue;
}
updateInputHandlerIfChanged(radio);
}
} else {
updateInputValue(inputDom, props);
}
changeEventTargets.forEach(target => {
controlValue(target);
});
changeEventTargets = null;
}
// 受控组件值重新赋值
@ -66,24 +77,13 @@ function controlValue(target: Element) {
}
}
function controlInputValue(inputDom: HTMLInputElement, props: Props) {
const { name, type } = props;
// 如果是 radio找出同一form内name相同的Radio更新它们Handler的Value
if (type === 'radio' && name != null) {
const radioList = document.querySelectorAll<HTMLInputElement>(`input[type="radio"][name="${name}"]`);
for (let i = 0; i < radioList.length; i++) {
const radio = radioList[i];
if (radio === inputDom) {
continue;
}
if (radio.form != null && inputDom.form != null && radio.form !== inputDom.form) {
continue;
}
updateInputHandlerIfChanged(radio);
}
} else {
updateInputValue(inputDom, props);
// 从缓存队列中对受控组件进行赋值
export function tryControlValue() {
if (!changeEventTargets) {
return;
}
changeEventTargets.forEach(target => {
controlValue(target);
});
changeEventTargets = null;
}

View File

@ -24,13 +24,14 @@ import {
EVENT_TYPE_ALL,
EVENT_TYPE_BUBBLE,
EVENT_TYPE_CAPTURE,
horizonEventToNativeMap,
transformToHorizonEvent,
inulaEventToNativeMap,
transformToInulaEvent,
} from './EventHub';
import { getDomTag } from '../dom/utils/Common';
import { updateInputHandlerIfChanged } from '../dom/valueHandler/ValueChangeHandler';
import { getDom } from '../dom/DOMInternalKeys';
import { recordChangeEventTargets, shouldControlValue, tryControlValue } from './FormValueController';
import { getMouseEnterListeners } from './MouseEvent';
// web规范鼠标右键key值
const RIGHT_MOUSE_BUTTON = 2;
@ -93,9 +94,9 @@ function getCommonListeners(
target: null | EventTarget,
isCapture: boolean
): ListenerUnitList {
const horizonEvtName = transformToHorizonEvent(nativeEvtName);
const inulaEvtName = transformToInulaEvent(nativeEvtName);
if (!horizonEvtName) {
if (!inulaEvtName) {
return [];
}
@ -112,8 +113,8 @@ function getCommonListeners(
nativeEvtName = 'blur';
}
const horizonEvent = decorateNativeEvent(horizonEvtName, nativeEvtName, nativeEvent);
return getListenersFromTree(vNode, horizonEvtName, horizonEvent, isCapture ? EVENT_TYPE_CAPTURE : EVENT_TYPE_BUBBLE);
const inulaEvent = decorateNativeEvent(inulaEvtName, nativeEvtName, nativeEvent);
return getListenersFromTree(vNode, inulaEvtName, inulaEvent, isCapture ? EVENT_TYPE_CAPTURE : EVENT_TYPE_BUBBLE);
}
// 按顺序执行事件队列
@ -131,8 +132,8 @@ function processListeners(listenerList: ListenerUnitList): void {
});
}
// 触发可以被执行的horizon事件监听
function triggerHorizonEvents(
// 触发可以被执行的inula事件监听
function triggerInulaEvents(
nativeEvtName: string,
isCapture: boolean,
nativeEvent: AnyNativeEvent,
@ -141,18 +142,26 @@ function triggerHorizonEvents(
const target = nativeEvent.target || nativeEvent.srcElement!;
// 触发普通委托事件
let listenerList: ListenerUnitList = getCommonListeners(nativeEvtName, vNode, nativeEvent, target, isCapture);
const listenerList: ListenerUnitList = getCommonListeners(nativeEvtName, vNode, nativeEvent, target, isCapture);
let mouseEnterListeners: ListenerUnitList = [];
if (inulaEventToNativeMap.get('onMouseEnter')!.includes(nativeEvtName)) {
mouseEnterListeners = getMouseEnterListeners(
nativeEvtName,
vNode,
nativeEvent,
target,
);
}
let changeEvents: ListenerUnitList = [];
// 触发特殊handler委托事件
if (!isCapture && horizonEventToNativeMap.get('onChange')!.includes(nativeEvtName)) {
const changeListeners = getChangeListeners(nativeEvtName, nativeEvent, vNode, target);
if (changeListeners.length) {
listenerList = listenerList.concat(changeListeners);
}
if (!isCapture && inulaEventToNativeMap.get('onChange')!.includes(nativeEvtName)) {
changeEvents = getChangeListeners(nativeEvtName, nativeEvent, vNode, target);
}
// 处理触发的事件队列
processListeners(listenerList);
processListeners([...listenerList, ...mouseEnterListeners, ...changeEvents]);
}
// 其他事件正在执行中标记
@ -176,14 +185,14 @@ export function handleEventMain(
// 有事件正在执行,同步执行事件
if (isInEventsExecution) {
triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode);
triggerInulaEvents(nativeEvtName, isCapture, nativeEvent, startVNode);
return;
}
// 没有事件在执行,经过调度再执行事件
isInEventsExecution = true;
try {
asyncUpdates(() => triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode));
asyncUpdates(() => triggerInulaEvents(nativeEvtName, isCapture, nativeEvent, startVNode));
} finally {
isInEventsExecution = false;
if (shouldControlValue()) {

View File

@ -40,11 +40,11 @@ function getListenerFromVNode(vNode: VNode, eventName: string): Function | null
// 获取监听事件
export function getListenersFromTree(
targetVNode: VNode | null,
horizonEvtName: string | null,
inulaEvtName: string | null,
nativeEvent: WrappedEvent,
eventType: string
): ListenerUnitList {
if (!horizonEvtName) {
if (!inulaEvtName) {
return [];
}
@ -57,7 +57,7 @@ export function getListenersFromTree(
const { realNode, tag } = vNode;
if (tag === DomComponent && realNode !== null) {
if (eventType === EVENT_TYPE_ALL || eventType === EVENT_TYPE_CAPTURE) {
const captureName = horizonEvtName + EVENT_TYPE_CAPTURE;
const captureName = inulaEvtName + EVENT_TYPE_CAPTURE;
const captureListener = getListenerFromVNode(vNode, captureName);
if (captureListener) {
listeners.unshift({
@ -70,7 +70,7 @@ export function getListenersFromTree(
}
if (eventType === EVENT_TYPE_ALL || eventType === EVENT_TYPE_BUBBLE) {
const bubbleListener = getListenerFromVNode(vNode, horizonEvtName);
const bubbleListener = getListenerFromVNode(vNode, inulaEvtName);
if (bubbleListener) {
listeners.push({
vNode,
@ -86,3 +86,91 @@ export function getListenersFromTree(
return listeners;
}
// 获取父节点
function getParent(inst: VNode | null): VNode | null {
if (inst === null) {
return null;
}
do {
inst = inst.parent;
} while (inst && inst.tag !== DomComponent);
return inst || null;
}
// 寻找两个节点的共同最近祖先如果没有则返回null
function getCommonAncestor(instA: VNode, instB: VNode): VNode | null {
const parentsSet = new Set<VNode>();
for (let tempA: VNode | null = instA; tempA; tempA = getParent(tempA)) {
parentsSet.add(tempA);
}
for (let tempB: VNode | null = instB; tempB; tempB = getParent(tempB)) {
if (parentsSet.has(tempB)) {
return tempB;
}
}
return null;
}
function getMouseListenersFromTree(
event: WrappedEvent,
target: VNode,
commonParent: VNode | null,
): ListenerUnitList {
const registrationName = event.customEventName;
const listeners: ListenerUnitList = [];
let vNode = target;
while (vNode !== null) {
// commonParent作为终点
if (vNode === commonParent) {
break;
}
const {realNode, tag} = vNode;
if (tag === DomComponent && realNode !== null) {
const currentTarget = realNode;
const listener = getListenerFromVNode(vNode, registrationName);
if (listener) {
listeners.push({
vNode,
listener,
currentTarget,
event,
});
}
}
vNode = vNode.parent;
}
return listeners;
}
// 获取enter和leave事件队列
export function collectMouseListeners(
leaveEvent: null | WrappedEvent,
enterEvent: null | WrappedEvent,
from: VNode | null,
to: VNode | null,
): ListenerUnitList {
// 确定公共父节点,作为在树上遍历的终点
const commonParent = from && to ? getCommonAncestor(from, to) : null;
let leaveEventList: ListenerUnitList = [];
if (from && leaveEvent) {
// 遍历树获取绑定的leave事件
leaveEventList = getMouseListenersFromTree(
leaveEvent,
from,
commonParent,
);
}
let enterEventList: ListenerUnitList = [];
if (to && enterEvent) {
// 先触发父节点enter事件所以需要逆序
enterEventList = getMouseListenersFromTree(
enterEvent,
to,
commonParent,
).reverse();
}
return [...leaveEventList, ...enterEventList];
}

View File

@ -0,0 +1,109 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { getNearestVNode } from '../dom/DOMInternalKeys';
import { WrappedEvent } from './EventWrapper';
import { VNode } from '../renderer/vnode/VNode';
import { AnyNativeEvent, ListenerUnitList } from './Types';
import { DomComponent, DomText } from '../renderer/vnode/VNodeTags';
import { collectMouseListeners } from './ListenerGetter';
import { getNearestMountedVNode } from './utils';
/**
* mouseEnter和mouseLeave事件不冒泡
* mouseoutmouseover事件的mouseEnter和mouseLeave事件
*
* 1. mouseout和mouseover事件
* 2.
* 3. enter和leave事件
* 4.
* 5. treeNodemouseEnter和mouseLeave监听方法
* mouseOut事件由D->C, A节点作为公共父节点 DB的mouseLeave事件和C节点的mouseEnter事件
* A
* / \
* B C
* / \
* D E
*
*/
function getWrapperEvents(nativeEventTarget, fromInst, toInst, nativeEvent, targetInst): (WrappedEvent | null)[] {
const vWindow = nativeEventTarget.window === nativeEventTarget ? nativeEventTarget : nativeEventTarget.ownerDocument.defaultView;
// 起点或者终点为空的话默认值为所在window
const fromNode = fromInst?.realNode || vWindow;
const toNode = toInst?.realNode || vWindow;
let leave: WrappedEvent | null = null;
let enter: WrappedEvent | null = null;
const nativeTargetInst = getNearestVNode(nativeEventTarget);
// 在Mounted的dom节点上render一个子组件系统中存在两个根节点子节点的mouseout事件触发两次取离target近的根节点生效
if (nativeTargetInst === targetInst) {
leave = new WrappedEvent('onMouseLeave', 'mouseleave', nativeEvent);
leave.target = fromNode;
leave.relatedTarget = toNode;
enter = new WrappedEvent('onMouseEnter', 'mouseenter', nativeEvent);
enter.target = toNode;
enter.relatedTarget = fromNode;
}
return [leave, enter];
}
function getEndpointVNode(
domEventName: string,
targetInst: null | VNode,
nativeEvent: AnyNativeEvent,
): (VNode | null)[] {
let fromVNode;
let toVNode;
if (domEventName === 'mouseover') {
fromVNode = null;
toVNode = targetInst;
} else {
const related = nativeEvent.relatedTarget || nativeEvent.toElement;
fromVNode = targetInst;
toVNode = related ? getNearestVNode(related) : null;
if (toVNode !== null) {
const nearestMounted = getNearestMountedVNode(toVNode);
if (toVNode !== nearestMounted || (toVNode.tag !== DomComponent && toVNode.tag !== DomText)) {
toVNode = null;
}
}
}
return [fromVNode, toVNode];
}
export function getMouseEnterListeners(
domEventName: string,
targetInst: null | VNode,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
): ListenerUnitList {
// 获取起点和终点的VNode
const [fromVNode, toVNode] = getEndpointVNode(domEventName, targetInst, nativeEvent);
if (fromVNode === toVNode) {
return [];
}
// 获取包装后的leave和enter事件
const [leave, enter] = getWrapperEvents(nativeEventTarget, fromVNode, toVNode, nativeEvent, targetInst);
// 收集事件的监听方法
return collectMouseListeners(leave, enter, fromVNode, toVNode);
}

View File

@ -18,13 +18,13 @@ import { WrappedEvent } from './EventWrapper';
export type AnyNativeEvent = KeyboardEvent | MouseEvent | TouchEvent | UIEvent | Event;
export interface HorizonEventListener {
export interface InulaEventListener {
(event: WrappedEvent): void;
}
export type ListenerUnit = {
vNode: null | VNode;
listener: HorizonEventListener;
listener: InulaEventListener;
currentTarget: EventTarget;
event: WrappedEvent;
};

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* InulaJS is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { VNode } from '../renderer/vnode/VNode';
import { Addition, FlagUtils } from '../renderer/vnode/VNodeFlags';
import { TreeRoot } from '../renderer/vnode/VNodeTags';
export function isInputElement(dom?: HTMLElement): boolean {
return dom instanceof HTMLInputElement || dom instanceof HTMLTextAreaElement;
}
export function setPropertyWritable(obj, propName) {
const desc = Object.getOwnPropertyDescriptor(obj, propName);
if (!desc || !desc.writable) {
Object.defineProperty(obj, propName, {writable: true});
}
}
// 获取离 vNode 最近的已挂载 vNode包含它自己
export function getNearestMountedVNode(vNode: VNode): null | VNode {
let node = vNode;
let target = vNode;
// 如果没有alternate说明是可能是未插入的新树需要处理插入的副作用。
while (node.parent) {
// 存在更新,节点未挂载,查找父节点,但是父节点也可能未挂载,需要继续往上查找无更新节点
if (FlagUtils.hasFlag(node, Addition)) {
target = node.parent;
}
node = node.parent;
}
// 如果根节点是 Dom 类型节点,表示已经挂载
if (node.tag === TreeRoot) {
return target;
}
// 如果没有找到根节点意味着Tree已经卸载或者未挂载
return null;
}

View File

@ -17,16 +17,51 @@ import { throwIfTrue } from '../renderer/utils/throwIfTrue';
import { TYPE_COMMON_ELEMENT, TYPE_PORTAL } from './JSXElementType';
import { isValidElement, JSXElement } from './JSXElement';
import { BELONG_CLASS_VNODE_KEY } from '../renderer/vnode/VNode';
// 生成key
function getItemKey(item: any, index: number): string {
if (typeof item === 'object' && item !== null && item.key != null) {
if (typeof item === 'object' && item !== null && item.key !== null && item.key !== undefined) {
return '.$' + item.key;
}
// 使用36进制减少生成字符串的长度以节省空间
return '.' + index.toString(36);
}
function processArrayChildren(children: any, arr: Array<any>, 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<any>, 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 = JSXElement(
mappedChild.type,
newKey,
mappedChild.ref,
mappedChild[BELONG_CLASS_VNODE_KEY],
mappedChild.props,
mappedChild.src
);
}
arr.push(mappedChild);
}
}
function mapChildrenToArray(children: any, arr: Array<any>, prefix: string, callback?: Function): number | void {
const type = typeof children;
switch (type) {
@ -53,45 +88,11 @@ function mapChildrenToArray(children: any, arr: Array<any>, prefix: string, call
processArrayChildren(children, arr, prefix, callback);
return;
}
throw new Error('Object is invalid as a Horizon child. ');
throw new Error('Object is invalid as a Inula child. ');
// No Default
}
}
function processArrayChildren(children: any, arr: Array<any>, 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<any>, 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 = JSXElement(
mappedChild.type,
newKey,
mappedChild.ref,
mappedChild.belongClassVNode,
mappedChild.props,
mappedChild.src
);
}
arr.push(mappedChild);
}
}
// 在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg
function mapChildren(children: any, func: Function, context?: any): Array<any> {
if (children === null || children === undefined) {
@ -118,7 +119,7 @@ const Children = {
return n;
},
only: children => {
throwIfTrue(!isValidElement(children), 'Horizon.Children.only function received invalid element.');
throwIfTrue(!isValidElement(children), 'Inula.Children.only function received invalid element.');
return children;
},
toArray: children => {

View File

@ -16,6 +16,7 @@
import { TYPE_COMMON_ELEMENT } from './JSXElementType';
import { getProcessingClassVNode } from '../renderer/GlobalVar';
import { Source } from '../renderer/Types';
import { BELONG_CLASS_VNODE_KEY } from '../renderer/vnode/VNode';
/**
* vtype element
@ -25,10 +26,10 @@ import { Source } from '../renderer/Types';
* props
*/
export function JSXElement(type, key, ref, vNode, props, source: Source | null) {
return {
const ele = {
// 元素标识符
vtype: TYPE_COMMON_ELEMENT,
src: isDev ? source : null,
src: null,
// 属于元素的内置属性
type: type,
@ -36,14 +37,28 @@ export function JSXElement(type, key, ref, vNode, props, source: Source | null)
ref: ref,
props: props,
// 所属的class组件
belongClassVNode: vNode,
// 所属的class组件,clonedeep jsxElement时需要防止无限循环
[BELONG_CLASS_VNODE_KEY]: vNode,
};
}
// 兼容IE11不支持Symbol
if (typeof BELONG_CLASS_VNODE_KEY === 'string') {
Object.defineProperty(ele, BELONG_CLASS_VNODE_KEY, {
configurable: false,
enumerable: false,
value: vNode,
});
}
if (isDev) {
// 为了test判断两个 JSXElement 对象是否相等时忽略src属性需要设置src的enumerable为false
Object.defineProperty(ele, 'src', {
configurable: false,
enumerable: false,
writable: false,
value: source,
});
}
function isValidKey(key) {
const keyArray = ['key', 'ref', '__source', '__self'];
return !keyArray.includes(key);
return ele;
}
function mergeDefault(sourceObj, defaultObj) {
@ -54,19 +69,20 @@ function mergeDefault(sourceObj, defaultObj) {
});
}
// ['key', 'ref', '__source', '__self']属性不从setting获取
const keyArray = ['key', 'ref', '__source', '__self'];
function buildElement(isClone, type, setting, children) {
// setting中的值优先级最高clone情况下从 type 中取值,创建情况下直接赋值为 null
const key = setting && setting.key !== undefined ? String(setting.key) : isClone ? type.key : null;
const ref = setting && setting.ref !== undefined ? setting.ref : isClone ? type.ref : null;
const key = (setting && setting.key !== undefined) ? String(setting.key) : (isClone ? type.key : null);
const ref = (setting && setting.ref !== undefined) ? setting.ref : (isClone ? type.ref : null);
const props = isClone ? { ...type.props } : {};
let vNode = isClone ? type.belongClassVNode : getProcessingClassVNode();
let vNode = isClone ? type[BELONG_CLASS_VNODE_KEY] : getProcessingClassVNode();
if (setting !== null && setting !== undefined) {
const keys = Object.keys(setting);
const keyLength = keys.length;
for (let i = 0; i < keyLength; i++) {
const k = keys[i];
if (isValidKey(k)) {
for (const k in setting) {
if (!keyArray.includes(k)) {
props[k] = setting[k];
}
}
@ -90,7 +106,6 @@ function buildElement(isClone, type, setting, children) {
lineNumber: setting.__source.lineNumber,
};
}
return JSXElement(element, key, ref, vNode, props, src);
}
@ -107,3 +122,12 @@ export function cloneElement(element, setting, ...children) {
export function isValidElement(element) {
return !!(element && element.vtype === TYPE_COMMON_ELEMENT);
}
// 兼容高版本的babel编译方式
export function jsx(type, setting, key) {
if (setting.key === undefined && key !== undefined) {
setting.key = key;
}
return buildElement(false, type, setting, []);
}

81
libs/inula/src/external/TestUtil.ts vendored Normal file
View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import {asyncUpdates} from '../renderer/TreeBuilder';
import {callRenderQueueImmediate} from '../renderer/taskExecutor/RenderQueue';
import {hasAsyncEffects, runAsyncEffects} from '../renderer/submit/HookEffectHandler';
import {isPromise} from '../renderer/ErrorHandler';
interface Thenable {
then(resolve: (val?: any) => void, reject: (err: any) => void): void;
}
// 防止死循环
const LOOPING_LIMIT = 50;
let loopingCount = 0;
function callRenderQueue() {
callRenderQueueImmediate();
while (hasAsyncEffects() && loopingCount < LOOPING_LIMIT) {
loopingCount++;
runAsyncEffects();
// effects可能产生刷新任务这里再执行一次
callRenderQueueImmediate();
}
}
// act用于测试作用是如果fun触发了刷新包含了异步刷新可以保证在act后面的代码是在刷新完成后才执行。
function act(fun: () => void | Thenable): Thenable {
const funRet = asyncUpdates(fun);
callRenderQueue();
// 如果fun返回的是Promise
if (isPromise(funRet)) {
// testing-library会返回Promise
return {
then(resolve, reject) {
funRet.then(
() => {
if (typeof setImmediate === 'function') {
// 通过setImmediate回调用于等待业务的setTimeout完成
setImmediate(() => {
callRenderQueue();
resolve();
});
} else {
callRenderQueue();
resolve();
}
},
err => {
reject(err);
},
);
},
};
} else {
return {
then(resolve) {
resolve();
},
};
}
}
export {
act
};

View File

@ -128,7 +128,7 @@ export const helper = {
};
export function injectUpdater() {
const hook = window.__HORIZON_DEV_HOOK__;
const hook = window.__INULA_DEV_HOOK__;
if (hook) {
hook.init(helper);
}

View File

@ -84,7 +84,6 @@ export function isSame(x, y) {
export function getDetailedType(val: any) {
if (val === undefined) return 'undefined';
if (val === null) return 'null';
if (isCollection(val)) return 'collection';
if (isPromise(val)) return 'promise';
if (isArray(val)) return 'array';
if (isWeakMap(val)) return 'weakMap';
@ -116,12 +115,30 @@ export function resolveMutation(from, to) {
if (res[i].mutation) found = true;
}
}
// TODO: resolve shifts
// need to resolve shifts
return { mutation: found, items: res, from, to };
}
case 'object': {
let keys = Object.keys({ ...from, ...to });
if (from._type && from._type === to._type) {
if (from._type === 'Map') {
const entries = resolveMutation(from.entries, to.entries);
return {
mutation: entries.items.some(item => item.mutation),
from,
to,
entries: entries.items,
};
}
if (from._type === 'Set') {
const values = resolveMutation(from.values, to.values);
return { mutation: values.items.some(item => item.mutation), from, to, values: values.items };
}
}
let keys = Object.keys({ ...from, ...to }).filter(key => key !== '_inulaObserver');
const res = {};
let found = false;
keys.forEach(key => {
@ -142,8 +159,6 @@ export function resolveMutation(from, to) {
return { mutation: found, attributes: res, from, to };
}
// TODO: implement collections
default: {
if (from === to) return { mutation: false };
@ -151,3 +166,9 @@ export function resolveMutation(from, to) {
}
}
}
export function omit(obj, ...attrs) {
let res = { ...obj };
attrs.forEach(attr => delete res[attr]);
return res;
}

View File

@ -13,4 +13,6 @@
* See the Mulan PSL v2 for more details.
*/
export const OBSERVER_KEY = '_horizonObserver';
export const OBSERVER_KEY = typeof Symbol === 'function' ? Symbol('_inulaObserver') : '_inulaObserver';
export const RAW_VALUE = '_rawValue';

View File

@ -53,6 +53,40 @@ export type ReduxMiddleware = (
type Reducer = (state: any, action: ReduxAction) => any;
function mergeData(state, data) {
if (!data) {
state.stateWrapper = data;
return;
}
if (Array.isArray(data) && Array.isArray(state?.stateWrapper)) {
state.stateWrapper.length = data.length;
data.forEach((item, idx) => {
if (item != state.stateWrapper[idx]) {
state.stateWrapper[idx] = item;
}
});
return;
}
if (typeof data === 'object' && typeof state?.stateWrapper === 'object') {
Object.keys(state.stateWrapper).forEach(key => {
if (!Object.prototype.hasOwnProperty.call(data, key)) {
delete state.stateWrapper[key];
}
});
Object.entries(data).forEach(([key, value]) => {
if (state.stateWrapper[key] !== value) {
state.stateWrapper[key] = value;
}
});
return;
}
state.stateWrapper = data;
}
export function createStore(reducer: Reducer, preloadedState?: any, enhancers?): ReduxStoreHandler {
const store = createStoreX({
id: 'defaultStore',
@ -92,13 +126,13 @@ export function createStore(reducer: Reducer, preloadedState?: any, enhancers?):
replaceReducer: newReducer => {
reducer = newReducer;
},
_horizonXstore: store,
_inulaXstore: store,
dispatch: store.$a.dispatch,
};
enhancers && enhancers(result);
result.dispatch({ type: 'HorizonX' });
result.dispatch({ type: 'InulaX' });
store.reduxHandler = result;
@ -106,7 +140,8 @@ export function createStore(reducer: Reducer, preloadedState?: any, enhancers?):
}
export function combineReducers(reducers: { [key: string]: Reducer }): Reducer {
return (state = {}, action) => {
return (state, action) => {
state = state || {};
const newState = {};
Object.entries(reducers).forEach(([key, reducer]) => {
newState[key] = reducer(state[key], action);
@ -115,12 +150,6 @@ export function combineReducers(reducers: { [key: string]: Reducer }): Reducer {
};
}
export function applyMiddleware(...middlewares: ReduxMiddleware[]): (store: ReduxStoreHandler) => void {
return store => {
return applyMiddlewares(store, middlewares);
};
}
function applyMiddlewares(store: ReduxStoreHandler, middlewares: ReduxMiddleware[]): void {
middlewares = middlewares.slice();
middlewares.reverse();
@ -131,6 +160,12 @@ function applyMiddlewares(store: ReduxStoreHandler, middlewares: ReduxMiddleware
store.dispatch = dispatch;
}
export function applyMiddleware(...middlewares: ReduxMiddleware[]): (store: ReduxStoreHandler) => void {
return store => {
return applyMiddlewares(store, middlewares);
};
}
type ActionCreator = (...params: any[]) => ReduxAction;
type ActionCreators = { [key: string]: ActionCreator };
export type BoundActionCreator = (...params: any[]) => void;
@ -162,7 +197,7 @@ export function compose(...middlewares: ReduxMiddleware[]) {
};
}
// HorizonX batches updates by default, this function is only for backwards compatibility
// InulaX batches updates by default, this function is only for backwards compatibility
export function batch(fn: () => void) {
fn();
}

View File

@ -30,7 +30,7 @@ export function Provider({
context: Context;
children?: any[];
}) {
const Context = context; // NOTE: bind redux API to horizon API requires this renaming;
const Context = context; // NOTE: bind redux API to inula API requires this renaming;
return createElement(Context.Provider, { value: store }, children);
}

View File

@ -0,0 +1,10 @@
export const INITIALIZED = 'inulax store initialized';
export const STATE_CHANGE = 'inulax state change';
export const SUBSCRIBED = 'inulax subscribed';
export const UNSUBSCRIBED = 'inulax unsubscribed';
export const ACTION = 'inulax action';
export const ACTION_QUEUED = 'inulax action queued';
export const QUEUE_PENDING = 'inulax queue pending';
export const QUEUE_FINISHED = 'inulax queue finished';
export const RENDER_TRIGGERED = 'inulax render triggered';
export const OBSERVED_COMPONENTS = 'inulax observed components';

View File

@ -0,0 +1,226 @@
import { isDomVNode } from '../../renderer/vnode/VNodeUtils';
import { isMap, isSet, isWeakMap, isWeakSet } from '../CommonUtils';
import { getStore, getAllStores } from '../store/StoreHandler';
import { OBSERVED_COMPONENTS } from './constants';
const sessionId = Date.now();
// this function is used to detect devtool connection
export function isPanelActive() {
return window['__INULA_DEV_HOOK__'];
}
// safely serializes variables containing values wrapped in Proxy object
function getType(value) {
if (!value) return 'nullish';
if (value.nativeEvent) return 'event';
if (typeof value === 'function') return 'function';
if (value.constructor?.name === 'VNode') return 'vnode';
if (isWeakMap(value)) return 'weakMap';
if (isWeakSet(value)) return 'weakSet';
if (isMap(value)) return 'map';
if (isSet(value)) return 'set';
if (Array.isArray(value)) return 'array';
if (typeof value === 'object') return 'object';
return 'primitive';
}
function makeProxySnapshot(obj, visited: any[] = []) {
const type = getType(obj);
let clone;
try {
//NULLISH VALUE
if (type === 'nullish') {
return obj;
}
//EVENT
if (type === 'event') return obj.type + 'Event';
// FUNCTION
if (type === 'function') {
return obj.toString();
}
// VNODE
if (type === 'vnode') {
return {
_type: 'VNode',
id: window['__INULA_DEV_HOOK__'].getVnodeId(obj),
tag: obj.tag,
};
}
// WEAK MAP
if (type === 'weakMap') {
return {
_type: 'WeakMap',
};
}
// WEAK SET
if (type === 'weakSet') {
return {
_type: 'WeakSet',
};
}
// MAP
if (type === 'map') {
return {
_type: 'Map',
entries: Array.from(obj.entries()).map(([key, value]) => ({
key: makeProxySnapshot(key),
value: makeProxySnapshot(value),
})),
};
}
// SET
if (type === 'set') {
return {
_type: 'Set',
values: Array.from(obj).map(value => makeProxySnapshot(value)),
};
}
// ARRAY
if (type === 'array') {
if (visited.some(item => item === obj)) return `<Cyclic ${obj.toString()}>`;
clone = [];
obj.forEach(item => clone.push(makeProxySnapshot(item, visited.concat([obj]))));
return clone;
}
// OBJECT
if (type === 'object') {
if (visited.some(item => item === obj)) return `<Cyclic ${obj.toString()}>`;
clone = {};
Object.entries(obj).forEach(([id, value]) => (clone[id] = makeProxySnapshot(value, visited.concat([obj]))));
return clone;
}
// PRIMITIVE
return obj;
} catch (err) {
console.error('cannot serialize object. ', { err, obj, type });
}
}
// serializes store and creates expanded object with baked-in containing current computed values
function makeStoreSnapshot({ type, data }) {
const expanded = {};
Object.keys(data.store.$c).forEach(key => {
expanded[key] = data.store[key];
});
data.store.expanded = expanded;
const snapshot = makeProxySnapshot({
data,
type,
sessionId,
});
return snapshot;
}
export const devtools = {
// returns vNode id from inula devtools
getVNodeId: vNode => {
if (!isPanelActive()) {
return null;
}
window['__INULA_DEV_HOOK__'].send(); // update list first
return window['__INULA_DEV_HOOK__'].getVnodeId(vNode);
},
// sends inulax devtool message to extension
emit: (type, data) => {
if (!isPanelActive()) {
return;
}
window.postMessage({
type: 'INULA_DEV_TOOLS',
payload: makeStoreSnapshot({ type, data }),
from: 'dev tool hook',
}, '');
},
};
// collects components that are dependant on inulax store and their ids
function getAffectedComponents() {
const allStores = getAllStores();
const keys = Object.keys(allStores);
const res = {};
keys.forEach(key => {
if (!allStores[key].$config.state._inulaObserver.keyVNodes) {
res[key] = [];
return;
}
const subRes = new Set();
const process = Array.from(allStores[key].$config.state._inulaObserver.keyVNodes.values());
while (process.length) {
const pivot = process.shift() as { tag: 'string' };
if (pivot.tag) subRes.add(pivot);
if (pivot.toString() === '[object Set]') Array.from(pivot).forEach(item => process.push(item));
}
res[key] = Array.from(subRes).map(vNode => {
return {
name: vNode?.type
.toString()
.replace(/\{.*\}/, '{...}')
.replace('function ', ''),
nodeId: window.__INULA_DEV_HOOK__.getVnodeId(vNode),
};
});
});
return res;
}
// listens to messages from background
window.addEventListener('message', (messageEvent?) => {
if (messageEvent?.data?.payload?.type === 'inulax request observed components') {
// get observed components
setTimeout(() => {
window.postMessage({
type: 'INULA_DEV_TOOLS',
payload: { type: OBSERVED_COMPONENTS, data: getAffectedComponents() },
from: 'dev tool hook',
}, '');
}, 100);
}
// executes store action
if (messageEvent.data?.payload?.type === 'inulax executue action') {
const data = messageEvent.data.payload.data;
const store = getStore(data.storeId);
if (!store?.[data.action]) return;
const action = store[data.action];
const params = data.params;
action(...params);
}
// queues store action
if (messageEvent?.data?.payload?.type === 'inulax queue action') {
const data = messageEvent.data.payload.data;
const store = getStore(data.storeId);
if (!store?.[data.action]) return;
const action = store.$queue[data.action];
const params = data.params;
action(...params);
}
// queues change store state
if (messageEvent?.data?.payload?.type === 'inulax change state') {
const data = messageEvent.data.payload;
const store = getStore(data.storeId);
if (!store) return;
let parent = store.$s;
if (data.operation === 'edit') {
try {
const path = messageEvent.data.payload.path;
while (path.length > 1) {
parent = parent[path.pop()];
}
parent[path[0]] = messageEvent.data.payload.value;
} catch (err) {
console.error(err);
}
}
// need to implement add and delete element
}
});

View File

@ -31,15 +31,16 @@ export class HooklessObserver implements IObserver {
this.listeners = this.listeners.filter(item => item != listener);
}
getListeners() {
return this.listeners;
}
setProp(key: string | symbol, mutation: any): void {
this.triggerChangeListeners(mutation);
}
triggerChangeListeners(mutation: any): void {
this.listeners.forEach(listener => {
if (!listener) {
return;
}
listener(mutation);
});
}

View File

@ -81,7 +81,7 @@ export class Observer implements IObserver {
const vNodes = this.keyVNodes.get(key);
//NOTE: using Set directly can lead to deadlock
const vNodeArray = Array.from(vNodes || []);
vNodeArray?.forEach((vNode: VNode) => {
vNodeArray.forEach((vNode: VNode) => {
if (vNode.isStoreChange) {
// VNode已经被触发过不再重复触发
return;
@ -97,10 +97,6 @@ export class Observer implements IObserver {
}
triggerUpdate(vNode: VNode): void {
if (!vNode) {
return;
}
// 触发VNode更新
launchUpdateFromVNode(vNode);
}

View File

@ -20,14 +20,30 @@ import { isArray, isCollection, isObject } from '../CommonUtils';
import { createArrayProxy } from './handlers/ArrayProxyHandler';
import { createCollectionProxy } from './handlers/CollectionProxyHandler';
import type { IObserver } from '../types';
import { OBSERVER_KEY } from '../Constants';
import { OBSERVER_KEY, RAW_VALUE } from '../Constants';
// 保存rawObj -> Proxy
const proxyMap = new WeakMap();
export const hookObserverMap = new WeakMap();
export function createProxy(rawObj: any, id, isHookObserver = true): any {
export function getObserver(rawObj: any): Observer {
return rawObj[OBSERVER_KEY];
}
const setObserverKey = typeof OBSERVER_KEY === 'string'
? (rawObj, observer) => {
Object.defineProperty(rawObj, OBSERVER_KEY, {
configurable: false,
enumerable: false,
value: observer,
});
}
: (rawObj, observer) => {
rawObj[OBSERVER_KEY] = observer;
};
export function createProxy(rawObj: any, listener: { current: (...args) => any }, isHookObserver = true): any {
// 不是对象(是原始数据类型)不用代理
if (!(rawObj && isObject(rawObj))) {
return rawObj;
@ -48,7 +64,7 @@ export function createProxy(rawObj: any, id, isHookObserver = true): any {
let observer: IObserver = getObserver(rawObj);
if (!observer) {
observer = isHookObserver ? new Observer() : new HooklessObserver();
rawObj[OBSERVER_KEY] = observer;
setObserverKey(rawObj, observer);
}
hookObserverMap.set(rawObj, isHookObserver);
@ -56,16 +72,35 @@ export function createProxy(rawObj: any, id, isHookObserver = true): any {
// 创建Proxy
let proxyObj;
if (!isHookObserver) {
proxyObj = createObjectProxy(rawObj, true);
proxyObj = createObjectProxy(rawObj, {
current: change => {
listener.current(change);
},
},
true);
} else if (isArray(rawObj)) {
// 数组
proxyObj = createArrayProxy(rawObj as []);
proxyObj = createArrayProxy(rawObj as [], {
current: change => {
listener.current(change);
},
});
} else if (isCollection(rawObj)) {
// 集合
proxyObj = createCollectionProxy(rawObj);
proxyObj = createCollectionProxy(rawObj, {
current: change => {
listener.current(change);
},
},
true);
} else {
// 原生对象 或 函数
proxyObj = createObjectProxy(rawObj);
proxyObj = createObjectProxy(rawObj, {
current: change => {
listener.current(change);
},
},
false);
}
proxyMap.set(rawObj, proxyObj);
@ -74,6 +109,6 @@ export function createProxy(rawObj: any, id, isHookObserver = true): any {
return proxyObj;
}
export function getObserver(rawObj: any): Observer {
return rawObj[OBSERVER_KEY];
export function toRaw<T>(observed: T): T {
return observed && (observed)[RAW_VALUE];
}

View File

@ -0,0 +1,160 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* InulaJS is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
import { isSame, isValidIntegerKey } from '../../CommonUtils';
import { resolveMutation } from '../../CommonUtils';
import { isPanelActive } from '../../devtools';
import { OBSERVER_KEY, RAW_VALUE } from '../../Constants';
function set(rawObj: any[], key: string, value: any, receiver: any) {
const oldValue = rawObj[key];
const oldLength = rawObj.length;
const newValue = value;
const oldArray = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
const ret = Reflect.set(rawObj, key, newValue, receiver);
const newLength = rawObj.length;
const observer = getObserver(rawObj);
const mutation = isPanelActive() ? resolveMutation(oldArray, rawObj) : resolveMutation(null, rawObj);
if (!isSame(newValue, oldValue)) {
// 值不一样,触发监听器
if (observer.watchers?.[key]) {
observer.watchers[key].forEach(cb => {
cb(key, oldValue, newValue, mutation);
});
}
// 触发属性变化
observer.setProp(key, mutation);
}
if (oldLength !== newLength) {
// 触发数组的大小变化
observer.setProp('length', mutation);
}
return ret;
}
export function createArrayProxy(rawObj: any[], listener: { current: (...args) => any }): any[] {
let listeners = [] as ((...args) => void)[];
function objectGet(rawObj: object, key: string | symbol, receiver: any, singleLevel = false): any {
// The observer object of symbol ('_inulaObserver') cannot be accessed from Proxy to prevent errors caused by clonedeep.
if (key === OBSERVER_KEY) {
return undefined;
}
const observer = getObserver(rawObj);
if (key === 'watch') {
return (prop, handler: (key: string, oldValue: any, newValue: any) => void) => {
if (!observer.watchers[prop]) {
observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[];
}
observer.watchers[prop].push(handler);
return () => {
observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler);
};
};
}
if (key === 'addListener') {
return listener => {
listeners.push(listener);
};
}
if (key === 'removeListener') {
return listener => {
listeners = listeners.filter(item => item != listener);
};
}
observer.useProp(key);
const value = Reflect.get(rawObj, key, receiver);
// 对于prototype不做代理
if (key !== 'prototype') {
// 对于value也需要进一步代理
const valProxy = singleLevel
? value
: createProxy(value, {
current: change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
let mutation = resolveMutation(
{ ...rawObj, [key]: change.mutation.from },
{ ...rawObj, [key]: change.mutation.to }
);
listener.current(mutation);
listeners.forEach(lst => lst(mutation));
},
},
hookObserverMap.get(rawObj)
);
return valProxy;
}
return value;
}
function get(rawObj: any[], key: string, receiver: any) {
if (key === 'watch') {
const observer = getObserver(rawObj);
return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => {
if (!observer.watchers[prop]) {
observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[];
}
observer.watchers[prop].push(handler);
return () => {
observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler);
};
};
}
if (isValidIntegerKey(key) || key === 'length') {
return objectGet(rawObj, key, receiver);
}
if (key === RAW_VALUE) {
return rawObj;
}
return Reflect.get(rawObj, key, receiver);
}
const handle = {
get,
set,
};
getObserver(rawObj).addListener(change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
listener.current(change);
listeners.forEach(lst => lst(change));
});
return new Proxy(rawObj, handle);
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* InulaJS is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { isWeakMap, isWeakSet, isSet } from '../../CommonUtils';
import { createWeakSetProxy } from './WeakSetProxy';
import { createSetProxy } from './SetProxy';
import { createWeakMapProxy } from './WeakMapProxy';
import { createMapProxy } from './MapProxy';
export function createCollectionProxy(
rawObj: Object,
listener: { current: (...args) => any },
hookObserver = true
): Object {
if (isWeakSet(rawObj)) {
return createWeakSetProxy(rawObj, listener, hookObserver);
}
if (isSet(rawObj)) {
return createSetProxy(rawObj, listener, hookObserver);
}
if (isWeakMap(rawObj)) {
return createWeakMapProxy(rawObj, listener, hookObserver);
}
return createMapProxy(rawObj, listener, hookObserver);
}

View File

@ -0,0 +1,413 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
import { isSame } from '../../CommonUtils';
import { resolveMutation } from '../../CommonUtils';
import { isPanelActive } from '../../devtools';
import { RAW_VALUE } from '../../Constants';
const COLLECTION_CHANGE = '_collectionChange';
export function createMapProxy(
rawObj: Object,
listener: { current: (...args) => any },
hookObserver = true
): Object {
let listeners: ((mutation) => {})[] = [];
let oldData: [any, any][] = [];
let proxies = new Map();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function getFun(rawObj: { get: (key: any) => any; has: (key: any) => boolean }, key: any): any {
const keyProxy = rawObj.has(key) ? key : proxies.get(key);
if (!keyProxy) return;
const observer = getObserver(rawObj);
observer.useProp(key);
const value = rawObj.get(keyProxy);
// 对于value也需要进一步代理
const valProxy = createProxy(value, {
current: change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
let mutation = resolveMutation(
{ ...rawObj, [key]: change.mutation.from },
{ ...rawObj, [key]: change.mutation.to }
);
listener.current({ ...change, mutation });
listeners.forEach(lst => lst({ ...change, mutation }));
},
},
hookObserverMap.get(rawObj)
);
return valProxy;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Map的set方法
function set(
rawObj: {
get: (key: any) => any;
set: (key: any, value: any) => any;
has: (key: any) => boolean;
entries: () => [any, any][];
},
key: any,
value: any
): any {
if (rawObj.has(key) || rawObj.has(proxies.get(key))) {
// VALUE CHANGE (whole value for selected key is changed)
const oldValue = rawObj.get(proxies.get(key));
if (isSame(value, oldValue)) return;
rawObj.set(proxies.get(key), value);
const mutation = isPanelActive() ? resolveMutation(oldValue, rawObj) : resolveMutation(null, rawObj);
const observer = getObserver(rawObj);
observer.setProp(COLLECTION_CHANGE, mutation);
if (observer.watchers[key]) {
observer.watchers[key].forEach(cb => {
cb(key, oldValue, value, mutation);
});
}
observer.setProp(key, mutation);
oldData = [...Array.from(rawObj.entries())];
} else {
// NEW VALUE
const keyProxy = createProxy(key, {
current: change => {
// KEY CHANGE
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
let mutation = resolveMutation(
{ ...rawObj, ['_keyChange']: change.mutation.from },
{ ...rawObj, ['_keyChange']: change.mutation.to }
);
listener.current({ ...change, mutation });
listeners.forEach(lst => lst({ ...change, mutation }));
},
},
hookObserverMap.get(rawObj)
);
proxies.set(key, keyProxy);
rawObj.set(keyProxy, value);
const observer = getObserver(rawObj);
const mutation = resolveMutation(
{
_type: 'Map',
entries: oldData,
},
{
_type: 'Map',
entries: Array.from(rawObj.entries()),
}
);
observer.setProp(COLLECTION_CHANGE, mutation);
if (observer.watchers?.[key]) {
observer.watchers[key].forEach(cb => {
cb(key, null, value, mutation);
});
}
observer.setProp(key, mutation);
oldData = [...Array.from(rawObj.entries())];
}
return rawObj;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function has(rawObj: { has: (any) => boolean }, key: any): boolean {
const observer = getObserver(rawObj);
observer.useProp(key);
if (rawObj.has(key)) {
return true;
}
return proxies.has(key);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function clear(rawObj: { size: number; clear: () => void; entries: () => [any, any][] }) {
const oldSize = rawObj.size;
rawObj.clear();
if (oldSize > 0) {
const observer = getObserver(rawObj);
observer.allChange();
oldData = [...Array.from(rawObj.entries())];
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function deleteFun(
rawObj: { has: (key: any) => boolean; delete: (key: any) => void; entries: () => [any, any][] },
key: any
) {
if (rawObj.has(key) || proxies.has(key)) {
rawObj.delete(key || proxies.get(key));
const observer = getObserver(rawObj);
const mutation = resolveMutation(
{
_type: 'Map',
entries: oldData,
},
{
_type: 'Map',
entries: Array.from(rawObj.entries()),
}
);
observer.setProp(key, mutation);
observer.setProp(COLLECTION_CHANGE, mutation);
oldData = [...Array.from(rawObj.entries())];
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function forEach(
rawObj: { forEach: (callback: (value: any, key: any) => void) => void },
callback: (valProxy: any, keyProxy: any, rawObj: any) => void
) {
const observer = getObserver(rawObj);
observer.useProp(COLLECTION_CHANGE);
rawObj.forEach((value, key) => {
const keyProxy = createProxy(value, {
current: change => {
//KEY ATTRIBUTES CHANGED
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
let mutation = resolveMutation(
{ ...rawObj, ['_keyChange']: change.mutation.from },
{ ...rawObj, ['_keyChange']: change.mutation.to }
);
listener.current({ ...change, mutation });
listeners.forEach(lst => lst({ ...change, mutation }));
},
},
hookObserverMap.get(rawObj)
);
const valProxy = createProxy(key, {
current: change => {
// VALUE ATTRIBUTE CHANGED
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
let mutation = resolveMutation(
{ ...rawObj, key: change.mutation.from },
{ ...rawObj, key: change.mutation.to }
);
listener.current({ ...change, mutation });
listeners.forEach(lst => lst({ ...change, mutation }));
},
},
hookObserverMap.get(rawObj)
);
// 最后一个参数要返回代理对象
return callback(keyProxy, valProxy, rawObj);
});
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function wrapIterator(rawObj: Object, rawIt: { next: () => { value: any; done: boolean } }, type) {
const observer = getObserver(rawObj);
const hookObserver = hookObserverMap.get(rawObj);
observer.useProp(COLLECTION_CHANGE);
return {
next() {
const { value, done } = rawIt.next();
if (done) {
return {
value: createProxy(value, {
current: change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
let mutation = resolveMutation(
{ ...rawObj, [value]: change.mutation.from },
{ ...rawObj, [value]: change.mutation.to }
);
listener.current({ ...change, mutation });
listeners.forEach(lst => lst({ ...change, mutation }));
},
},
hookObserver
),
done,
};
}
observer.useProp(COLLECTION_CHANGE);
let newVal;
if (type === 'entries') {
//ENTRY CHANGED
newVal = [
createProxy(value[0], {
current: change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
let mutation = resolveMutation(
{ ...rawObj, ['itemChange']: { key: change.mutation.from, value: value[1] } },
{ ...rawObj, ['itemChange']: { key: change.mutation.to, value: value[1] } }
);
listener.current({ ...change, mutation });
listeners.forEach(lst => lst({ ...change, mutation }));
},
},
hookObserver
),
createProxy(value[1], {
current: change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
let mutation = resolveMutation(
{ ...rawObj, item: { key: value[0], value: change.mutation.from } },
{ ...rawObj, item: { key: value[0], value: change.mutation.to } }
);
listener.current({ ...change, mutation });
listeners.forEach(lst => lst({ ...change, mutation }));
},
},
hookObserver
),
];
} else {
// SINGLE VALUE CHANGED
newVal = createProxy(value, {
current: change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
let mutation = resolveMutation(
{ ...rawObj, [type === 'keys' ? 'key' : 'value']: change.mutation.from },
{ ...rawObj, [type === 'keys' ? 'key' : 'value']: change.mutation.to }
);
listener.current({ ...change, mutation });
listeners.forEach(lst => lst({ ...change, mutation }));
},
},
hookObserver
);
}
return { value: newVal, done };
},
// 判断Symbol类型兼容IE
[typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']() {
return this;
},
};
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function size(rawObj: { size: number }) {
const observer = getObserver(rawObj);
observer.useProp(COLLECTION_CHANGE);
return rawObj.size;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function keys(rawObj: { keys: () => { next: () => { value: any; done: boolean } } }) {
return wrapIterator(rawObj, rawObj.keys(), 'keys');
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function values(rawObj: { values: () => { next: () => { value: any; done: boolean } } }) {
return wrapIterator(rawObj, rawObj.values(), 'values');
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function entries(rawObj: { entries: () => { next: () => { value: any; done: boolean } } }) {
return wrapIterator(rawObj, rawObj.entries(), 'entries');
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function forOf(rawObj: {
entries: () => { next: () => { value: any; done: boolean } };
values: () => { next: () => { value: any; done: boolean } };
}) {
return wrapIterator(rawObj, rawObj.entries(), 'entries');
}
const handler = {
get,
set,
delete: deleteFun,
clear,
has,
entries,
forEach,
keys,
values,
// 判断Symbol类型兼容IE
[typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']: forOf,
};
function get(rawObj: { size: number }, key: any, receiver: any): any {
if (key === 'size') {
return size(rawObj);
}
if (key === 'get') {
return getFun.bind(null, rawObj);
}
if (Object.prototype.hasOwnProperty.call(handler, key)) {
const value = Reflect.get(handler, key, receiver);
return value.bind(null, rawObj);
}
if (key === 'watch') {
const observer = getObserver(rawObj);
return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => {
if (!observer.watchers[prop]) {
observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[];
}
observer.watchers[prop].push(handler);
return () => {
observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler);
};
};
}
if (key === 'addListener') {
return listener => {
listeners.push(listener);
};
}
if (key === 'removeListener') {
return listener => {
listeners = listeners.filter(item => item != listener);
};
}
if (key === RAW_VALUE) {
return rawObj;
}
return Reflect.get(rawObj, key, receiver);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const boundHandler = {};
Object.entries(handler).forEach(([id, val]) => {
boundHandler[id] = (...args: any[]) => {
return (val as any)(...args, hookObserver);
};
});
getObserver(rawObj).addListener(change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
listener.current(change);
listeners.forEach(lst => lst(change));
});
return new Proxy(rawObj, { ...boundHandler });
}

View File

@ -0,0 +1,128 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* InulaJS is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { isSame, resolveMutation } from '../../CommonUtils';
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
import { OBSERVER_KEY, RAW_VALUE } from '../../Constants';
import { isPanelActive } from '../../devtools';
function set(rawObj: object, key: string, value: any, receiver: any): boolean {
const oldObject = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
const observer = getObserver(rawObj);
const oldValue = rawObj[key];
const newValue = value;
const ret = Reflect.set(rawObj, key, newValue, receiver);
const mutation = isPanelActive() ? resolveMutation(oldObject, rawObj) : resolveMutation(null, rawObj);
if (!isSame(newValue, oldValue)) {
if (observer.watchers?.[key]) {
observer.watchers[key].forEach(cb => {
cb(key, oldValue, newValue, mutation);
});
}
observer.setProp(key, mutation);
}
return ret;
}
export function createObjectProxy<T extends object>(
rawObj: T,
listener: { current: (...args) => any },
singleLevel = false
): ProxyHandler<T> {
let listeners = [] as ((...args) => void)[];
function get(rawObj: object, key: string | symbol, receiver: any): any {
// The observer object of symbol ('_inulaObserver') cannot be accessed from Proxy to prevent errors caused by clonedeep.
if (key === OBSERVER_KEY) {
return undefined;
}
const observer = getObserver(rawObj);
if (key === 'watch') {
return (prop, handler: (key: string, oldValue: any, newValue: any) => void) => {
if (!observer.watchers[prop]) {
observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[];
}
observer.watchers[prop].push(handler);
return () => {
observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler);
};
};
}
if (key === 'addListener') {
return listener => {
listeners.push(listener);
};
}
if (key === 'removeListener') {
return listener => {
listeners = listeners.filter(item => item != listener);
};
}
if (key === RAW_VALUE) {
return rawObj;
}
observer.useProp(key);
const value = Reflect.get(rawObj, key, receiver);
// 对于prototype不做代理
if (key !== 'prototype') {
// 对于value也需要进一步代理
const valProxy = singleLevel
? value
: createProxy(value, {
current: change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
let mutation = resolveMutation(
{ ...rawObj, [key]: change.mutation.from },
{ ...rawObj, [key]: change.mutation.to }
);
listener.current({ ...change, mutation });
listeners.forEach(lst => lst({ ...change, mutation }));
},
},
hookObserverMap.get(rawObj)
);
return valProxy;
}
return value;
}
const proxy = new Proxy(rawObj, {
get,
set,
});
getObserver(rawObj).addListener(change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
listener.current(change);
listeners.forEach(lst => lst(change));
});
return proxy;
}

View File

@ -0,0 +1,306 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { resolveMutation } from '../../CommonUtils';
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
import { RAW_VALUE } from '../../Constants';
const COLLECTION_CHANGE = '_collectionChange';
export function createSetProxy<T extends object>(
rawObj: T,
listener: { current: (...args) => any },
hookObserver = true
): ProxyHandler<T> {
let listeners: ((mutation) => {})[] = [];
let proxies = new WeakMap();
// Set的add方法
function add(rawObj: { add: (any) => void; has: (any) => boolean; values: () => any[] }, value: any): Object {
if (!rawObj.has(proxies.get(value))) {
const proxy = createProxy(value, {
current: change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
let mutation = resolveMutation(
{ ...rawObj, valueChange: change.mutation.from },
{ ...rawObj, valueChange: change.mutation.to }
);
listener.current({
...change,
mutation,
});
listeners.forEach(lst =>
lst({
...change,
mutation,
})
);
},
},
hookObserverMap.get(rawObj)
);
const oldValues = Array.from(rawObj.values());
proxies.set(value, proxy);
rawObj.add(proxies.get(value));
const observer = getObserver(rawObj);
const mutation = resolveMutation(
{
_type: 'Set',
values: oldValues,
},
{
_type: 'Set',
values: Array.from(rawObj.values()),
}
);
observer.setProp(value, mutation);
observer.setProp(COLLECTION_CHANGE, mutation);
}
return rawObj;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function has(rawObj: { has: (string) => boolean }, value: any): boolean {
const observer = getObserver(rawObj);
observer.useProp(value);
return rawObj.has(proxies.get(value));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function deleteFun(
rawObj: { has: (key: any) => boolean; delete: (value: any) => void; values: () => any[] },
value: any
) {
const val = rawObj.has(proxies.get(value)) ? proxies.get(value) : value;
if (rawObj.has(val)) {
const oldValues = Array.from(rawObj.values());
rawObj.delete(val);
proxies.delete(value);
const observer = getObserver(rawObj);
const mutation = resolveMutation(
{
_type: 'Set',
values: oldValues,
},
{
_type: 'Set',
values: Array.from(rawObj.values()),
}
);
observer.setProp(value, mutation);
observer.setProp(COLLECTION_CHANGE, mutation);
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function clear(rawObj: { size: number; clear: () => void }) {
const oldSize = rawObj.size;
rawObj.clear();
if (oldSize > 0) {
const observer = getObserver(rawObj);
observer.allChange();
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function size(rawObj: { size: number }) {
const observer = getObserver(rawObj);
observer.useProp(COLLECTION_CHANGE);
return rawObj.size;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const handler = {
get,
add,
delete: deleteFun,
has,
clear,
forEach,
forOf,
entries,
keys,
values,
[typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']: forOf,
};
function get(rawObj: { size: number }, key: any, receiver: any): any {
if (Object.prototype.hasOwnProperty.call(handler, key)) {
const value = Reflect.get(handler, key, receiver);
return value.bind(null, rawObj);
}
if (key === 'size') {
return size(rawObj);
}
if (key === 'addListener') {
return listener => {
listeners.push(listener);
};
}
if (key === 'removeListener') {
return listener => {
listeners = listeners.filter(item => item != listener);
};
}
if (key === 'watch') {
const observer = getObserver(rawObj);
return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => {
if (!observer.watchers[prop]) {
observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[];
}
observer.watchers[prop].push(handler);
return () => {
observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler);
};
};
}
if (key === RAW_VALUE) {
return rawObj;
}
return Reflect.get(rawObj, key, receiver);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function wrapIterator(rawObj: Object, rawIt: { next: () => { value: any; done: boolean } }) {
const observer = getObserver(rawObj);
const hookObserver = hookObserverMap.get(rawObj);
observer.useProp(COLLECTION_CHANGE);
return {
next() {
const currentListener = {
current: change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
let mutation = resolveMutation(
{ ...rawObj, valueChange: change.mutation.from },
{ ...rawObj, valueChange: change.mutation.to }
);
listener.current({
...change,
mutation,
});
listeners.forEach(lst =>
lst({
...change,
mutation,
})
);
},
};
const { value, done } = rawIt.next();
if (done) {
return { value: createProxy(value, currentListener, hookObserver), done };
}
observer.useProp(COLLECTION_CHANGE);
let newVal;
newVal = createProxy(value, currentListener, hookObserver);
return { value: newVal, done };
},
// 判断Symbol类型兼容IE
[typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']() {
return this;
},
};
}
function keys(rawObj: { keys: () => { next: () => { value: any; done: boolean } } }) {
return wrapIterator(rawObj, rawObj.keys());
}
function values(rawObj: { values: () => { next: () => { value: any; done: boolean } } }) {
return wrapIterator(rawObj, rawObj.values());
}
function entries(rawObj: { entries: () => { next: () => { value: any; done: boolean } } }) {
return wrapIterator(rawObj, rawObj.entries());
}
function forOf(rawObj: {
entries: () => { next: () => { value: any; done: boolean } };
values: () => { next: () => { value: any; done: boolean } };
}) {
const iterator = rawObj.values();
return wrapIterator(rawObj, iterator);
}
function forEach(
rawObj: { forEach: (callback: (value: any, key: any) => void) => void },
callback: (valProxy: any, keyProxy: any, rawObj: any) => void
) {
const observer = getObserver(rawObj);
observer.useProp(COLLECTION_CHANGE);
rawObj.forEach((value, key) => {
const currentListener = {
current: change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
let mutation = resolveMutation(
{ ...rawObj, valueChange: change.mutation.from },
{ ...rawObj, valueChange: change.mutation.to }
);
listener.current({
...change,
mutation,
});
listeners.forEach(lst =>
lst({
...change,
mutation,
})
);
},
};
const valProxy = createProxy(value, currentListener, hookObserverMap.get(rawObj));
const keyProxy = createProxy(key, currentListener, hookObserverMap.get(rawObj));
// 最后一个参数要返回代理对象
return callback(valProxy, keyProxy, rawObj);
});
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
getObserver(rawObj).addListener(change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
listener.current(change);
listeners.forEach(lst => lst(change));
});
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const boundHandler = {};
Object.entries(handler).forEach(([id, val]) => {
boundHandler[id] = (...args: any[]) => {
return (val as any)(...args, hookObserver);
};
});
return new Proxy(rawObj, { ...boundHandler });
}

View File

@ -0,0 +1,204 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
import { isSame } from '../../CommonUtils';
import { resolveMutation } from '../../CommonUtils';
import { isPanelActive } from '../../devtools';
import { RAW_VALUE } from '../../Constants';
const COLLECTION_CHANGE = '_collectionChange';
export function createWeakMapProxy(
rawObj: Object,
listener: { current: (...args) => any },
hookObserver = true
): Object {
let listeners: ((mutation) => {})[] = [];
const handler = {
get,
set,
add,
delete: deleteFun,
clear,
has,
};
function getFun(rawObj: { get: (key: any) => any }, key: any) {
const observer = getObserver(rawObj);
observer.useProp(key);
const value = rawObj.get(key);
// 对于value也需要进一步代理
const valProxy = createProxy(value, {
current: change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
let mutation = resolveMutation(
{ ...rawObj, [key]: change.mutation.from },
{ ...rawObj, [key]: change.mutation.to }
);
listener.current({ ...change, mutation });
listeners.forEach(lst => lst({ ...change, mutation }));
},
},
hookObserverMap.get(rawObj)
);
return valProxy;
}
function get(rawObj: { size: number }, key: any, receiver: any): any {
if (key === 'get') {
return getFun.bind(null, rawObj);
}
if (Object.prototype.hasOwnProperty.call(handler, key)) {
const value = Reflect.get(handler, key, receiver);
return value.bind(null, rawObj);
}
if (key === 'watch') {
const observer = getObserver(rawObj);
return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => {
if (!observer.watchers[prop]) {
observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[];
}
observer.watchers[prop].push(handler);
return () => {
observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler);
};
};
}
if (key === 'addListener') {
return listener => {
listeners.push(listener);
};
}
if (key === 'removeListener') {
return listener => {
listeners = listeners.filter(item => item != listener);
};
}
if (key === RAW_VALUE) {
return rawObj;
}
return Reflect.get(rawObj, key, receiver);
}
// Map的set方法
function set(
rawObj: { get: (key: any) => any; set: (key: any, value: any) => any; has: (key: any) => boolean },
key: any,
value: any
) {
const oldValue = rawObj.get(key);
const newValue = value;
rawObj.set(key, newValue);
const valChange = !isSame(newValue, oldValue);
const observer = getObserver(rawObj);
const mutation = isPanelActive() ? resolveMutation(oldValue, rawObj) : resolveMutation(null, rawObj);
if (valChange || !rawObj.has(key)) {
observer.setProp(COLLECTION_CHANGE, mutation);
}
if (valChange) {
if (observer.watchers?.[key]) {
observer.watchers[key].forEach(cb => {
cb(key, oldValue, newValue, mutation);
});
}
observer.setProp(key, mutation);
}
return rawObj;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Set的add方法
function add(rawObj: { add: (any) => void; set: (string, any) => any; has: (any) => boolean }, value: any): Object {
const oldCollection = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
if (!rawObj.has(value)) {
rawObj.add(value);
const observer = getObserver(rawObj);
const mutation = isPanelActive()
? resolveMutation(oldCollection, rawObj)
: { mutation: true, from: null, to: rawObj };
observer.setProp(value, mutation);
observer.setProp(COLLECTION_CHANGE, mutation);
}
return rawObj;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function has(rawObj: { has: (string) => boolean }, key: any): boolean {
const observer = getObserver(rawObj);
observer.useProp(key);
return rawObj.has(key);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function clear(rawObj: { size: number; clear: () => void }) {
const oldSize = rawObj.size;
rawObj.clear();
if (oldSize > 0) {
const observer = getObserver(rawObj);
observer.allChange();
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function deleteFun(rawObj: { has: (key: any) => boolean; delete: (key: any) => void }, key: any) {
const oldCollection = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
if (rawObj.has(key)) {
rawObj.delete(key);
const observer = getObserver(rawObj);
const mutation = isPanelActive()
? resolveMutation(oldCollection, rawObj)
: { mutation: true, from: null, to: rawObj };
observer.setProp(key, mutation);
observer.setProp(COLLECTION_CHANGE, mutation);
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
getObserver(rawObj).addListener(change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
listener.current(change);
listeners.forEach(lst => lst(change));
});
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const boundHandler = {};
Object.entries(handler).forEach(([id, val]) => {
boundHandler[id] = (...args: any[]) => {
return (val as any)(...args, hookObserver);
};
});
return new Proxy(rawObj, { ...boundHandler });
}

View File

@ -0,0 +1,141 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import { resolveMutation } from '../../CommonUtils';
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
import { RAW_VALUE } from '../../Constants';
export function createWeakSetProxy<T extends object>(
rawObj: T,
listener: { current: (...args) => any },
hookObserver = true,
): ProxyHandler<T> {
let listeners: ((mutation) => {})[] = [];
let proxies = new WeakMap();
const handler = {
get,
add,
delete: deleteFun,
has,
};
function get(rawObj: { size: number }, key: any, receiver: any): any {
if (Object.prototype.hasOwnProperty.call(handler, key)) {
const value = Reflect.get(handler, key, receiver);
return value.bind(null, rawObj);
}
if (key === 'addListener') {
return listener => {
listeners.push(listener);
};
}
if (key === 'removeListener') {
return listener => {
listeners = listeners.filter(item => item != listener);
};
}
if (key === 'watch') {
const observer = getObserver(rawObj);
return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => {
if (!observer.watchers[prop]) {
observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[];
}
observer.watchers[prop].push(handler);
return () => {
observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler);
};
};
}
if (key === RAW_VALUE) {
return rawObj;
}
return Reflect.get(rawObj, key, receiver);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Set的add方法
function add(rawObj: { add: (any) => void; has: (any) => boolean }, value: any): Object {
if (!rawObj.has(proxies.get(value))) {
const proxy = createProxy(value, {
current: change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
let mutation = resolveMutation(
{ ...rawObj, [value]: change.mutation.from },
{ ...rawObj, [value]: change.mutation.to }
);
listener.current({ ...change, mutation });
listeners.forEach(lst => lst({ ...change, mutation }));
},
},
hookObserverMap.get(rawObj)
);
proxies.set(value, proxy);
rawObj.add(proxies.get(value));
const observer = getObserver(rawObj);
const mutation = { mutation: true, from: rawObj, to: value };
observer.setProp(value, mutation);
}
return rawObj;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function has(rawObj: { has: (string) => boolean }, value: any): boolean {
const observer = getObserver(rawObj);
observer.useProp(value);
return rawObj.has(proxies.get(value));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function deleteFun(rawObj: { has: (key: any) => boolean; delete: (value: any) => void }, value: any) {
if (rawObj.has(proxies.get(value))) {
rawObj.delete(proxies.get(value));
proxies.delete(value);
const observer = getObserver(rawObj);
const mutation = { mutation: true, from: value, to: rawObj };
observer.setProp(value, mutation);
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
getObserver(rawObj).addListener(change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
listener.current(change);
listeners.forEach(lst => lst(change));
});
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const boundHandler = {};
Object.entries(handler).forEach(([id, val]) => {
boundHandler[id] = (...args: any[]) => {
return (val as any)(...args, hookObserver);
};
});
return new Proxy(rawObj, { ...boundHandler });
}

View File

@ -38,6 +38,7 @@ import {
ACTION_QUEUED,
INITIALIZED,
QUEUE_FINISHED,
QUEUE_PENDING,
STATE_CHANGE,
SUBSCRIBED,
UNSUBSCRIBED,
@ -52,6 +53,100 @@ const idGenerator = {
const storeMap = new Map<string, StoreObj<any, any, any>>();
// 通过该方法执行store.$queue中的action
function tryNextAction(storeObj, proxyObj, config, plannedActions) {
if (!plannedActions.length) {
if (proxyObj.$pending) {
const timestamp = Date.now();
const duration = timestamp - proxyObj.$pending;
proxyObj.$pending = false;
devtools.emit(QUEUE_FINISHED, {
store: storeObj,
endedAt: timestamp,
duration,
});
}
return;
}
const nextAction = plannedActions.shift()!;
const result = config.actions
? config.actions[nextAction.action].bind(storeObj, proxyObj)(...nextAction.payload)
: undefined;
if (isPromise(result)) {
result.then(value => {
nextAction.resolve(value);
tryNextAction(storeObj, proxyObj, config, plannedActions);
});
} else {
nextAction.resolve(result);
tryNextAction(storeObj, proxyObj, config, plannedActions);
}
}
// 删除Observers中保存的这个VNode的相关数据
export function clearVNodeObservers(vNode: VNode) {
if (!vNode.observers) {
return;
}
vNode.observers.forEach(observer => {
observer.clearByVNode(vNode);
});
vNode.observers.clear();
}
// 注册VNode销毁时的清理动作
function registerDestroyFunction() {
const processingVNode = getProcessingVNode();
// 获取不到当前运行的VNode说明不在组件中运行属于非法场景
if (!processingVNode) {
return;
}
if (!processingVNode.observers) {
processingVNode.observers = new Set<Observer>();
}
// 函数组件
if (processingVNode.tag === FunctionComponent) {
const vNodeRef = useRef(processingVNode);
useEffect(() => {
return () => {
clearVNodeObservers(vNodeRef.current);
vNodeRef.current.observers = null;
};
}, []);
} else if (processingVNode.tag === ClassComponent) {
// 类组件
if (!processingVNode.classComponentWillUnmount) {
processingVNode.classComponentWillUnmount = vNode => {
clearVNodeObservers(vNode);
vNode.observers = null;
};
}
}
}
// createStore返回的是一个getStore的函数这个函数必须要在组件函数/类组件里面被执行因为要注册VNode销毁时的清理动作
function createGetStore<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>>(
storeObj: StoreObj<S, A, C>
): () => StoreObj<S, A, C> {
const getStore = () => {
if (!storeObj.$config.options?.isReduxAdapter) {
registerDestroyFunction();
}
return storeObj;
};
return getStore;
}
export function createStore<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>>(
config: StoreConfig<S, A, C>
): () => StoreObj<S, A, C> {
@ -62,7 +157,11 @@ export function createStore<S extends object, A extends UserActions<S>, C extend
const id = config.id || idGenerator.get('UNNAMED_STORE');
const proxyObj = createProxy(config.state, id, !config.options?.isReduxAdapter);
const listener = {
current: listener => {},
};
const proxyObj = createProxy(config.state, listener, !config.options?.isReduxAdapter);
proxyObj.$pending = false;
@ -76,16 +175,28 @@ export function createStore<S extends object, A extends UserActions<S>, C extend
$c: $c as ComputedValues<S, C>,
$queue: $queue as QueuedStoreActions<S, A>,
$config: config,
$listeners: [
change => {
devtools.emit(STATE_CHANGE, {
store: storeObj,
change,
});
},
],
$subscribe: listener => {
devtools.emit(SUBSCRIBED, { store: storeObj, listener });
proxyObj.addListener(listener);
storeObj.$listeners.push(listener);
},
$unsubscribe: listener => {
devtools.emit(UNSUBSCRIBED, storeObj);
proxyObj.removeListener(listener);
devtools.emit(UNSUBSCRIBED, { store: storeObj });
storeObj.$listeners = storeObj.$listeners.filter(item => item != listener);
},
} as unknown as StoreObj<S, A, C>;
listener.current = (...args) => {
storeObj.$listeners.forEach(listener => listener(...args));
};
const plannedActions: PlannedAction<S, ActionFunction<S>>[] = [];
// 包装actions
@ -104,7 +215,11 @@ export function createStore<S extends object, A extends UserActions<S>, C extend
});
return new Promise(resolve => {
if (!proxyObj.$pending) {
proxyObj.$pending = true;
proxyObj.$pending = Date.now();
devtools.emit(QUEUE_PENDING, {
store: storeObj,
startedAt: proxyObj.$pending,
});
const result = config.actions![action].bind(storeObj, proxyObj)(...payload);
@ -192,101 +307,9 @@ export function createStore<S extends object, A extends UserActions<S>, C extend
store: storeObj,
});
proxyObj.addListener(change => {
devtools.emit(STATE_CHANGE, {
store: storeObj,
change,
});
});
return createGetStore(storeObj);
}
// 通过该方法执行store.$queue中的action
function tryNextAction(storeObj, proxyObj, config, plannedActions) {
if (!plannedActions.length) {
proxyObj.$pending = false;
return;
}
const nextAction = plannedActions.shift()!;
const result = config.actions
? config.actions[nextAction.action].bind(storeObj, proxyObj)(...nextAction.payload)
: undefined;
if (isPromise(result)) {
result.then(value => {
nextAction.resolve(value);
tryNextAction(storeObj, proxyObj, config, plannedActions);
});
} else {
nextAction.resolve(result);
tryNextAction(storeObj, proxyObj, config, plannedActions);
}
}
// createStore返回的是一个getStore的函数这个函数必须要在组件函数/类组件里面被执行因为要注册VNode销毁时的清理动作
function createGetStore<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>>(
storeObj: StoreObj<S, A, C>
): () => StoreObj<S, A, C> {
const getStore = () => {
if (!storeObj.$config.options?.isReduxAdapter) {
registerDestroyFunction();
}
return storeObj;
};
return getStore;
}
// 删除Observers中保存的这个VNode的相关数据
export function clearVNodeObservers(vNode: VNode) {
if (!vNode.observers) {
return;
}
vNode.observers.forEach(observer => {
observer.clearByVNode(vNode);
});
vNode.observers.clear();
}
// 注册VNode销毁时的清理动作
function registerDestroyFunction() {
const processingVNode = getProcessingVNode();
// 获取不到当前运行的VNode说明不在组件中运行属于非法场景
if (!processingVNode) {
return;
}
if (!processingVNode.observers) {
processingVNode.observers = new Set<Observer>();
}
// 函数组件
if (processingVNode.tag === FunctionComponent) {
const vNodeRef = useRef(processingVNode);
useEffect(() => {
return () => {
clearVNodeObservers(vNodeRef.current);
vNodeRef.current.observers = null;
};
}, []);
} else if (processingVNode.tag === ClassComponent) {
// 类组件
if (!processingVNode.classComponentWillUnmount) {
processingVNode.classComponentWillUnmount = vNode => {
clearVNodeObservers(vNode);
vNode.observers = null;
};
}
}
}
// 函数组件中使用的hook
export function useStore<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>>(
id: string

View File

@ -61,6 +61,7 @@ export type StoreObj<S extends object, A extends UserActions<S>, C extends UserC
$a: StoreActions<S, A>;
$c: UserComputedValues<S>;
$queue: QueuedStoreActions<S, A>;
$listeners;
$subscribe: (listener: (mutation) => void) => void;
$unsubscribe: (listener: (mutation) => void) => void;
} & { [K in keyof S]: S[K] } & { [K in keyof A]: Action<A[K], S> } & { [K in keyof C]: ReturnType<C[K]> };

View File

@ -72,7 +72,7 @@ function createClassErrorUpdate(vNode: VNode, error: any): Update {
}
return update;
}
function isPromise(error: any): error is PromiseType<any> {
export function isPromise(error: any): error is PromiseType<any> {
return error !== null && typeof error === 'object' && typeof error.then === 'function';
}
// 处理capture和bubble阶段抛出的错误

View File

@ -14,15 +14,21 @@
*/
import { VNode } from './vnode/VNode';
const currentRootStack: VNode[] = [];
const currentRootStack: (VNode | undefined)[] = [];
let index = -1;
export function getCurrentRoot() {
return currentRootStack[currentRootStack.length - 1];
return currentRootStack[index];
}
export function pushCurrentRoot(root: VNode) {
return currentRootStack.push(root);
index++;
currentRootStack[index] = root;
}
export function popCurrentRoot() {
return currentRootStack.pop();
const target = currentRootStack[index];
currentRootStack[index] = undefined;
index--;
return target;
}

View File

@ -139,7 +139,7 @@ function bubbleVNode(vNode: VNode): void {
node = parent;
// 更新processing抛出异常时可以使用
processing = node;
} while (node !== null);
} while (node);
// 修改结果
if (getBuildResult() === BuildInComplete) {
@ -179,6 +179,11 @@ function isEqualByIndex(idx: number, pathArrays: string[][]) {
function getChildByIndex(vNode: VNode, idx: number) {
let node = vNode.child;
for (let i = 0; i < idx; i++) {
// 场景当组件被销毁业务若异步定时器调用setState修改状态可能出现路径错误此处进行保护。
if (node === null || node === undefined) {
return null;
}
node = node.next;
}
return node;
@ -220,7 +225,7 @@ export function calcStartUpdateVNode(treeRoot: VNode) {
const pathIndex = Number(startNodePath[i]);
node = getChildByIndex(node, pathIndex)!;
// 路径错误时,回退到从根更新
if (node == null) {
if (node === null) {
return treeRoot;
}
}
@ -228,6 +233,40 @@ export function calcStartUpdateVNode(treeRoot: VNode) {
return node;
}
// 在局部更新时从上到下恢复父节点的context和PortalStack
function recoverTreeContext(vNode: VNode) {
const contextProviders: VNode[] = [];
let parent = vNode.parent;
while (parent !== null) {
if (parent.tag === ContextProvider) {
contextProviders.unshift(parent);
}
if (parent.tag === DomPortal) {
pushCurrentRoot(parent);
}
parent = parent.parent;
}
contextProviders.forEach(node => {
setContext(node, node.props.value);
});
}
// 在局部更新时从下到上重置父节点的context
function resetTreeContext(vNode: VNode) {
let parent = vNode.parent;
while (parent !== null) {
if (parent.tag === ContextProvider) {
resetContext(parent);
}
if (parent.tag === DomPortal) {
popCurrentRoot();
}
parent = parent.parent;
}
}
// ============================== 深度遍历 ==============================
function buildVNodeTree(treeRoot: VNode) {
const preMode = copyExecuteMode();
@ -299,43 +338,11 @@ function buildVNodeTree(treeRoot: VNode) {
setExecuteMode(preMode);
}
// 在局部更新时从上到下恢复父节点的context和PortalStack
function recoverTreeContext(vNode: VNode) {
const contextProviders: VNode[] = [];
let parent = vNode.parent;
while (parent !== null) {
if (parent.tag === ContextProvider) {
contextProviders.unshift(parent);
}
if (parent.tag === DomPortal) {
pushCurrentRoot(parent);
}
parent = parent.parent;
}
contextProviders.forEach(node => {
setContext(node, node.props.value);
});
}
// 在局部更新时从下到上重置父节点的context
function resetTreeContext(vNode: VNode) {
let parent = vNode.parent;
while (parent !== null) {
if (parent.tag === ContextProvider) {
resetContext(parent);
}
if (parent.tag === DomPortal) {
popCurrentRoot();
}
parent = parent.parent;
}
}
// 总体任务入口
function renderFromRoot(treeRoot) {
runAsyncEffects();
pushCurrentRoot(treeRoot);
// 1. 构建vNode树
buildVNodeTree(treeRoot);
@ -346,11 +353,12 @@ function renderFromRoot(treeRoot) {
// 2. 提交变更
submitToRender(treeRoot);
popCurrentRoot();
if (window.__HORIZON_DEV_HOOK__) {
const hook = window.__HORIZON_DEV_HOOK__;
// injector.js 可能在 Horizon 代码之后加载,此时无 __HORIZON_DEV_HOOK__ 全局变量
// Horizon 代码初次加载时不会初始化 helper
if (window.__INULA_DEV_HOOK__) {
const hook = window.__INULA_DEV_HOOK__;
// injector.js 可能在 Inula 代码之后加载,此时无 __INULA_DEV_HOOK__ 全局变量
// Inula 代码初次加载时不会初始化 helper
if (!hook.isInit) {
injectUpdater();
}
@ -390,7 +398,7 @@ export function launchUpdateFromVNode(vNode: VNode) {
) {
// 不是渲染阶段触发
// 业务直接调用Horizon.render的时候会进入这个分支同步渲染。
// 业务直接调用Inula.render的时候会进入这个分支同步渲染。
// 不能改成下面的异步,否则会有时序问题,因为业务可能会依赖这个渲染的完成。
renderFromRoot(treeRoot);
} else {
@ -403,7 +411,7 @@ export function launchUpdateFromVNode(vNode: VNode) {
}
}
// ============================== HorizonDOM使用 ==============================
// ============================== InulaDOM使用 ==============================
export function runDiscreteUpdates() {
if (checkMode(ByAsync) || checkMode(InRender)) {
// 已经渲染不能再同步执行待工作的任务有可能是被生命周期或effect触发的事件导致的如el.focus()

View File

@ -13,6 +13,8 @@
* See the Mulan PSL v2 for more details.
*/
import { BELONG_CLASS_VNODE_KEY } from './vnode/VNode';
export { VNode } from './vnode/VNode';
type Trigger<A> = (A) => void;
@ -32,7 +34,7 @@ export type JSXElement = {
key: any;
ref: any;
props: any;
belongClassVNode: any;
[BELONG_CLASS_VNODE_KEY]: any;
};
export type ProviderType<T> = {
@ -77,3 +79,5 @@ export type Source = {
fileName: string;
lineNumber: number;
};
export type Callback = () => void;

View File

@ -13,7 +13,7 @@
* See the Mulan PSL v2 for more details.
*/
import type { VNode } from './Types';
import type { VNode, Callback } from './Types';
import { FlagUtils, ShouldCapture } from './vnode/VNodeFlags';
export type Update = {
@ -22,8 +22,6 @@ export type Update = {
callback: Callback | null;
};
export type Callback = () => any;
export type Updates = Array<Update> | null;
export enum UpdateState {
@ -37,8 +35,8 @@ export enum UpdateState {
export function newUpdate(): Update {
return {
type: UpdateState.Update, // 更新的类型
content: null, // ClassComponent的content是setState第一个参数TreeRoot的content是HorizonDOM.render的第一个参数
callback: null, // setState的第二个参数HorizonDOM.render的第三个参数
content: null, // ClassComponent的content是setState第一个参数TreeRoot的content是InulaDOM.render的第一个参数
callback: null, // setState的第二个参数InulaDOM.render的第三个参数
};
}

View File

@ -13,6 +13,8 @@
* See the Mulan PSL v2 for more details.
*/
import {Callback} from '../Types';
/**
* Component的api setState和forceUpdate在实例生成阶段实现
*/
@ -29,9 +31,9 @@ class Component<P, S, C> {
this.context = context;
}
setState(state: S) {
setState(state: S, callback?: Callback) {
if (isDev) {
console.error('Cant not call `this.setState` in the constructor of class component, it will do nothing');
console.error('Can not call `this.setState` in the constructor of class component, it will do nothing');
}
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* InulaJS is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
import {TYPE_FORWARD_REF, TYPE_MEMO} from '../../external/JSXElementType';
export function forwardRef(render: Function) {
const forwardRefJSXElement = {
vtype: TYPE_FORWARD_REF,
$$typeof: TYPE_FORWARD_REF, // 规避三方件hoist-non-react-statics中通过$$typeof获取类型但获取不到导致render被覆盖
render,
};
// 控制vtype不能修改规避三方件hoist-non-react-statics修改vtype导致问题
Object.defineProperty(forwardRefJSXElement, 'vtype', {
configurable: false,
writable: false,
});
return forwardRefJSXElement;
}

View File

@ -16,9 +16,18 @@
import { TYPE_MEMO } from '../../external/JSXElementType';
export function memo<Props>(type, compare?: (oldProps: Props, newProps: Props) => boolean) {
return {
const memoJSXElement = {
vtype: TYPE_MEMO,
$$typeof: TYPE_MEMO, // 规避三方件hoist-non-react-statics中通过$$typeof获取类型但获取不到导致type被覆盖
type: type,
compare: compare === undefined ? null : compare,
};
// 控制vtype不能修改规避三方件hoist-non-react-statics修改vtype导致问题
Object.defineProperty(memoJSXElement, 'vtype', {
configurable: false,
writable: false,
});
return memoJSXElement;
}

View File

@ -27,6 +27,7 @@ import {
import { isSameType, getIteratorFn, isTextType, isIteratorType, isObjectType } from './DiffTools';
import { travelChildren } from '../vnode/VNodeUtils';
import { markVNodePath } from '../utils/vNodePath';
import { BELONG_CLASS_VNODE_KEY } from '../vnode/VNode';
enum DiffCategory {
TEXT_NODE = 'TEXT_NODE',
@ -166,11 +167,11 @@ function getNewNode(parentNode: VNode, newChild: any, oldNode: VNode | null) {
if (oldNode === null || !isSameType(oldNode, newChild)) {
resultNode = createVNodeFromElement(newChild);
resultNode.ref = newChild.ref;
resultNode.belongClassVNode = newChild.belongClassVNode;
resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY];
} else {
resultNode = updateVNode(oldNode, newChild.props);
resultNode.ref = newChild.ref;
resultNode.belongClassVNode = newChild.belongClassVNode;
resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY];
}
break;
} else if (newChild.vtype === TYPE_PORTAL) {
@ -181,6 +182,10 @@ function getNewNode(parentNode: VNode, newChild: any, oldNode: VNode | null) {
}
break;
}
break;
}
default: {
break;
}
}
@ -231,6 +236,19 @@ function getOldNodeFromMap(nodeMap: Map<string | number, VNode>, newIdx: number,
return null;
}
// 设置vNode中的cIndex属性cIndex是节点在children中的位置
function setVNodesCIndex(startChild: VNode | null, startIdx: number) {
let node: VNode | null = startChild;
let idx = startIdx;
while (node !== null) {
node.cIndex = idx;
markVNodePath(node);
node = node.next;
idx++;
}
}
// diff数组类型的节点核心算法
function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newChildren: Array<any>): VNode | null {
let resultingFirstChild: VNode | null = null;
@ -360,7 +378,7 @@ function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newC
// 4. 新节点还有一部分,但是老节点已经没有了
if (oldNode === null) {
let isDirectAdd = false;
// TODO: 是否可以扩大至非dom类型节点
// 是否可以扩大至非dom类型节点待确认
// 如果dom节点在上次添加前没有节点说明本次添加时可以直接添加到最后不需要通过 getSiblingDom 函数找到 before 节点
if (
parentNode.tag === DomComponent &&
@ -478,19 +496,6 @@ function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newC
return resultingFirstChild;
}
// 设置vNode中的cIndex属性cIndex是节点在children中的位置
function setVNodesCIndex(startChild: VNode | null, startIdx: number) {
let node: VNode | null = startChild;
let idx = startIdx;
while (node !== null) {
node.cIndex = idx;
markVNodePath(node);
node = node.next;
idx++;
}
}
// 新节点是迭代器类型
function diffIteratorNodesHandler(
parentNode: VNode,
@ -512,7 +517,7 @@ function diffIteratorNodesHandler(
}
// 新节点是字符串类型
function diffStringNodeHandler(parentNode: VNode, newChild: any, firstChildVNode: VNode, isComparing: boolean) {
function diffStringNodeHandler(parentNode: VNode, newChild: any, firstChildVNode: VNode | null, isComparing: boolean) {
let newTextNode: VNode | null = null;
// 第一个vNode是Text则复用
@ -559,7 +564,7 @@ function diffObjectNodeHandler(
}
let resultNode: VNode | null = null;
let startDelVNode = firstChildVNode;
let startDelVNode: VNode | null = firstChildVNode;
if (newChild.vtype === TYPE_COMMON_ELEMENT) {
if (canReuseNode) {
// 可以复用
@ -570,7 +575,7 @@ function diffObjectNodeHandler(
} else if (isSameType(canReuseNode, newChild)) {
resultNode = updateVNode(canReuseNode, newChild.props);
resultNode.ref = newChild.ref;
resultNode.belongClassVNode = newChild.belongClassVNode;
resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY];
startDelVNode = resultNode.next;
resultNode.next = null;
}
@ -583,7 +588,7 @@ function diffObjectNodeHandler(
} else {
resultNode = createVNodeFromElement(newChild);
resultNode.ref = newChild.ref;
resultNode.belongClassVNode = newChild.belongClassVNode;
resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY];
}
}
} else if (newChild.vtype === TYPE_PORTAL) {

View File

@ -34,7 +34,7 @@ export function setCurrentHook(hook: Hook<any, any> | null) {
currentHook = hook;
}
export function throwNotInFuncError() {
export function throwNotInFuncError(): never {
throw Error('Hooks should be used inside function component.');
}
@ -52,7 +52,7 @@ export function createHook(state: any = null): Hook<any, any> {
return currentHook;
}
export function getNextHook(hook: Hook<any, any>, hooks: Array<Hook<any, any>>) {
export function getNextHook(hook: Hook<any, any>, hooks: Array<Hook<any, any>>): Hook<any, any> | null {
return hooks[hook.hIndex + 1] || null;
}

View File

@ -27,22 +27,25 @@ import { getProcessingVNode } from '../GlobalVar';
import { Ref, Trigger } from './HookType';
type BasicStateAction<S> = ((S) => S) | S;
type Dispatch<A> = (A) => void;
type Dispatch<A> = (value: A) => void;
export function useContext<T>(Context: ContextType<T>): T {
const processingVNode = getProcessingVNode();
return getNewContext(processingVNode!, Context, true);
}
export function useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>] {
export function useState<S = undefined>(): [S | undefined, Dispatch<BasicStateAction<S | undefined>>]
export function useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>]
export function useState<S>(initialState?: (() => S) | S): [S, Dispatch<BasicStateAction<S>>] {
return useStateImpl(initialState);
}
export function useReducer<S, I, A>(reducer: (S, A) => S, initialArg: I, init?: (I) => S): [S, Trigger<A>] | void {
export function useReducer<S, I, A>(reducer: (S, A) => S, initialArg: I, init?: (I) => S): [S, Trigger<A>] {
return useReducerImpl(reducer, initialArg, init);
}
export function useRef<T>(initialValue: T): Ref<T> {
export function useRef<T = undefined>(): Ref<T | undefined>
export function useRef<T>(initialValue: T): Ref<T>
export function useRef<T>(initialValue?: T): Ref<T> {
return useRefImpl(initialValue);
}

View File

@ -18,6 +18,12 @@ import type { VNode } from '../Types';
import { getLastTimeHook, setLastTimeHook, setCurrentHook, getNextHook } from './BaseHook';
import { HookStage, setHookStage } from './HookStage';
function resetGlobalVariable() {
setHookStage(null);
setLastTimeHook(null);
setCurrentHook(null);
}
// hook对外入口
export function runFunctionWithHooks<Props extends Record<string, any>, Arg>(
funcComp: (props: Props, arg: Arg) => any,
@ -57,9 +63,3 @@ export function runFunctionWithHooks<Props extends Record<string, any>, Arg>(
return comp;
}
function resetGlobalVariable() {
setHookStage(null);
setLastTimeHook(null);
setCurrentHook(null);
}

View File

@ -57,4 +57,4 @@ export type Ref<V> = {
current: V;
};
export type Trigger<A> = (A) => void;
export type Trigger<A> = (state: A) => void;

View File

@ -21,27 +21,17 @@ import { getHookStage, HookStage } from './HookStage';
import { isArrayEqual } from '../utils/compare';
import { getProcessingVNode } from '../GlobalVar';
export function useEffectImpl(effectFunc: () => (() => void) | void, deps?: Array<any> | null): void {
// 异步触发的effect
useEffect(effectFunc, deps, EffectConstant.Effect);
}
function createEffect(effectFunc, removeFunc, deps, effectConstant): Effect {
const effect: Effect = {
effect: effectFunc,
removeEffect: removeFunc,
dependencies: deps,
effectConstant: effectConstant,
};
export function useLayoutEffectImpl(effectFunc: () => (() => void) | void, deps?: Array<any> | null): void {
// 同步触发的effect
useEffect(effectFunc, deps, EffectConstant.LayoutEffect);
}
getProcessingVNode().effectList.push(effect);
function useEffect(effectFunc: () => (() => void) | void, deps: Array<any> | void | null, effectType: number): void {
const stage = getHookStage();
if (stage === null) {
throwNotInFuncError();
}
if (stage === HookStage.Init) {
useEffectForInit(effectFunc, deps, effectType);
} else if (stage === HookStage.Update) {
useEffectForUpdate(effectFunc, deps, effectType);
}
return effect;
}
export function useEffectForInit(effectFunc, deps, effectType): void {
@ -76,15 +66,25 @@ export function useEffectForUpdate(effectFunc, deps, effectType): void {
hook.state = createEffect(effectFunc, removeFunc, nextDeps, EffectConstant.DepsChange | effectType);
}
function createEffect(effectFunc, removeFunc, deps, effectConstant): Effect {
const effect: Effect = {
effect: effectFunc,
removeEffect: removeFunc,
dependencies: deps,
effectConstant: effectConstant,
};
function useEffect(effectFunc: () => (() => void) | void, deps: Array<any> | void | null, effectType: number): void {
const stage = getHookStage();
if (stage === null) {
throwNotInFuncError();
}
getProcessingVNode().effectList.push(effect);
return effect;
if (stage === HookStage.Init) {
useEffectForInit(effectFunc, deps, effectType);
} else if (stage === HookStage.Update) {
useEffectForUpdate(effectFunc, deps, effectType);
}
}
export function useEffectImpl(effectFunc: () => (() => void) | void, deps?: Array<any> | null): void {
// 异步触发的effect
useEffect(effectFunc, deps, EffectConstant.Effect);
}
export function useLayoutEffectImpl(effectFunc: () => (() => void) | void, deps?: Array<any> | null): void {
// 同步触发的effect
useEffect(effectFunc, deps, EffectConstant.LayoutEffect);
}

View File

@ -17,26 +17,9 @@ import { useLayoutEffectImpl } from './UseEffectHook';
import { getHookStage } from './HookStage';
import { throwNotInFuncError } from './BaseHook';
import type { Ref } from './HookType';
import { isNotNull } from '../../dom/utils/Common';
export function useImperativeHandleImpl<R>(
ref: { current: R | null } | ((any) => any) | null | void,
func: () => R,
dependencies?: Array<any> | null
): void {
const stage = getHookStage();
if (stage === null) {
throwNotInFuncError();
}
const params = isNotNull(dependencies) ? dependencies.concat([ref]) : null;
useLayoutEffectImpl(effectFunc.bind(null, func, ref), params);
}
function isNotNull(object: any): boolean {
return object !== null && object !== undefined;
}
function effectFunc<R>(func: () => R, ref: Ref<R> | ((any) => any) | null): (() => void) | void {
function effectFunc<R>(func: () => R, ref: Ref<R> | ((any) => any) | null): (() => void) | null {
if (typeof ref === 'function') {
const value = func();
ref(value);
@ -51,4 +34,19 @@ function effectFunc<R>(func: () => R, ref: Ref<R> | ((any) => any) | null): (()
ref.current = null;
};
}
return null;
}
export function useImperativeHandleImpl<R>(
ref: { current: R | null } | ((any) => any) | null | void,
func: () => R,
dependencies?: Array<any> | null
): void {
const stage = getHookStage();
if (stage === null) {
throwNotInFuncError();
}
const params = isNotNull(dependencies) ? dependencies.concat([ref]) : null;
useLayoutEffectImpl(effectFunc.bind(null, func, ref), params);
}

View File

@ -22,29 +22,6 @@ import { getHookStage, HookStage } from './HookStage';
import type { VNode } from '../Types';
import { getProcessingVNode } from '../GlobalVar';
export function useReducerImpl<S, P, A>(
reducer: (S, A) => S,
initArg: P,
init?: (P) => S,
isUseState?: boolean
): [S, Trigger<A>] | void {
const stage = getHookStage();
if (stage === null) {
throwNotInFuncError();
}
if (stage === HookStage.Init) {
return useReducerForInit(reducer, initArg, init, isUseState);
} else if (stage === HookStage.Update) {
// 获取当前的hook
const currentHook = getCurrentHook();
// 获取currentHook的更新数组
const currentHookUpdates = (currentHook.state as Reducer<S, A>).updates;
return updateReducerHookState(currentHookUpdates, currentHook, reducer);
}
}
// 构造新的Update数组
function insertUpdate<S, A>(action: A, hook: Hook<S, A>): Update<S, A> {
const newUpdate: Update<S, A> = {
@ -116,6 +93,25 @@ export function useReducerForInit<S, A>(reducer, initArg, init, isUseState?: boo
return [hook.state.stateValue, trigger];
}
// 计算stateValue值
function calculateNewState<S, A>(currentHookUpdates: Array<Update<S, A>>, currentHook, reducer: (S, A) => S) {
const reducerObj = currentHook.state;
let state = reducerObj.stateValue;
// 循环遍历更新数组,计算新的状态值
currentHookUpdates.forEach(update => {
// 1. didCalculated = true 说明state已经计算过; 2. 如果来自 isUseState
if (update.didCalculated && reducerObj.isUseState) {
state = update.state;
} else {
const action = update.action;
state = reducer(state, action);
}
});
return state;
}
// 更新hook.state
function updateReducerHookState<S, A>(currentHookUpdates, currentHook, reducer): [S, Trigger<A>] {
if (currentHookUpdates !== null) {
@ -135,21 +131,25 @@ function updateReducerHookState<S, A>(currentHookUpdates, currentHook, reducer):
return [currentHook.state.stateValue, currentHook.state.trigger];
}
// 计算stateValue值
function calculateNewState<S, A>(currentHookUpdates: Array<Update<S, A>>, currentHook, reducer: (S, A) => S) {
const reducerObj = currentHook.state;
let state = reducerObj.stateValue;
export function useReducerImpl<S, P, A>(
reducer: (S, A) => S,
initArg: P,
init?: (P) => S,
isUseState?: boolean
): [S, Trigger<A>] | void {
const stage = getHookStage();
if (stage === null) {
throwNotInFuncError();
}
// 循环遍历更新数组,计算新的状态值
currentHookUpdates.forEach(update => {
// 1. didCalculated = true 说明state已经计算过; 2. 如果来自 isUseState
if (update.didCalculated && reducerObj.isUseState) {
state = update.state;
} else {
const action = update.action;
state = reducer(state, action);
}
});
if (stage === HookStage.Init) {
return useReducerForInit(reducer, initArg, init, isUseState);
} else if (stage === HookStage.Update) {
// 获取当前的hook
const currentHook = getCurrentHook();
// 获取currentHook的更新数组
const currentHookUpdates = (currentHook.state as Reducer<S, A>).updates;
return state;
return updateReducerHookState(currentHookUpdates, currentHook, reducer);
}
}

Some files were not shown because too many files have changed in this diff Show More