Match-id-86ccf4dae9f9c5614127dc3e5ffc401effbf9913
This commit is contained in:
commit
65df4422f3
|
@ -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":[]}}}
|
||||
|
|
|
@ -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 }],
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
# `horizon`
|
|
@ -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';
|
|
@ -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);
|
||||
}
|
||||
});
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
},
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
# `inula`
|
|
@ -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;
|
|
@ -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;
|
|
@ -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
|
||||
};
|
|
@ -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
|
||||
};
|
|
@ -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');
|
||||
}
|
|
@ -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",
|
|
@ -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);
|
|
@ -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 {
|
|
@ -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) {
|
|
@ -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);
|
|
@ -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',
|
||||
];
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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.');
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
|
@ -14,7 +14,7 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* Horizon的输入框和文本框的change事件在原生的change事件上做了一层处理
|
||||
* Inula的输入框和文本框的change事件在原生的change事件上做了一层处理
|
||||
* 只有值发生变化时才会触发change事件。
|
||||
*/
|
||||
|
|
@ -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);
|
|
@ -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);
|
||||
}
|
|
@ -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)}`;
|
||||
}
|
|
@ -37,6 +37,9 @@ export class WrappedEvent {
|
|||
key: string;
|
||||
currentTarget: EventTarget | null = null;
|
||||
|
||||
target: HTMLElement;
|
||||
relatedTarget: HTMLElement;
|
||||
|
||||
stopPropagation: () => void;
|
||||
preventDefault: () => void;
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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()) {
|
|
@ -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];
|
||||
}
|
|
@ -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事件不冒泡,所以无法直接委托给根节点进行代理
|
||||
* 实现方案:利用mouseout、mouseover事件的,找到事件触发的起点和终点,判断出鼠标移动轨迹,在轨迹中的节点触发mouseEnter和mouseLeave事件
|
||||
* 步骤:
|
||||
* 1. 根节点绑定mouseout和mouseover事件
|
||||
* 2. 事件触发后找到事件的起点和终点
|
||||
* 3. 封装装enter和leave事件
|
||||
* 4. 根据起止点找到公共父节点,作为事件冒泡的终点
|
||||
* 5. 遍历treeNode,找到每个节点绑定的mouseEnter和mouseLeave监听方法
|
||||
* 例如: mouseOut事件由D->C, A节点作为公共父节点,将触发 D、B的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);
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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 => {
|
|
@ -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, []);
|
||||
}
|
|
@ -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
|
||||
};
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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';
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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';
|
|
@ -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
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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];
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 });
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 });
|
||||
}
|
|
@ -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 });
|
||||
}
|
|
@ -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 });
|
||||
}
|
|
@ -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
|
|
@ -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]> };
|
|
@ -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阶段抛出的错误
|
|
@ -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;
|
||||
}
|
|
@ -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()
|
|
@ -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;
|
|
@ -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的第三个参数
|
||||
};
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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) {
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -57,4 +57,4 @@ export type Ref<V> = {
|
|||
current: V;
|
||||
};
|
||||
|
||||
export type Trigger<A> = (A) => void;
|
||||
export type Trigger<A> = (state: A) => void;
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue