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',
|
'@babel/plugin-transform-react-jsx',
|
||||||
{
|
{
|
||||||
pragma: 'Horizon.createElement',
|
pragma: 'Inula.createElement',
|
||||||
pragmaFrag: 'Horizon.Fragment',
|
pragmaFrag: 'Inula.Fragment',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
['@babel/plugin-proposal-class-properties', { loose: true }],
|
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||||
|
|
|
@ -26,8 +26,10 @@ module.exports = {
|
||||||
testEnvironment: 'jest-environment-jsdom-sixteen',
|
testEnvironment: 'jest-environment-jsdom-sixteen',
|
||||||
|
|
||||||
testMatch: [
|
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.js',
|
||||||
'<rootDir>/scripts/__tests__/**/*.test.tsx'
|
'<rootDir>/scripts/__tests__/**/*.test.tsx',
|
||||||
],
|
],
|
||||||
|
|
||||||
timers: 'fake',
|
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 isDev: boolean;
|
||||||
declare var isTest: boolean;
|
declare var isTest: boolean;
|
||||||
declare const __VERSION__: string;
|
declare const __VERSION__: string;
|
||||||
|
declare var setImmediate: Function;
|
||||||
|
declare var __INULA_DEV_HOOK__: any;
|
|
@ -18,6 +18,8 @@ import {
|
||||||
TYPE_PROFILER as Profiler,
|
TYPE_PROFILER as Profiler,
|
||||||
TYPE_STRICT_MODE as StrictMode,
|
TYPE_STRICT_MODE as StrictMode,
|
||||||
TYPE_SUSPENSE as Suspense,
|
TYPE_SUSPENSE as Suspense,
|
||||||
|
TYPE_FORWARD_REF as ForwardRef,
|
||||||
|
TYPE_MEMO as Memo,
|
||||||
} from './src/external/JSXElementType';
|
} from './src/external/JSXElementType';
|
||||||
|
|
||||||
import { Component, PureComponent } from './src/renderer/components/BaseClassComponent';
|
import { Component, PureComponent } from './src/renderer/components/BaseClassComponent';
|
||||||
|
@ -42,9 +44,6 @@ import {
|
||||||
useState,
|
useState,
|
||||||
useDebugValue,
|
useDebugValue,
|
||||||
} from './src/renderer/hooks/HookExternal';
|
} 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 {
|
import {
|
||||||
isContextProvider,
|
isContextProvider,
|
||||||
isContextConsumer,
|
isContextConsumer,
|
||||||
|
@ -55,17 +54,11 @@ import {
|
||||||
isLazy,
|
isLazy,
|
||||||
isMemo,
|
isMemo,
|
||||||
isPortal,
|
isPortal,
|
||||||
} from './src/external/HorizonIs';
|
} from './src/external/InulaIs';
|
||||||
import { createStore, useStore, clearStore } from './src/horizonx/store/StoreHandler';
|
import { createStore, useStore, clearStore } from './src/inulax/store/StoreHandler';
|
||||||
import * as reduxAdapter from './src/horizonx/adapters/redux';
|
import * as reduxAdapter from './src/inulax/adapters/redux';
|
||||||
import { watch } from './src/horizonx/proxy/watch';
|
import { watch } from './src/inulax/proxy/watch';
|
||||||
|
import { act } from './src/external/TestUtil';
|
||||||
// act用于测试,作用是:如果fun触发了刷新(包含了异步刷新),可以保证在act后面的代码是在刷新完成后才执行。
|
|
||||||
const act = fun => {
|
|
||||||
asyncUpdates(fun);
|
|
||||||
callRenderQueueImmediate();
|
|
||||||
runAsyncEffects();
|
|
||||||
};
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
render,
|
render,
|
||||||
|
@ -75,7 +68,10 @@ import {
|
||||||
unmountComponentAtNode,
|
unmountComponentAtNode,
|
||||||
} from './src/dom/DOMExternal';
|
} from './src/dom/DOMExternal';
|
||||||
|
|
||||||
const Horizon = {
|
import { syncUpdates as flushSync } from './src/renderer/TreeBuilder';
|
||||||
|
import { toRaw } from './src/inulax/proxy/ProxyHandler';
|
||||||
|
|
||||||
|
const Inula = {
|
||||||
Children,
|
Children,
|
||||||
createRef,
|
createRef,
|
||||||
Component,
|
Component,
|
||||||
|
@ -94,10 +90,6 @@ const Horizon = {
|
||||||
useReducer,
|
useReducer,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
Fragment,
|
|
||||||
Profiler,
|
|
||||||
StrictMode,
|
|
||||||
Suspense,
|
|
||||||
createElement,
|
createElement,
|
||||||
cloneElement,
|
cloneElement,
|
||||||
isValidElement,
|
isValidElement,
|
||||||
|
@ -107,6 +99,7 @@ const Horizon = {
|
||||||
findDOMNode,
|
findDOMNode,
|
||||||
unmountComponentAtNode,
|
unmountComponentAtNode,
|
||||||
act,
|
act,
|
||||||
|
flushSync,
|
||||||
createStore,
|
createStore,
|
||||||
useStore,
|
useStore,
|
||||||
clearStore,
|
clearStore,
|
||||||
|
@ -121,6 +114,12 @@ const Horizon = {
|
||||||
isPortal,
|
isPortal,
|
||||||
isContextProvider,
|
isContextProvider,
|
||||||
isContextConsumer,
|
isContextConsumer,
|
||||||
|
ForwardRef,
|
||||||
|
Memo,
|
||||||
|
Fragment,
|
||||||
|
Profiler,
|
||||||
|
StrictMode,
|
||||||
|
Suspense,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const version = __VERSION__;
|
export const version = __VERSION__;
|
||||||
|
@ -143,10 +142,6 @@ export {
|
||||||
useReducer,
|
useReducer,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
Fragment,
|
|
||||||
Profiler,
|
|
||||||
StrictMode,
|
|
||||||
Suspense,
|
|
||||||
createElement,
|
createElement,
|
||||||
cloneElement,
|
cloneElement,
|
||||||
isValidElement,
|
isValidElement,
|
||||||
|
@ -156,12 +151,14 @@ export {
|
||||||
findDOMNode,
|
findDOMNode,
|
||||||
unmountComponentAtNode,
|
unmountComponentAtNode,
|
||||||
act,
|
act,
|
||||||
// 状态管理器HorizonX接口
|
flushSync,
|
||||||
|
// 状态管理器InulaX接口
|
||||||
createStore,
|
createStore,
|
||||||
useStore,
|
useStore,
|
||||||
clearStore,
|
clearStore,
|
||||||
reduxAdapter,
|
reduxAdapter,
|
||||||
watch,
|
watch,
|
||||||
|
toRaw,
|
||||||
// 兼容ReactIs
|
// 兼容ReactIs
|
||||||
isFragment,
|
isFragment,
|
||||||
isElement,
|
isElement,
|
||||||
|
@ -172,6 +169,12 @@ export {
|
||||||
isPortal,
|
isPortal,
|
||||||
isContextProvider,
|
isContextProvider,
|
||||||
isContextConsumer,
|
isContextConsumer,
|
||||||
|
ForwardRef,
|
||||||
|
Memo,
|
||||||
|
Fragment,
|
||||||
|
Profiler,
|
||||||
|
StrictMode,
|
||||||
|
Suspense,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Horizon;
|
export default Inula;
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
* 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 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:
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
*
|
*
|
||||||
|
@ -13,11 +13,12 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* 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) {
|
export {
|
||||||
return {
|
jsxDEV,
|
||||||
vtype: TYPE_FORWARD_REF,
|
Fragment
|
||||||
render,
|
};
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
* 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 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:
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
*
|
*
|
||||||
|
@ -13,13 +13,13 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function isInputElement(dom?: HTMLElement): boolean {
|
import {
|
||||||
return dom instanceof HTMLInputElement || dom instanceof HTMLTextAreaElement;
|
TYPE_FRAGMENT as Fragment,
|
||||||
}
|
} from './src/external/JSXElementType';
|
||||||
|
import { jsx, jsx as jsxs } from './src/external/JSXElement';
|
||||||
|
|
||||||
export function setPropertyWritable(obj, propName) {
|
export {
|
||||||
const desc = Object.getOwnPropertyDescriptor(obj, propName);
|
jsx,
|
||||||
if (!desc || !desc.writable) {
|
jsxs,
|
||||||
Object.defineProperty(obj, propName, { writable: true });
|
Fragment
|
||||||
}
|
};
|
||||||
}
|
|
|
@ -16,7 +16,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
module.exports = require('./cjs/horizon.production.min.js');
|
module.exports = require('./cjs/inula.production.min.js');
|
||||||
} else {
|
} else {
|
||||||
module.exports = require('./cjs/horizon.development.js');
|
module.exports = require('./cjs/inula.development.js');
|
||||||
}
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"name": "@cloudsop/horizon",
|
"name": "inulajs",
|
||||||
"description": "Horizon is a JavaScript framework library.",
|
"description": "InulaJS is a JavaScript framework library.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"horizon"
|
"inula"
|
||||||
],
|
],
|
||||||
"version": "0.0.33",
|
"version": "0.0.52",
|
||||||
"homepage": "",
|
"homepage": "",
|
||||||
"bugs": "",
|
"bugs": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
|
@ -19,6 +19,7 @@ import type { Container } from './DOMOperator';
|
||||||
import { isElement } from './utils/Common';
|
import { isElement } from './utils/Common';
|
||||||
import { findDOMByClassInst } from '../renderer/vnode/VNodeUtils';
|
import { findDOMByClassInst } from '../renderer/vnode/VNodeUtils';
|
||||||
import { Callback } from '../renderer/UpdateHandler';
|
import { Callback } from '../renderer/UpdateHandler';
|
||||||
|
import { listenSimulatedDelegatedEvents } from '../event/EventBinding';
|
||||||
|
|
||||||
function createRoot(children: any, container: Container, callback?: Callback) {
|
function createRoot(children: any, container: Container, callback?: Callback) {
|
||||||
// 清空容器
|
// 清空容器
|
||||||
|
@ -31,6 +32,7 @@ function createRoot(children: any, container: Container, callback?: Callback) {
|
||||||
// 调度器创建根节点,并给容器dom赋vNode结构体
|
// 调度器创建根节点,并给容器dom赋vNode结构体
|
||||||
const treeRoot = createTreeRootVNode(container);
|
const treeRoot = createTreeRootVNode(container);
|
||||||
container._treeRoot = treeRoot;
|
container._treeRoot = treeRoot;
|
||||||
|
listenSimulatedDelegatedEvents(treeRoot);
|
||||||
|
|
||||||
// 执行回调
|
// 执行回调
|
||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
|
@ -71,7 +73,7 @@ function executeRender(children: any, container: Container, callback?: Callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function findDOMNode(domOrEle?: Element): null | Element | Text {
|
function findDOMNode(domOrEle?: Element): null | Element | Text {
|
||||||
if (domOrEle == null) {
|
if (domOrEle === null || domOrEle === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +103,7 @@ function removeRootEventLister(container: Container) {
|
||||||
|
|
||||||
// 卸载入口
|
// 卸载入口
|
||||||
function destroy(container: Container): boolean {
|
function destroy(container: Container): boolean {
|
||||||
if (container && container._treeRoot) {
|
if (container._treeRoot) {
|
||||||
syncUpdates(() => {
|
syncUpdates(() => {
|
||||||
executeRender(null, container, () => {
|
executeRender(null, container, () => {
|
||||||
removeRootEventLister(container);
|
removeRootEventLister(container);
|
|
@ -22,9 +22,9 @@ import type { Container, Props } from './DOMOperator';
|
||||||
|
|
||||||
import { DomComponent, DomText, TreeRoot } from '../renderer/vnode/VNodeTags';
|
import { DomComponent, DomText, TreeRoot } from '../renderer/vnode/VNodeTags';
|
||||||
|
|
||||||
const INTERNAL_VNODE = '_horizon_VNode';
|
const INTERNAL_VNODE = '_inula_VNode';
|
||||||
const INTERNAL_PROPS = '_horizon_Props';
|
const INTERNAL_PROPS = '_inula_Props';
|
||||||
const INTERNAL_NONDELEGATEEVENTS = '_horizon_NonDelegatedEvents';
|
const INTERNAL_NONDELEGATEEVENTS = '_inula_NonDelegatedEvents';
|
||||||
|
|
||||||
// 通过 VNode 实例获取 DOM 节点
|
// 通过 VNode 实例获取 DOM 节点
|
||||||
export function getDom(vNode: VNode): Element | Text | null {
|
export function getDom(vNode: VNode): Element | Text | null {
|
|
@ -16,7 +16,7 @@
|
||||||
import { saveVNode, updateVNodeProps } from './DOMInternalKeys';
|
import { saveVNode, updateVNodeProps } from './DOMInternalKeys';
|
||||||
import { createDom } from './utils/DomCreator';
|
import { createDom } from './utils/DomCreator';
|
||||||
import { getSelectionInfo, resetSelectionRange, SelectionData } from './SelectionRangeHandler';
|
import { getSelectionInfo, resetSelectionRange, SelectionData } from './SelectionRangeHandler';
|
||||||
import { shouldAutoFocus } from './utils/Common';
|
import { isDocument, shouldAutoFocus } from './utils/Common';
|
||||||
import { NSS } from './utils/DomCreator';
|
import { NSS } from './utils/DomCreator';
|
||||||
import { adjustStyleValue } from './DOMPropertiesHandler/StyleHandler';
|
import { adjustStyleValue } from './DOMPropertiesHandler/StyleHandler';
|
||||||
import type { VNode } from '../renderer/Types';
|
import type { VNode } from '../renderer/Types';
|
||||||
|
@ -26,6 +26,7 @@ import { isNativeElement, validateProps } from './validators/ValidateProps';
|
||||||
import { watchValueChange } from './valueHandler/ValueChangeHandler';
|
import { watchValueChange } from './valueHandler/ValueChangeHandler';
|
||||||
import { DomComponent, DomText } from '../renderer/vnode/VNodeTags';
|
import { DomComponent, DomText } from '../renderer/vnode/VNodeTags';
|
||||||
import { updateCommonProp } from './DOMPropertiesHandler/UpdateCommonProp';
|
import { updateCommonProp } from './DOMPropertiesHandler/UpdateCommonProp';
|
||||||
|
import {getCurrentRoot} from '../renderer/RootStack';
|
||||||
|
|
||||||
export type Props = Record<string, any> & {
|
export type Props = Record<string, any> & {
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
|
@ -45,7 +46,7 @@ function getChildNS(parentNS: string | null, tagName: string): string {
|
||||||
return NSS.html;
|
return NSS.html;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentNS == null || parentNS === NSS.html) {
|
if (parentNS === null || parentNS === NSS.html) {
|
||||||
// 没有父命名空间,或父命名空间为xhtml
|
// 没有父命名空间,或父命名空间为xhtml
|
||||||
return NSS[tagName] ?? NSS.html;
|
return NSS[tagName] ?? NSS.html;
|
||||||
}
|
}
|
||||||
|
@ -70,7 +71,12 @@ export function resetAfterSubmit(): void {
|
||||||
|
|
||||||
// 创建 DOM 对象
|
// 创建 DOM 对象
|
||||||
export function newDom(tagName: string, props: Props, parentNamespace: string, vNode: VNode): Element {
|
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 对象上
|
// 将 vNode 节点挂到 DOM 对象上
|
||||||
saveVNode(vNode, dom);
|
saveVNode(vNode, dom);
|
||||||
// 将属性挂到 DOM 对象上
|
// 将属性挂到 DOM 对象上
|
||||||
|
@ -124,7 +130,8 @@ export function isTextChild(type: string, props: Props): boolean {
|
||||||
return (
|
return (
|
||||||
props.dangerouslySetInnerHTML &&
|
props.dangerouslySetInnerHTML &&
|
||||||
typeof props.dangerouslySetInnerHTML === 'object' &&
|
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;
|
return textNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交vNode的类型为Component或者Text的更新
|
// 提交vNode的类型为DomComponent或者DomText的更新
|
||||||
export function submitDomUpdate(tag: string, vNode: VNode) {
|
export function submitDomUpdate(tag: string, vNode: VNode) {
|
||||||
const newProps = vNode.props;
|
const newProps = vNode.props;
|
||||||
const element: Element | null = vNode.realNode;
|
const element: Element | null = vNode.realNode;
|
||||||
|
|
||||||
if (tag === DomComponent) {
|
if (tag === DomComponent) {
|
||||||
// DomComponent类型
|
// DomComponent类型
|
||||||
if (element != null) {
|
if (element !== null && element !== undefined) {
|
||||||
const type = vNode.type;
|
const type = vNode.type;
|
||||||
const changeList = vNode.changeList;
|
const changeList = vNode.changeList;
|
||||||
vNode.changeList = null;
|
vNode.changeList = null;
|
||||||
|
@ -152,7 +159,14 @@ export function submitDomUpdate(tag: string, vNode: VNode) {
|
||||||
updateVNodeProps(element, newProps);
|
updateVNodeProps(element, newProps);
|
||||||
// 应用diff更新Properties.
|
// 应用diff更新Properties.
|
||||||
// 当一个选中的radio改变名称,浏览器使另一个radio的复选框为false.
|
// 当一个选中的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);
|
updateCommonProp(element, 'checked', newProps.checked, true);
|
||||||
}
|
}
|
||||||
const isNativeTag = isNativeElement(type, newProps);
|
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) {
|
if (tag === DomComponent) {
|
||||||
dom.style.display = adjustStyleValue('display', props?.style?.display ?? '');
|
dom.style.display = adjustStyleValue('display', props?.style?.display ?? '');
|
||||||
} else if (tag === DomText) {
|
} else if (tag === DomText) {
|
|
@ -13,7 +13,7 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { allDelegatedHorizonEvents } from '../../event/EventHub';
|
import { allDelegatedInulaEvents } from '../../event/EventHub';
|
||||||
import { updateCommonProp } from './UpdateCommonProp';
|
import { updateCommonProp } from './UpdateCommonProp';
|
||||||
import { setStyles } from './StyleHandler';
|
import { setStyles } from './StyleHandler';
|
||||||
import { lazyDelegateOnRoot, listenNonDelegatedEvent } from '../../event/EventBinding';
|
import { lazyDelegateOnRoot, listenNonDelegatedEvent } from '../../event/EventBinding';
|
||||||
|
@ -35,7 +35,7 @@ export function setDomProps(dom: Element, props: Object, isNativeTag: boolean, i
|
||||||
} else if (isEventProp(propName)) {
|
} else if (isEventProp(propName)) {
|
||||||
// 事件监听属性处理
|
// 事件监听属性处理
|
||||||
const currentRoot = getCurrentRoot();
|
const currentRoot = getCurrentRoot();
|
||||||
if (!allDelegatedHorizonEvents.has(propName)) {
|
if (!allDelegatedInulaEvents.has(propName)) {
|
||||||
listenNonDelegatedEvent(propName, dom, propVal);
|
listenNonDelegatedEvent(propName, dom, propVal);
|
||||||
} else if (currentRoot && !currentRoot.delegatedEvents.has(propName)) {
|
} else if (currentRoot && !currentRoot.delegatedEvents.has(propName)) {
|
||||||
lazyDelegateOnRoot(currentRoot, propName);
|
lazyDelegateOnRoot(currentRoot, propName);
|
||||||
|
@ -48,7 +48,7 @@ export function setDomProps(dom: Element, props: Object, isNativeTag: boolean, i
|
||||||
}
|
}
|
||||||
} else if (propName === 'dangerouslySetInnerHTML') {
|
} else if (propName === 'dangerouslySetInnerHTML') {
|
||||||
dom.innerHTML = propVal.__html;
|
dom.innerHTML = propVal.__html;
|
||||||
} else if (!isInit || (isInit && propVal != null)) {
|
} else if (!isInit || (propVal !== null && propVal !== undefined)) {
|
||||||
updateCommonProp(dom, propName, propVal, isNativeTag);
|
updateCommonProp(dom, propName, propVal, isNativeTag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ export function compareProps(oldProps: Object, newProps: Object): Object {
|
||||||
for (let i = 0; i < oldPropsLength; i++) {
|
for (let i = 0; i < oldPropsLength; i++) {
|
||||||
propName = keysOfOldProps[i];
|
propName = keysOfOldProps[i];
|
||||||
// 新属性中包含该属性或者该属性为空值的属性不需要处理
|
// 新属性中包含该属性或者该属性为空值的属性不需要处理
|
||||||
if (keysOfNewProps.includes(propName) || oldProps[propName] == null) {
|
if ( oldProps[propName] === null || oldProps[propName] === undefined || keysOfNewProps.includes(propName)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ export function compareProps(oldProps: Object, newProps: Object): Object {
|
||||||
} else if (propName === 'autoFocus' || propName === 'children' || propName === 'dangerouslySetInnerHTML') {
|
} else if (propName === 'autoFocus' || propName === 'children' || propName === 'dangerouslySetInnerHTML') {
|
||||||
continue;
|
continue;
|
||||||
} else if (isEventProp(propName)) {
|
} else if (isEventProp(propName)) {
|
||||||
if (!allDelegatedHorizonEvents.has(propName)) {
|
if (!allDelegatedInulaEvents.has(propName)) {
|
||||||
toUpdateProps[propName] = null;
|
toUpdateProps[propName] = null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -103,9 +103,13 @@ export function compareProps(oldProps: Object, newProps: Object): Object {
|
||||||
for (let i = 0; i < keysOfNewProps.length; i++) {
|
for (let i = 0; i < keysOfNewProps.length; i++) {
|
||||||
propName = keysOfNewProps[i];
|
propName = keysOfNewProps[i];
|
||||||
newPropValue = newProps[propName];
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -140,7 +144,7 @@ export function compareProps(oldProps: Object, newProps: Object): Object {
|
||||||
} else if (propName === 'dangerouslySetInnerHTML') {
|
} else if (propName === 'dangerouslySetInnerHTML') {
|
||||||
newHTML = newPropValue ? newPropValue.__html : undefined;
|
newHTML = newPropValue ? newPropValue.__html : undefined;
|
||||||
oldHTML = oldPropValue ? oldPropValue.__html : undefined;
|
oldHTML = oldPropValue ? oldPropValue.__html : undefined;
|
||||||
if (newHTML != null) {
|
if (newHTML !== null && newHTML !== undefined) {
|
||||||
if (oldHTML !== newHTML) {
|
if (oldHTML !== newHTML) {
|
||||||
toUpdateProps[propName] = newPropValue;
|
toUpdateProps[propName] = newPropValue;
|
||||||
}
|
}
|
||||||
|
@ -151,7 +155,7 @@ export function compareProps(oldProps: Object, newProps: Object): Object {
|
||||||
}
|
}
|
||||||
} else if (isEventProp(propName)) {
|
} else if (isEventProp(propName)) {
|
||||||
const currentRoot = getCurrentRoot();
|
const currentRoot = getCurrentRoot();
|
||||||
if (!allDelegatedHorizonEvents.has(propName)) {
|
if (!allDelegatedInulaEvents.has(propName)) {
|
||||||
toUpdateProps[propName] = newPropValue;
|
toUpdateProps[propName] = newPropValue;
|
||||||
} else if (currentRoot && !currentRoot.delegatedEvents.has(propName)) {
|
} else if (currentRoot && !currentRoot.delegatedEvents.has(propName)) {
|
||||||
lazyDelegateOnRoot(currentRoot, propName);
|
lazyDelegateOnRoot(currentRoot, propName);
|
|
@ -13,6 +13,37 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* 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) {
|
function isNeedUnitCSS(styleName: string) {
|
||||||
return !(
|
return !(
|
||||||
noUnitCSS.includes(styleName) ||
|
noUnitCSS.includes(styleName) ||
|
||||||
|
@ -36,7 +67,7 @@ export function adjustStyleValue(name, value) {
|
||||||
|
|
||||||
if (typeof value === 'number' && value !== 0 && isNeedUnitCSS(name)) {
|
if (typeof value === 'number' && value !== 0 && isNeedUnitCSS(name)) {
|
||||||
validValue = `${value}px`;
|
validValue = `${value}px`;
|
||||||
} else if (value === '' || value == null || typeof value === 'boolean') {
|
} else if (value === '' || value === null || value === undefined || typeof value === 'boolean') {
|
||||||
validValue = '';
|
validValue = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,27 +85,12 @@ export function setStyles(dom, styles) {
|
||||||
const style = dom.style;
|
const style = dom.style;
|
||||||
Object.keys(styles).forEach(name => {
|
Object.keys(styles).forEach(name => {
|
||||||
const styleVal = styles[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;
|
const { start, end } = range;
|
||||||
let realEnd = end;
|
let realEnd = end;
|
||||||
|
|
||||||
if (realEnd == null) {
|
if (realEnd === null || realEnd === undefined) {
|
||||||
realEnd = start;
|
realEnd = start;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,14 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { HorizonDom } from './Interface';
|
import { InulaDom } from './Interface';
|
||||||
import { Props } from '../DOMOperator';
|
import { Props } from '../DOMOperator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前聚焦的 input 或者 textarea 元素
|
* 获取当前聚焦的 input 或者 textarea 元素
|
||||||
* @param doc 指定 document
|
* @param doc 指定 document
|
||||||
*/
|
*/
|
||||||
export function getFocusedDom(doc?: Document): HorizonDom | null {
|
export function getFocusedDom(doc?: Document): InulaDom | null {
|
||||||
const currentDocument = doc ?? document;
|
const currentDocument = doc ?? document;
|
||||||
|
|
||||||
return currentDocument.activeElement ?? currentDocument.body;
|
return currentDocument.activeElement ?? currentDocument.body;
|
||||||
|
@ -84,3 +84,7 @@ const types = ['button', 'input', 'select', 'textarea'];
|
||||||
export function shouldAutoFocus(tagName: string, props: Props): boolean {
|
export function shouldAutoFocus(tagName: string, props: Props): boolean {
|
||||||
return types.includes(tagName) ? Boolean(props.autoFocus) : false;
|
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元素
|
// 创建DOM元素
|
||||||
export function createDom(tagName: string, parentNamespace: string): Element {
|
export function createDom(tagName: string, parentNamespace: string, doc: Document): Element {
|
||||||
let dom: Element;
|
let dom: Element;
|
||||||
const selfNamespace = NSS[tagName] || NSS.html;
|
const selfNamespace = NSS[tagName] || NSS.html;
|
||||||
const ns = parentNamespace !== NSS.html ? parentNamespace : selfNamespace;
|
const ns = parentNamespace !== NSS.html ? parentNamespace : selfNamespace;
|
||||||
|
|
||||||
if (ns !== NSS.html) {
|
if (ns !== NSS.html) {
|
||||||
dom = document.createElementNS(ns, tagName);
|
dom = doc.createElementNS(ns, tagName);
|
||||||
} else {
|
} else {
|
||||||
dom = document.createElement(tagName);
|
dom = doc.createElement(tagName);
|
||||||
}
|
}
|
||||||
return dom;
|
return dom;
|
||||||
}
|
}
|
|
@ -17,8 +17,8 @@ export interface Props {
|
||||||
[propName: string]: any;
|
[propName: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HorizonSelect extends HTMLSelectElement {
|
export interface InulaSelect extends HTMLSelectElement {
|
||||||
_multiple?: boolean;
|
_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,
|
propDetails: PropDetails | null,
|
||||||
isNativeTag: boolean
|
isNativeTag: boolean
|
||||||
): boolean {
|
): boolean {
|
||||||
if (value == null) {
|
if (value === null || value === undefined) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,13 +98,13 @@ export function validateProps(type, props) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 非内置的变迁
|
// 非内置的元素
|
||||||
if (!isNativeElement(type, props)) {
|
if (!isNativeElement(type, props)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// style属性必须是对象
|
// 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.');
|
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) {
|
export function getInputPropsWithoutValue(dom: HTMLInputElement, props: Props) {
|
||||||
// checked属于必填属性,无法置
|
// checked属于必填属性,无法置
|
||||||
let { checked } = props;
|
let { checked } = props;
|
||||||
if (checked == null) {
|
if (checked === undefined) {
|
||||||
checked = getInitValue(dom, props).initChecked;
|
checked = getInitValue(dom, props).initChecked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,12 +45,12 @@ export function getInputPropsWithoutValue(dom: HTMLInputElement, props: Props) {
|
||||||
export function updateInputValue(dom: HTMLInputElement, props: Props) {
|
export function updateInputValue(dom: HTMLInputElement, props: Props) {
|
||||||
const { value, checked } = props;
|
const { value, checked } = props;
|
||||||
|
|
||||||
if (value != null) {
|
if (value !== undefined) {
|
||||||
// 处理 dom.value 逻辑
|
// 处理 dom.value 逻辑
|
||||||
if (dom.value !== String(value)) {
|
if (dom.value !== String(value)) {
|
||||||
dom.value = String(value);
|
dom.value = String(value);
|
||||||
}
|
}
|
||||||
} else if (checked != null) {
|
} else if (checked !== undefined) {
|
||||||
updateCommonProp(dom, 'checked', checked, true);
|
updateCommonProp(dom, 'checked', checked, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ export function setInitInputValue(dom: HTMLInputElement, props: Props) {
|
||||||
const { value, defaultValue } = props;
|
const { value, defaultValue } = props;
|
||||||
const { initValue, initChecked } = getInitValue(dom, props);
|
const { initValue, initChecked } = getInitValue(dom, props);
|
||||||
|
|
||||||
if (value != null || defaultValue != null) {
|
if (value !== undefined || defaultValue !== undefined) {
|
||||||
// value 的使用优先级 value 属性 > defaultValue 属性 > 空字符串
|
// value 的使用优先级 value 属性 > defaultValue 属性 > 空字符串
|
||||||
const initValueStr = String(initValue);
|
const initValueStr = String(initValue);
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { HorizonSelect, Props } from '../utils/Interface';
|
import { InulaSelect, Props } from '../utils/Interface';
|
||||||
|
|
||||||
function updateMultipleValue(options, newValues) {
|
function updateMultipleValue(options, newValues) {
|
||||||
const newValueSet = new Set();
|
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 {
|
return {
|
||||||
...properties,
|
...properties,
|
||||||
value: undefined,
|
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 { value, defaultValue, multiple } = props;
|
||||||
|
|
||||||
const oldMultiple = dom._multiple !== undefined ? dom._multiple : dom.multiple;
|
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;
|
dom._multiple = newMultiple;
|
||||||
|
|
||||||
// 设置了 value 属性
|
// 设置了 value 属性
|
||||||
if (value != null) {
|
if (value !== null && value !== undefined) {
|
||||||
updateValue(dom.options, value, newMultiple);
|
updateValue(dom.options, value, newMultiple);
|
||||||
} else if (oldMultiple !== newMultiple) {
|
} else if (oldMultiple !== newMultiple) {
|
||||||
// 修改了 multiple 属性
|
// 修改了 multiple 属性
|
||||||
// 切换 multiple 之后,如果设置了 defaultValue 需要重新应用
|
// 切换 multiple 之后,如果设置了 defaultValue 需要重新应用
|
||||||
if (defaultValue != null) {
|
if (defaultValue !== null && defaultValue !== undefined) {
|
||||||
updateValue(dom.options, defaultValue, newMultiple);
|
updateValue(dom.options, defaultValue, newMultiple);
|
||||||
} else {
|
} else {
|
||||||
// 恢复到未选定状态
|
// 恢复到未选定状态
|
||||||
updateValue(dom.options, newMultiple ? [] : '', newMultiple);
|
updateValue(dom.options, newMultiple ? [] : '', newMultiple);
|
||||||
}
|
}
|
||||||
} else if (isInit && defaultValue != null) {
|
} else if (isInit && defaultValue !== null && defaultValue !== undefined) {
|
||||||
// 设置了 defaultValue 属性
|
// 设置了 defaultValue 属性
|
||||||
updateValue(dom.options, defaultValue, newMultiple);
|
updateValue(dom.options, defaultValue, newMultiple);
|
||||||
}
|
}
|
|
@ -19,7 +19,7 @@ import { Props } from '../utils/Interface';
|
||||||
function getInitValue(props: Props) {
|
function getInitValue(props: Props) {
|
||||||
const { value } = props;
|
const { value } = props;
|
||||||
|
|
||||||
if (value == null) {
|
if (value === undefined) {
|
||||||
const { defaultValue, children } = props;
|
const { defaultValue, children } = props;
|
||||||
let initValue = defaultValue;
|
let initValue = defaultValue;
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ function getInitValue(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultValue 属性未配置,置为空字符串
|
// defaultValue 属性未配置,置为空字符串
|
||||||
initValue = initValue != null ? initValue : '';
|
initValue = initValue ?? '';
|
||||||
return initValue;
|
return initValue;
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
|
@ -14,7 +14,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Horizon的输入框和文本框的change事件在原生的change事件上做了一层处理
|
* Inula的输入框和文本框的change事件在原生的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 { getInputPropsWithoutValue, setInitInputValue, updateInputValue } from './InputValueHandler';
|
||||||
import { getOptionPropsWithoutValue } from './OptionValueHandler';
|
import { getOptionPropsWithoutValue } from './OptionValueHandler';
|
||||||
import { getSelectPropsWithoutValue, updateSelectValue } from './SelectValueHandler';
|
import { getSelectPropsWithoutValue, updateSelectValue } from './SelectValueHandler';
|
||||||
import { getTextareaPropsWithoutValue, updateTextareaValue } from './TextareaValueHandler';
|
import { getTextareaPropsWithoutValue, updateTextareaValue } from './TextareaValueHandler';
|
||||||
|
|
||||||
// 获取元素除了被代理的值以外的属性
|
// 获取元素除了被代理的值以外的属性
|
||||||
function getPropsWithoutValue(type: string, dom: HorizonDom, props: Props) {
|
function getPropsWithoutValue(type: string, dom: InulaDom, props: Props) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'input':
|
case 'input':
|
||||||
return getInputPropsWithoutValue(<HTMLInputElement>dom, props);
|
return getInputPropsWithoutValue(<HTMLInputElement>dom, props);
|
||||||
case 'option':
|
case 'option':
|
||||||
return getOptionPropsWithoutValue(dom, props);
|
return getOptionPropsWithoutValue(dom, props);
|
||||||
case 'select':
|
case 'select':
|
||||||
return getSelectPropsWithoutValue(<HorizonSelect>dom, props);
|
return getSelectPropsWithoutValue(<InulaSelect>dom, props);
|
||||||
case 'textarea':
|
case 'textarea':
|
||||||
return getTextareaPropsWithoutValue(<HTMLTextAreaElement>dom, props);
|
return getTextareaPropsWithoutValue(<HTMLTextAreaElement>dom, props);
|
||||||
default:
|
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) {
|
switch (type) {
|
||||||
case 'input':
|
case 'input':
|
||||||
setInitInputValue(<HTMLInputElement>dom, props);
|
setInitInputValue(<HTMLInputElement>dom, props);
|
||||||
break;
|
break;
|
||||||
case 'select':
|
case 'select':
|
||||||
updateSelectValue(<HorizonSelect>dom, props, true);
|
updateSelectValue(<InulaSelect>dom, props, true);
|
||||||
break;
|
break;
|
||||||
case 'textarea':
|
case 'textarea':
|
||||||
updateTextareaValue(<HTMLTextAreaElement>dom, props, true);
|
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) {
|
switch (type) {
|
||||||
case 'input':
|
case 'input':
|
||||||
updateInputValue(<HTMLInputElement>dom, props);
|
updateInputValue(<HTMLInputElement>dom, props);
|
||||||
break;
|
break;
|
||||||
case 'select':
|
case 'select':
|
||||||
updateSelectValue(<HorizonSelect>dom, props);
|
updateSelectValue(<InulaSelect>dom, props);
|
||||||
break;
|
break;
|
||||||
case 'textarea':
|
case 'textarea':
|
||||||
updateTextareaValue(<HTMLTextAreaElement>dom, props);
|
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 { isDocument } from '../dom/utils/Common';
|
||||||
import { getNearestVNode, getNonDelegatedListenerMap } from '../dom/DOMInternalKeys';
|
import { getNearestVNode, getNonDelegatedListenerMap } from '../dom/DOMInternalKeys';
|
||||||
import { asyncUpdates, runDiscreteUpdates } from '../renderer/TreeBuilder';
|
import { asyncUpdates, runDiscreteUpdates } from '../renderer/TreeBuilder';
|
||||||
import { handleEventMain } from './HorizonEventMain';
|
import { handleEventMain } from './InulaEventMain';
|
||||||
import { decorateNativeEvent } from './EventWrapper';
|
import { decorateNativeEvent } from './EventWrapper';
|
||||||
import { VNode } from '../renderer/vnode/VNode';
|
import { VNode } from '../renderer/vnode/VNode';
|
||||||
|
|
||||||
const listeningMarker = '_horizonListening' + Math.random().toString(36).slice(4);
|
|
||||||
|
|
||||||
// 触发委托事件
|
// 触发委托事件
|
||||||
function triggerDelegatedEvent(
|
function triggerDelegatedEvent(
|
||||||
nativeEvtName: string,
|
nativeEvtName: string,
|
||||||
|
@ -56,37 +54,48 @@ function listenToNativeEvent(nativeEvtName: string, delegatedElement: Element, i
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 是否捕获事件
|
||||||
|
function isCaptureEvent(inulaEventName) {
|
||||||
|
if (inulaEventName === 'onLostPointerCapture' || inulaEventName === 'onGotPointerCapture') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return inulaEventName.slice(-7) === 'Capture';
|
||||||
|
}
|
||||||
|
|
||||||
// 事件懒委托,当用户定义事件后,再进行委托到根节点
|
// 事件懒委托,当用户定义事件后,再进行委托到根节点
|
||||||
export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) {
|
export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) {
|
||||||
currentRoot.delegatedEvents.add(eventName);
|
currentRoot.delegatedEvents.add(eventName);
|
||||||
|
|
||||||
const isCapture = isCaptureEvent(eventName);
|
const isCapture = isCaptureEvent(eventName);
|
||||||
const nativeEvents = allDelegatedHorizonEvents.get(eventName);
|
const nativeEvents = allDelegatedInulaEvents.get(eventName);
|
||||||
|
|
||||||
nativeEvents.forEach(nativeEvent => {
|
nativeEvents.forEach(nativeEvent => {
|
||||||
const nativeFullName = isCapture ? nativeEvent + 'capture' : nativeEvent;
|
const nativeFullName = isCapture ? nativeEvent + 'capture' : nativeEvent;
|
||||||
|
|
||||||
// 事件存储在DOM节点属性,避免多个VNode(root和portal)对应同一个DOM, 造成事件重复监听
|
// 事件存储在DOM节点属性,避免多个VNode(root和portal)对应同一个DOM, 造成事件重复监听
|
||||||
let events = currentRoot.realNode.$EV;
|
currentRoot.realNode.$EV = currentRoot.realNode.$EV ?? {};
|
||||||
|
const events = currentRoot.realNode.$EV;
|
||||||
if (!events) {
|
|
||||||
events = (currentRoot.realNode as any).$EV = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!events[nativeFullName]) {
|
if (!events[nativeFullName]) {
|
||||||
const listener = listenToNativeEvent(nativeEvent, currentRoot.realNode, isCapture);
|
events[nativeFullName] = listenToNativeEvent(nativeEvent, currentRoot.realNode, isCapture);
|
||||||
events[nativeFullName] = listener;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通过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;
|
let nativeName;
|
||||||
if (capture) {
|
if (capture) {
|
||||||
nativeName = horizonEventName.slice(2, -7);
|
nativeName = inulaEventName.slice(2, -7);
|
||||||
} else {
|
} else {
|
||||||
nativeName = horizonEventName.slice(2);
|
nativeName = inulaEventName.slice(2);
|
||||||
}
|
}
|
||||||
if (!nativeName) {
|
if (!nativeName) {
|
||||||
return '';
|
return '';
|
||||||
|
@ -94,18 +103,10 @@ function getNativeEvtName(horizonEventName, capture) {
|
||||||
return nativeName.toLowerCase();
|
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 => {
|
return event => {
|
||||||
const customEvent = decorateNativeEvent(horizonEventName, nativeEvtName, event);
|
const customEvent = decorateNativeEvent(inulaEventName, nativeEvtName, event);
|
||||||
asyncUpdates(() => {
|
asyncUpdates(() => {
|
||||||
listener(customEvent);
|
listener(customEvent);
|
||||||
});
|
});
|
||||||
|
@ -113,16 +114,16 @@ function getWrapperListener(horizonEventName, nativeEvtName, targetElement, list
|
||||||
}
|
}
|
||||||
|
|
||||||
// 非委托事件单独监听到各自dom节点
|
// 非委托事件单独监听到各自dom节点
|
||||||
export function listenNonDelegatedEvent(horizonEventName: string, domElement: Element, listener): void {
|
export function listenNonDelegatedEvent(inulaEventName: string, domElement: Element, listener): void {
|
||||||
const isCapture = isCaptureEvent(horizonEventName);
|
const isCapture = isCaptureEvent(inulaEventName);
|
||||||
const nativeEvtName = getNativeEvtName(horizonEventName, isCapture);
|
const nativeEvtName = getNativeEvtName(inulaEventName, isCapture);
|
||||||
|
|
||||||
// 先判断是否存在老的监听事件,若存在则移除
|
// 先判断是否存在老的监听事件,若存在则移除
|
||||||
const nonDelegatedListenerMap = getNonDelegatedListenerMap(domElement);
|
const nonDelegatedListenerMap = getNonDelegatedListenerMap(domElement);
|
||||||
const currentListener = nonDelegatedListenerMap.get(horizonEventName);
|
const currentListener = nonDelegatedListenerMap.get(inulaEventName);
|
||||||
if (currentListener) {
|
if (currentListener) {
|
||||||
domElement.removeEventListener(nativeEvtName, currentListener);
|
domElement.removeEventListener(nativeEvtName, currentListener);
|
||||||
nonDelegatedListenerMap.delete(horizonEventName);
|
nonDelegatedListenerMap.delete(inulaEventName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof listener !== 'function') {
|
if (typeof listener !== 'function') {
|
||||||
|
@ -130,8 +131,8 @@ export function listenNonDelegatedEvent(horizonEventName: string, domElement: El
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为了和委托事件对外行为一致,将事件对象封装成CustomBaseEvent
|
// 为了和委托事件对外行为一致,将事件对象封装成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);
|
domElement.addEventListener(nativeEvtName, wrapperListener, isCapture);
|
||||||
}
|
}
|
|
@ -13,13 +13,16 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 需要委托的horizon事件和原生事件对应关系
|
// 需要委托的inula事件和原生事件对应关系
|
||||||
export const allDelegatedHorizonEvents = new Map();
|
export const allDelegatedInulaEvents = new Map();
|
||||||
|
|
||||||
|
// 模拟委托事件,不冒泡事件需要利用其他事件来触发冒泡过程
|
||||||
|
export const simulatedDelegatedEvents = ['onMouseEnter', 'onMouseLeave'];
|
||||||
// 所有委托的原生事件集合
|
// 所有委托的原生事件集合
|
||||||
export const allDelegatedNativeEvents = new Set();
|
export const allDelegatedNativeEvents = new Set();
|
||||||
|
|
||||||
// Horizon事件和原生事件对应关系
|
// Inula事件和原生事件对应关系
|
||||||
export const horizonEventToNativeMap = new Map([
|
export const inulaEventToNativeMap = new Map([
|
||||||
['onKeyPress', ['keypress']],
|
['onKeyPress', ['keypress']],
|
||||||
['onTextInput', ['textInput']],
|
['onTextInput', ['textInput']],
|
||||||
['onClick', ['click']],
|
['onClick', ['click']],
|
||||||
|
@ -49,13 +52,15 @@ export const horizonEventToNativeMap = new Map([
|
||||||
['onCompositionUpdate', ['compositionupdate']],
|
['onCompositionUpdate', ['compositionupdate']],
|
||||||
['onChange', ['change', 'click', 'focusout', 'input']],
|
['onChange', ['change', 'click', 'focusout', 'input']],
|
||||||
['onSelect', ['select']],
|
['onSelect', ['select']],
|
||||||
|
['onMouseEnter', ['mouseout', 'mouseover']],
|
||||||
|
['onMouseLeave', ['mouseout', 'mouseover']],
|
||||||
|
|
||||||
['onAnimationEnd', ['animationend']],
|
['onAnimationEnd', ['animationend']],
|
||||||
['onAnimationIteration', ['animationiteration']],
|
['onAnimationIteration', ['animationiteration']],
|
||||||
['onAnimationStart', ['animationstart']],
|
['onAnimationStart', ['animationstart']],
|
||||||
['onTransitionEnd', ['transitionend']],
|
['onTransitionEnd', ['transitionend']],
|
||||||
]);
|
]);
|
||||||
export const NativeEventToHorizonMap = {
|
export const NativeEventToInulaMap = {
|
||||||
click: 'click',
|
click: 'click',
|
||||||
wheel: 'wheel',
|
wheel: 'wheel',
|
||||||
dblclick: 'doubleClick',
|
dblclick: 'doubleClick',
|
||||||
|
@ -94,17 +99,17 @@ export const EVENT_TYPE_BUBBLE = 'Bubble';
|
||||||
export const EVENT_TYPE_CAPTURE = 'Capture';
|
export const EVENT_TYPE_CAPTURE = 'Capture';
|
||||||
export const EVENT_TYPE_ALL = 'All';
|
export const EVENT_TYPE_ALL = 'All';
|
||||||
|
|
||||||
horizonEventToNativeMap.forEach((dependencies, horizonEvent) => {
|
inulaEventToNativeMap.forEach((dependencies, inulaEvent) => {
|
||||||
allDelegatedHorizonEvents.set(horizonEvent, dependencies);
|
allDelegatedInulaEvents.set(inulaEvent, dependencies);
|
||||||
allDelegatedHorizonEvents.set(horizonEvent + 'Capture', dependencies);
|
allDelegatedInulaEvents.set(inulaEvent + 'Capture', dependencies);
|
||||||
|
|
||||||
dependencies.forEach(d => {
|
dependencies.forEach(d => {
|
||||||
allDelegatedNativeEvents.add(d);
|
allDelegatedNativeEvents.add(d);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export function transformToHorizonEvent(nativeEvtName: string) {
|
export function transformToInulaEvent(nativeEvtName: string) {
|
||||||
const name = NativeEventToHorizonMap[nativeEvtName];
|
const name = NativeEventToInulaMap[nativeEvtName];
|
||||||
// 例:dragEnd -> onDragEnd
|
// 例:dragEnd -> onDragEnd
|
||||||
return !name ? '' : `on${name[0].toUpperCase()}${name.slice(1)}`;
|
return !name ? '' : `on${name[0].toUpperCase()}${name.slice(1)}`;
|
||||||
}
|
}
|
|
@ -37,6 +37,9 @@ export class WrappedEvent {
|
||||||
key: string;
|
key: string;
|
||||||
currentTarget: EventTarget | null = null;
|
currentTarget: EventTarget | null = null;
|
||||||
|
|
||||||
|
target: HTMLElement;
|
||||||
|
relatedTarget: HTMLElement;
|
||||||
|
|
||||||
stopPropagation: () => void;
|
stopPropagation: () => void;
|
||||||
preventDefault: () => void;
|
preventDefault: () => void;
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getVNodeProps } from '../dom/DOMInternalKeys';
|
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 { Props } from '../dom/utils/Interface';
|
||||||
import { updateTextareaValue } from '../dom/valueHandler/TextareaValueHandler';
|
import { updateTextareaValue } from '../dom/valueHandler/TextareaValueHandler';
|
||||||
import { updateInputHandlerIfChanged } from '../dom/valueHandler/ValueChangeHandler';
|
import { updateInputHandlerIfChanged } from '../dom/valueHandler/ValueChangeHandler';
|
||||||
|
@ -37,15 +37,26 @@ export function shouldControlValue(): boolean {
|
||||||
return changeEventTargets !== null && changeEventTargets.length > 0;
|
return changeEventTargets !== null && changeEventTargets.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从缓存队列中对受控组件进行赋值
|
function controlInputValue(inputDom: HTMLInputElement, props: Props) {
|
||||||
export function tryControlValue() {
|
const { name, type } = props;
|
||||||
if (!changeEventTargets) {
|
|
||||||
return;
|
// 如果是 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;
|
export function tryControlValue() {
|
||||||
|
if (!changeEventTargets) {
|
||||||
// 如果是 radio,找出同一form内,name相同的Radio,更新它们Handler的Value
|
return;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
changeEventTargets.forEach(target => {
|
||||||
|
controlValue(target);
|
||||||
|
});
|
||||||
|
changeEventTargets = null;
|
||||||
}
|
}
|
|
@ -24,13 +24,14 @@ import {
|
||||||
EVENT_TYPE_ALL,
|
EVENT_TYPE_ALL,
|
||||||
EVENT_TYPE_BUBBLE,
|
EVENT_TYPE_BUBBLE,
|
||||||
EVENT_TYPE_CAPTURE,
|
EVENT_TYPE_CAPTURE,
|
||||||
horizonEventToNativeMap,
|
inulaEventToNativeMap,
|
||||||
transformToHorizonEvent,
|
transformToInulaEvent,
|
||||||
} from './EventHub';
|
} from './EventHub';
|
||||||
import { getDomTag } from '../dom/utils/Common';
|
import { getDomTag } from '../dom/utils/Common';
|
||||||
import { updateInputHandlerIfChanged } from '../dom/valueHandler/ValueChangeHandler';
|
import { updateInputHandlerIfChanged } from '../dom/valueHandler/ValueChangeHandler';
|
||||||
import { getDom } from '../dom/DOMInternalKeys';
|
import { getDom } from '../dom/DOMInternalKeys';
|
||||||
import { recordChangeEventTargets, shouldControlValue, tryControlValue } from './FormValueController';
|
import { recordChangeEventTargets, shouldControlValue, tryControlValue } from './FormValueController';
|
||||||
|
import { getMouseEnterListeners } from './MouseEvent';
|
||||||
|
|
||||||
// web规范,鼠标右键key值
|
// web规范,鼠标右键key值
|
||||||
const RIGHT_MOUSE_BUTTON = 2;
|
const RIGHT_MOUSE_BUTTON = 2;
|
||||||
|
@ -93,9 +94,9 @@ function getCommonListeners(
|
||||||
target: null | EventTarget,
|
target: null | EventTarget,
|
||||||
isCapture: boolean
|
isCapture: boolean
|
||||||
): ListenerUnitList {
|
): ListenerUnitList {
|
||||||
const horizonEvtName = transformToHorizonEvent(nativeEvtName);
|
const inulaEvtName = transformToInulaEvent(nativeEvtName);
|
||||||
|
|
||||||
if (!horizonEvtName) {
|
if (!inulaEvtName) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,8 +113,8 @@ function getCommonListeners(
|
||||||
nativeEvtName = 'blur';
|
nativeEvtName = 'blur';
|
||||||
}
|
}
|
||||||
|
|
||||||
const horizonEvent = decorateNativeEvent(horizonEvtName, nativeEvtName, nativeEvent);
|
const inulaEvent = decorateNativeEvent(inulaEvtName, nativeEvtName, nativeEvent);
|
||||||
return getListenersFromTree(vNode, horizonEvtName, horizonEvent, isCapture ? EVENT_TYPE_CAPTURE : EVENT_TYPE_BUBBLE);
|
return getListenersFromTree(vNode, inulaEvtName, inulaEvent, isCapture ? EVENT_TYPE_CAPTURE : EVENT_TYPE_BUBBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按顺序执行事件队列
|
// 按顺序执行事件队列
|
||||||
|
@ -131,8 +132,8 @@ function processListeners(listenerList: ListenerUnitList): void {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 触发可以被执行的horizon事件监听
|
// 触发可以被执行的inula事件监听
|
||||||
function triggerHorizonEvents(
|
function triggerInulaEvents(
|
||||||
nativeEvtName: string,
|
nativeEvtName: string,
|
||||||
isCapture: boolean,
|
isCapture: boolean,
|
||||||
nativeEvent: AnyNativeEvent,
|
nativeEvent: AnyNativeEvent,
|
||||||
|
@ -141,18 +142,26 @@ function triggerHorizonEvents(
|
||||||
const target = nativeEvent.target || nativeEvent.srcElement!;
|
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委托事件
|
// 触发特殊handler委托事件
|
||||||
if (!isCapture && horizonEventToNativeMap.get('onChange')!.includes(nativeEvtName)) {
|
if (!isCapture && inulaEventToNativeMap.get('onChange')!.includes(nativeEvtName)) {
|
||||||
const changeListeners = getChangeListeners(nativeEvtName, nativeEvent, vNode, target);
|
changeEvents = getChangeListeners(nativeEvtName, nativeEvent, vNode, target);
|
||||||
if (changeListeners.length) {
|
|
||||||
listenerList = listenerList.concat(changeListeners);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理触发的事件队列
|
// 处理触发的事件队列
|
||||||
processListeners(listenerList);
|
processListeners([...listenerList, ...mouseEnterListeners, ...changeEvents]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 其他事件正在执行中标记
|
// 其他事件正在执行中标记
|
||||||
|
@ -176,14 +185,14 @@ export function handleEventMain(
|
||||||
|
|
||||||
// 有事件正在执行,同步执行事件
|
// 有事件正在执行,同步执行事件
|
||||||
if (isInEventsExecution) {
|
if (isInEventsExecution) {
|
||||||
triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode);
|
triggerInulaEvents(nativeEvtName, isCapture, nativeEvent, startVNode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 没有事件在执行,经过调度再执行事件
|
// 没有事件在执行,经过调度再执行事件
|
||||||
isInEventsExecution = true;
|
isInEventsExecution = true;
|
||||||
try {
|
try {
|
||||||
asyncUpdates(() => triggerHorizonEvents(nativeEvtName, isCapture, nativeEvent, startVNode));
|
asyncUpdates(() => triggerInulaEvents(nativeEvtName, isCapture, nativeEvent, startVNode));
|
||||||
} finally {
|
} finally {
|
||||||
isInEventsExecution = false;
|
isInEventsExecution = false;
|
||||||
if (shouldControlValue()) {
|
if (shouldControlValue()) {
|
|
@ -40,11 +40,11 @@ function getListenerFromVNode(vNode: VNode, eventName: string): Function | null
|
||||||
// 获取监听事件
|
// 获取监听事件
|
||||||
export function getListenersFromTree(
|
export function getListenersFromTree(
|
||||||
targetVNode: VNode | null,
|
targetVNode: VNode | null,
|
||||||
horizonEvtName: string | null,
|
inulaEvtName: string | null,
|
||||||
nativeEvent: WrappedEvent,
|
nativeEvent: WrappedEvent,
|
||||||
eventType: string
|
eventType: string
|
||||||
): ListenerUnitList {
|
): ListenerUnitList {
|
||||||
if (!horizonEvtName) {
|
if (!inulaEvtName) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ export function getListenersFromTree(
|
||||||
const { realNode, tag } = vNode;
|
const { realNode, tag } = vNode;
|
||||||
if (tag === DomComponent && realNode !== null) {
|
if (tag === DomComponent && realNode !== null) {
|
||||||
if (eventType === EVENT_TYPE_ALL || eventType === EVENT_TYPE_CAPTURE) {
|
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);
|
const captureListener = getListenerFromVNode(vNode, captureName);
|
||||||
if (captureListener) {
|
if (captureListener) {
|
||||||
listeners.unshift({
|
listeners.unshift({
|
||||||
|
@ -70,7 +70,7 @@ export function getListenersFromTree(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventType === EVENT_TYPE_ALL || eventType === EVENT_TYPE_BUBBLE) {
|
if (eventType === EVENT_TYPE_ALL || eventType === EVENT_TYPE_BUBBLE) {
|
||||||
const bubbleListener = getListenerFromVNode(vNode, horizonEvtName);
|
const bubbleListener = getListenerFromVNode(vNode, inulaEvtName);
|
||||||
if (bubbleListener) {
|
if (bubbleListener) {
|
||||||
listeners.push({
|
listeners.push({
|
||||||
vNode,
|
vNode,
|
||||||
|
@ -86,3 +86,91 @@ export function getListenersFromTree(
|
||||||
|
|
||||||
return listeners;
|
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 type AnyNativeEvent = KeyboardEvent | MouseEvent | TouchEvent | UIEvent | Event;
|
||||||
|
|
||||||
export interface HorizonEventListener {
|
export interface InulaEventListener {
|
||||||
(event: WrappedEvent): void;
|
(event: WrappedEvent): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ListenerUnit = {
|
export type ListenerUnit = {
|
||||||
vNode: null | VNode;
|
vNode: null | VNode;
|
||||||
listener: HorizonEventListener;
|
listener: InulaEventListener;
|
||||||
currentTarget: EventTarget;
|
currentTarget: EventTarget;
|
||||||
event: WrappedEvent;
|
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 { TYPE_COMMON_ELEMENT, TYPE_PORTAL } from './JSXElementType';
|
||||||
|
|
||||||
import { isValidElement, JSXElement } from './JSXElement';
|
import { isValidElement, JSXElement } from './JSXElement';
|
||||||
|
import { BELONG_CLASS_VNODE_KEY } from '../renderer/vnode/VNode';
|
||||||
|
|
||||||
// 生成key
|
// 生成key
|
||||||
function getItemKey(item: any, index: number): string {
|
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;
|
return '.$' + item.key;
|
||||||
}
|
}
|
||||||
// 使用36进制减少生成字符串的长度以节省空间
|
// 使用36进制减少生成字符串的长度以节省空间
|
||||||
return '.' + index.toString(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 {
|
function mapChildrenToArray(children: any, arr: Array<any>, prefix: string, callback?: Function): number | void {
|
||||||
const type = typeof children;
|
const type = typeof children;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -53,45 +88,11 @@ function mapChildrenToArray(children: any, arr: Array<any>, prefix: string, call
|
||||||
processArrayChildren(children, arr, prefix, callback);
|
processArrayChildren(children, arr, prefix, callback);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new Error('Object is invalid as a Horizon child. ');
|
throw new Error('Object is invalid as a Inula child. ');
|
||||||
// No Default
|
// 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
|
// 在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg
|
||||||
function mapChildren(children: any, func: Function, context?: any): Array<any> {
|
function mapChildren(children: any, func: Function, context?: any): Array<any> {
|
||||||
if (children === null || children === undefined) {
|
if (children === null || children === undefined) {
|
||||||
|
@ -118,7 +119,7 @@ const Children = {
|
||||||
return n;
|
return n;
|
||||||
},
|
},
|
||||||
only: children => {
|
only: children => {
|
||||||
throwIfTrue(!isValidElement(children), 'Horizon.Children.only function received invalid element.');
|
throwIfTrue(!isValidElement(children), 'Inula.Children.only function received invalid element.');
|
||||||
return children;
|
return children;
|
||||||
},
|
},
|
||||||
toArray: children => {
|
toArray: children => {
|
|
@ -16,6 +16,7 @@
|
||||||
import { TYPE_COMMON_ELEMENT } from './JSXElementType';
|
import { TYPE_COMMON_ELEMENT } from './JSXElementType';
|
||||||
import { getProcessingClassVNode } from '../renderer/GlobalVar';
|
import { getProcessingClassVNode } from '../renderer/GlobalVar';
|
||||||
import { Source } from '../renderer/Types';
|
import { Source } from '../renderer/Types';
|
||||||
|
import { BELONG_CLASS_VNODE_KEY } from '../renderer/vnode/VNode';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* vtype 节点的类型,这里固定是element
|
* vtype 节点的类型,这里固定是element
|
||||||
|
@ -25,10 +26,10 @@ import { Source } from '../renderer/Types';
|
||||||
* props 其他常规属性
|
* props 其他常规属性
|
||||||
*/
|
*/
|
||||||
export function JSXElement(type, key, ref, vNode, props, source: Source | null) {
|
export function JSXElement(type, key, ref, vNode, props, source: Source | null) {
|
||||||
return {
|
const ele = {
|
||||||
// 元素标识符
|
// 元素标识符
|
||||||
vtype: TYPE_COMMON_ELEMENT,
|
vtype: TYPE_COMMON_ELEMENT,
|
||||||
src: isDev ? source : null,
|
src: null,
|
||||||
|
|
||||||
// 属于元素的内置属性
|
// 属于元素的内置属性
|
||||||
type: type,
|
type: type,
|
||||||
|
@ -36,14 +37,28 @@ export function JSXElement(type, key, ref, vNode, props, source: Source | null)
|
||||||
ref: ref,
|
ref: ref,
|
||||||
props: props,
|
props: props,
|
||||||
|
|
||||||
// 所属的class组件
|
// 所属的class组件,clonedeep jsxElement时需要防止无限循环
|
||||||
belongClassVNode: vNode,
|
[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) {
|
return ele;
|
||||||
const keyArray = ['key', 'ref', '__source', '__self'];
|
|
||||||
return !keyArray.includes(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeDefault(sourceObj, defaultObj) {
|
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) {
|
function buildElement(isClone, type, setting, children) {
|
||||||
// setting中的值优先级最高,clone情况下从 type 中取值,创建情况下直接赋值为 null
|
// setting中的值优先级最高,clone情况下从 type 中取值,创建情况下直接赋值为 null
|
||||||
const key = setting && setting.key !== undefined ? String(setting.key) : isClone ? type.key : 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 ref = (setting && setting.ref !== undefined) ? setting.ref : (isClone ? type.ref : null);
|
||||||
const props = isClone ? { ...type.props } : {};
|
const props = isClone ? { ...type.props } : {};
|
||||||
let vNode = isClone ? type.belongClassVNode : getProcessingClassVNode();
|
let vNode = isClone ? type[BELONG_CLASS_VNODE_KEY] : getProcessingClassVNode();
|
||||||
|
|
||||||
if (setting !== null && setting !== undefined) {
|
if (setting !== null && setting !== undefined) {
|
||||||
const keys = Object.keys(setting);
|
|
||||||
const keyLength = keys.length;
|
for (const k in setting) {
|
||||||
for (let i = 0; i < keyLength; i++) {
|
if (!keyArray.includes(k)) {
|
||||||
const k = keys[i];
|
|
||||||
if (isValidKey(k)) {
|
|
||||||
props[k] = setting[k];
|
props[k] = setting[k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,7 +106,6 @@ function buildElement(isClone, type, setting, children) {
|
||||||
lineNumber: setting.__source.lineNumber,
|
lineNumber: setting.__source.lineNumber,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return JSXElement(element, key, ref, vNode, props, src);
|
return JSXElement(element, key, ref, vNode, props, src);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,3 +122,12 @@ export function cloneElement(element, setting, ...children) {
|
||||||
export function isValidElement(element) {
|
export function isValidElement(element) {
|
||||||
return !!(element && element.vtype === TYPE_COMMON_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() {
|
export function injectUpdater() {
|
||||||
const hook = window.__HORIZON_DEV_HOOK__;
|
const hook = window.__INULA_DEV_HOOK__;
|
||||||
if (hook) {
|
if (hook) {
|
||||||
hook.init(helper);
|
hook.init(helper);
|
||||||
}
|
}
|
|
@ -84,7 +84,6 @@ export function isSame(x, y) {
|
||||||
export function getDetailedType(val: any) {
|
export function getDetailedType(val: any) {
|
||||||
if (val === undefined) return 'undefined';
|
if (val === undefined) return 'undefined';
|
||||||
if (val === null) return 'null';
|
if (val === null) return 'null';
|
||||||
if (isCollection(val)) return 'collection';
|
|
||||||
if (isPromise(val)) return 'promise';
|
if (isPromise(val)) return 'promise';
|
||||||
if (isArray(val)) return 'array';
|
if (isArray(val)) return 'array';
|
||||||
if (isWeakMap(val)) return 'weakMap';
|
if (isWeakMap(val)) return 'weakMap';
|
||||||
|
@ -116,12 +115,30 @@ export function resolveMutation(from, to) {
|
||||||
if (res[i].mutation) found = true;
|
if (res[i].mutation) found = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: resolve shifts
|
|
||||||
|
// need to resolve shifts
|
||||||
return { mutation: found, items: res, from, to };
|
return { mutation: found, items: res, from, to };
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'object': {
|
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 = {};
|
const res = {};
|
||||||
let found = false;
|
let found = false;
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
|
@ -142,8 +159,6 @@ export function resolveMutation(from, to) {
|
||||||
return { mutation: found, attributes: res, from, to };
|
return { mutation: found, attributes: res, from, to };
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement collections
|
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
if (from === to) return { mutation: false };
|
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.
|
* 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;
|
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 {
|
export function createStore(reducer: Reducer, preloadedState?: any, enhancers?): ReduxStoreHandler {
|
||||||
const store = createStoreX({
|
const store = createStoreX({
|
||||||
id: 'defaultStore',
|
id: 'defaultStore',
|
||||||
|
@ -92,13 +126,13 @@ export function createStore(reducer: Reducer, preloadedState?: any, enhancers?):
|
||||||
replaceReducer: newReducer => {
|
replaceReducer: newReducer => {
|
||||||
reducer = newReducer;
|
reducer = newReducer;
|
||||||
},
|
},
|
||||||
_horizonXstore: store,
|
_inulaXstore: store,
|
||||||
dispatch: store.$a.dispatch,
|
dispatch: store.$a.dispatch,
|
||||||
};
|
};
|
||||||
|
|
||||||
enhancers && enhancers(result);
|
enhancers && enhancers(result);
|
||||||
|
|
||||||
result.dispatch({ type: 'HorizonX' });
|
result.dispatch({ type: 'InulaX' });
|
||||||
|
|
||||||
store.reduxHandler = result;
|
store.reduxHandler = result;
|
||||||
|
|
||||||
|
@ -106,7 +140,8 @@ export function createStore(reducer: Reducer, preloadedState?: any, enhancers?):
|
||||||
}
|
}
|
||||||
|
|
||||||
export function combineReducers(reducers: { [key: string]: Reducer }): Reducer {
|
export function combineReducers(reducers: { [key: string]: Reducer }): Reducer {
|
||||||
return (state = {}, action) => {
|
return (state, action) => {
|
||||||
|
state = state || {};
|
||||||
const newState = {};
|
const newState = {};
|
||||||
Object.entries(reducers).forEach(([key, reducer]) => {
|
Object.entries(reducers).forEach(([key, reducer]) => {
|
||||||
newState[key] = reducer(state[key], action);
|
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 {
|
function applyMiddlewares(store: ReduxStoreHandler, middlewares: ReduxMiddleware[]): void {
|
||||||
middlewares = middlewares.slice();
|
middlewares = middlewares.slice();
|
||||||
middlewares.reverse();
|
middlewares.reverse();
|
||||||
|
@ -131,6 +160,12 @@ function applyMiddlewares(store: ReduxStoreHandler, middlewares: ReduxMiddleware
|
||||||
store.dispatch = dispatch;
|
store.dispatch = dispatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyMiddleware(...middlewares: ReduxMiddleware[]): (store: ReduxStoreHandler) => void {
|
||||||
|
return store => {
|
||||||
|
return applyMiddlewares(store, middlewares);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
type ActionCreator = (...params: any[]) => ReduxAction;
|
type ActionCreator = (...params: any[]) => ReduxAction;
|
||||||
type ActionCreators = { [key: string]: ActionCreator };
|
type ActionCreators = { [key: string]: ActionCreator };
|
||||||
export type BoundActionCreator = (...params: any[]) => void;
|
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) {
|
export function batch(fn: () => void) {
|
||||||
fn();
|
fn();
|
||||||
}
|
}
|
|
@ -30,7 +30,7 @@ export function Provider({
|
||||||
context: Context;
|
context: Context;
|
||||||
children?: any[];
|
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);
|
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);
|
this.listeners = this.listeners.filter(item => item != listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getListeners() {
|
||||||
|
return this.listeners;
|
||||||
|
}
|
||||||
|
|
||||||
setProp(key: string | symbol, mutation: any): void {
|
setProp(key: string | symbol, mutation: any): void {
|
||||||
this.triggerChangeListeners(mutation);
|
this.triggerChangeListeners(mutation);
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerChangeListeners(mutation: any): void {
|
triggerChangeListeners(mutation: any): void {
|
||||||
this.listeners.forEach(listener => {
|
this.listeners.forEach(listener => {
|
||||||
if (!listener) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
listener(mutation);
|
listener(mutation);
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -81,7 +81,7 @@ export class Observer implements IObserver {
|
||||||
const vNodes = this.keyVNodes.get(key);
|
const vNodes = this.keyVNodes.get(key);
|
||||||
//NOTE: using Set directly can lead to deadlock
|
//NOTE: using Set directly can lead to deadlock
|
||||||
const vNodeArray = Array.from(vNodes || []);
|
const vNodeArray = Array.from(vNodes || []);
|
||||||
vNodeArray?.forEach((vNode: VNode) => {
|
vNodeArray.forEach((vNode: VNode) => {
|
||||||
if (vNode.isStoreChange) {
|
if (vNode.isStoreChange) {
|
||||||
// VNode已经被触发过,不再重复触发
|
// VNode已经被触发过,不再重复触发
|
||||||
return;
|
return;
|
||||||
|
@ -97,10 +97,6 @@ export class Observer implements IObserver {
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerUpdate(vNode: VNode): void {
|
triggerUpdate(vNode: VNode): void {
|
||||||
if (!vNode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 触发VNode更新
|
// 触发VNode更新
|
||||||
launchUpdateFromVNode(vNode);
|
launchUpdateFromVNode(vNode);
|
||||||
}
|
}
|
|
@ -20,14 +20,30 @@ import { isArray, isCollection, isObject } from '../CommonUtils';
|
||||||
import { createArrayProxy } from './handlers/ArrayProxyHandler';
|
import { createArrayProxy } from './handlers/ArrayProxyHandler';
|
||||||
import { createCollectionProxy } from './handlers/CollectionProxyHandler';
|
import { createCollectionProxy } from './handlers/CollectionProxyHandler';
|
||||||
import type { IObserver } from '../types';
|
import type { IObserver } from '../types';
|
||||||
import { OBSERVER_KEY } from '../Constants';
|
import { OBSERVER_KEY, RAW_VALUE } from '../Constants';
|
||||||
|
|
||||||
// 保存rawObj -> Proxy
|
// 保存rawObj -> Proxy
|
||||||
const proxyMap = new WeakMap();
|
const proxyMap = new WeakMap();
|
||||||
|
|
||||||
export const hookObserverMap = 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))) {
|
if (!(rawObj && isObject(rawObj))) {
|
||||||
return rawObj;
|
return rawObj;
|
||||||
|
@ -48,7 +64,7 @@ export function createProxy(rawObj: any, id, isHookObserver = true): any {
|
||||||
let observer: IObserver = getObserver(rawObj);
|
let observer: IObserver = getObserver(rawObj);
|
||||||
if (!observer) {
|
if (!observer) {
|
||||||
observer = isHookObserver ? new Observer() : new HooklessObserver();
|
observer = isHookObserver ? new Observer() : new HooklessObserver();
|
||||||
rawObj[OBSERVER_KEY] = observer;
|
setObserverKey(rawObj, observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
hookObserverMap.set(rawObj, isHookObserver);
|
hookObserverMap.set(rawObj, isHookObserver);
|
||||||
|
@ -56,16 +72,35 @@ export function createProxy(rawObj: any, id, isHookObserver = true): any {
|
||||||
// 创建Proxy
|
// 创建Proxy
|
||||||
let proxyObj;
|
let proxyObj;
|
||||||
if (!isHookObserver) {
|
if (!isHookObserver) {
|
||||||
proxyObj = createObjectProxy(rawObj, true);
|
proxyObj = createObjectProxy(rawObj, {
|
||||||
|
current: change => {
|
||||||
|
listener.current(change);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true);
|
||||||
} else if (isArray(rawObj)) {
|
} else if (isArray(rawObj)) {
|
||||||
// 数组
|
// 数组
|
||||||
proxyObj = createArrayProxy(rawObj as []);
|
proxyObj = createArrayProxy(rawObj as [], {
|
||||||
|
current: change => {
|
||||||
|
listener.current(change);
|
||||||
|
},
|
||||||
|
});
|
||||||
} else if (isCollection(rawObj)) {
|
} else if (isCollection(rawObj)) {
|
||||||
// 集合
|
// 集合
|
||||||
proxyObj = createCollectionProxy(rawObj);
|
proxyObj = createCollectionProxy(rawObj, {
|
||||||
|
current: change => {
|
||||||
|
listener.current(change);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true);
|
||||||
} else {
|
} else {
|
||||||
// 原生对象 或 函数
|
// 原生对象 或 函数
|
||||||
proxyObj = createObjectProxy(rawObj);
|
proxyObj = createObjectProxy(rawObj, {
|
||||||
|
current: change => {
|
||||||
|
listener.current(change);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyMap.set(rawObj, proxyObj);
|
proxyMap.set(rawObj, proxyObj);
|
||||||
|
@ -74,6 +109,6 @@ export function createProxy(rawObj: any, id, isHookObserver = true): any {
|
||||||
return proxyObj;
|
return proxyObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getObserver(rawObj: any): Observer {
|
export function toRaw<T>(observed: T): T {
|
||||||
return rawObj[OBSERVER_KEY];
|
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,
|
ACTION_QUEUED,
|
||||||
INITIALIZED,
|
INITIALIZED,
|
||||||
QUEUE_FINISHED,
|
QUEUE_FINISHED,
|
||||||
|
QUEUE_PENDING,
|
||||||
STATE_CHANGE,
|
STATE_CHANGE,
|
||||||
SUBSCRIBED,
|
SUBSCRIBED,
|
||||||
UNSUBSCRIBED,
|
UNSUBSCRIBED,
|
||||||
|
@ -52,6 +53,100 @@ const idGenerator = {
|
||||||
|
|
||||||
const storeMap = new Map<string, StoreObj<any, any, any>>();
|
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>>(
|
export function createStore<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>>(
|
||||||
config: StoreConfig<S, A, C>
|
config: StoreConfig<S, A, C>
|
||||||
): () => StoreObj<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 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;
|
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>,
|
$c: $c as ComputedValues<S, C>,
|
||||||
$queue: $queue as QueuedStoreActions<S, A>,
|
$queue: $queue as QueuedStoreActions<S, A>,
|
||||||
$config: config,
|
$config: config,
|
||||||
|
$listeners: [
|
||||||
|
change => {
|
||||||
|
devtools.emit(STATE_CHANGE, {
|
||||||
|
store: storeObj,
|
||||||
|
change,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
],
|
||||||
$subscribe: listener => {
|
$subscribe: listener => {
|
||||||
devtools.emit(SUBSCRIBED, { store: storeObj, listener });
|
devtools.emit(SUBSCRIBED, { store: storeObj, listener });
|
||||||
proxyObj.addListener(listener);
|
storeObj.$listeners.push(listener);
|
||||||
},
|
},
|
||||||
$unsubscribe: listener => {
|
$unsubscribe: listener => {
|
||||||
devtools.emit(UNSUBSCRIBED, storeObj);
|
devtools.emit(UNSUBSCRIBED, { store: storeObj });
|
||||||
proxyObj.removeListener(listener);
|
storeObj.$listeners = storeObj.$listeners.filter(item => item != listener);
|
||||||
},
|
},
|
||||||
} as unknown as StoreObj<S, A, C>;
|
} as unknown as StoreObj<S, A, C>;
|
||||||
|
|
||||||
|
listener.current = (...args) => {
|
||||||
|
storeObj.$listeners.forEach(listener => listener(...args));
|
||||||
|
};
|
||||||
|
|
||||||
const plannedActions: PlannedAction<S, ActionFunction<S>>[] = [];
|
const plannedActions: PlannedAction<S, ActionFunction<S>>[] = [];
|
||||||
|
|
||||||
// 包装actions
|
// 包装actions
|
||||||
|
@ -104,7 +215,11 @@ export function createStore<S extends object, A extends UserActions<S>, C extend
|
||||||
});
|
});
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
if (!proxyObj.$pending) {
|
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);
|
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,
|
store: storeObj,
|
||||||
});
|
});
|
||||||
|
|
||||||
proxyObj.addListener(change => {
|
|
||||||
devtools.emit(STATE_CHANGE, {
|
|
||||||
store: storeObj,
|
|
||||||
change,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return createGetStore(storeObj);
|
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
|
// 函数组件中使用的hook
|
||||||
export function useStore<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>>(
|
export function useStore<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>>(
|
||||||
id: string
|
id: string
|
|
@ -61,6 +61,7 @@ export type StoreObj<S extends object, A extends UserActions<S>, C extends UserC
|
||||||
$a: StoreActions<S, A>;
|
$a: StoreActions<S, A>;
|
||||||
$c: UserComputedValues<S>;
|
$c: UserComputedValues<S>;
|
||||||
$queue: QueuedStoreActions<S, A>;
|
$queue: QueuedStoreActions<S, A>;
|
||||||
|
$listeners;
|
||||||
$subscribe: (listener: (mutation) => void) => void;
|
$subscribe: (listener: (mutation) => void) => void;
|
||||||
$unsubscribe: (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]> };
|
} & { [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;
|
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';
|
return error !== null && typeof error === 'object' && typeof error.then === 'function';
|
||||||
}
|
}
|
||||||
// 处理capture和bubble阶段抛出的错误
|
// 处理capture和bubble阶段抛出的错误
|
|
@ -14,15 +14,21 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { VNode } from './vnode/VNode';
|
import { VNode } from './vnode/VNode';
|
||||||
const currentRootStack: VNode[] = [];
|
|
||||||
|
const currentRootStack: (VNode | undefined)[] = [];
|
||||||
|
let index = -1;
|
||||||
export function getCurrentRoot() {
|
export function getCurrentRoot() {
|
||||||
return currentRootStack[currentRootStack.length - 1];
|
return currentRootStack[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pushCurrentRoot(root: VNode) {
|
export function pushCurrentRoot(root: VNode) {
|
||||||
return currentRootStack.push(root);
|
index++;
|
||||||
|
currentRootStack[index] = root;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function popCurrentRoot() {
|
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;
|
node = parent;
|
||||||
// 更新processing,抛出异常时可以使用
|
// 更新processing,抛出异常时可以使用
|
||||||
processing = node;
|
processing = node;
|
||||||
} while (node !== null);
|
} while (node);
|
||||||
|
|
||||||
// 修改结果
|
// 修改结果
|
||||||
if (getBuildResult() === BuildInComplete) {
|
if (getBuildResult() === BuildInComplete) {
|
||||||
|
@ -179,6 +179,11 @@ function isEqualByIndex(idx: number, pathArrays: string[][]) {
|
||||||
function getChildByIndex(vNode: VNode, idx: number) {
|
function getChildByIndex(vNode: VNode, idx: number) {
|
||||||
let node = vNode.child;
|
let node = vNode.child;
|
||||||
for (let i = 0; i < idx; i++) {
|
for (let i = 0; i < idx; i++) {
|
||||||
|
// 场景:当组件被销毁,业务若异步(定时器)调用setState修改状态,可能出现路径错误,此处进行保护。
|
||||||
|
if (node === null || node === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
node = node.next;
|
node = node.next;
|
||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
|
@ -220,7 +225,7 @@ export function calcStartUpdateVNode(treeRoot: VNode) {
|
||||||
const pathIndex = Number(startNodePath[i]);
|
const pathIndex = Number(startNodePath[i]);
|
||||||
node = getChildByIndex(node, pathIndex)!;
|
node = getChildByIndex(node, pathIndex)!;
|
||||||
// 路径错误时,回退到从根更新
|
// 路径错误时,回退到从根更新
|
||||||
if (node == null) {
|
if (node === null) {
|
||||||
return treeRoot;
|
return treeRoot;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,6 +233,40 @@ export function calcStartUpdateVNode(treeRoot: VNode) {
|
||||||
return node;
|
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) {
|
function buildVNodeTree(treeRoot: VNode) {
|
||||||
const preMode = copyExecuteMode();
|
const preMode = copyExecuteMode();
|
||||||
|
@ -299,43 +338,11 @@ function buildVNodeTree(treeRoot: VNode) {
|
||||||
setExecuteMode(preMode);
|
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) {
|
function renderFromRoot(treeRoot) {
|
||||||
runAsyncEffects();
|
runAsyncEffects();
|
||||||
pushCurrentRoot(treeRoot);
|
pushCurrentRoot(treeRoot);
|
||||||
|
|
||||||
// 1. 构建vNode树
|
// 1. 构建vNode树
|
||||||
buildVNodeTree(treeRoot);
|
buildVNodeTree(treeRoot);
|
||||||
|
|
||||||
|
@ -346,11 +353,12 @@ function renderFromRoot(treeRoot) {
|
||||||
|
|
||||||
// 2. 提交变更
|
// 2. 提交变更
|
||||||
submitToRender(treeRoot);
|
submitToRender(treeRoot);
|
||||||
|
|
||||||
popCurrentRoot();
|
popCurrentRoot();
|
||||||
if (window.__HORIZON_DEV_HOOK__) {
|
if (window.__INULA_DEV_HOOK__) {
|
||||||
const hook = window.__HORIZON_DEV_HOOK__;
|
const hook = window.__INULA_DEV_HOOK__;
|
||||||
// injector.js 可能在 Horizon 代码之后加载,此时无 __HORIZON_DEV_HOOK__ 全局变量
|
// injector.js 可能在 Inula 代码之后加载,此时无 __INULA_DEV_HOOK__ 全局变量
|
||||||
// Horizon 代码初次加载时不会初始化 helper
|
// Inula 代码初次加载时不会初始化 helper
|
||||||
if (!hook.isInit) {
|
if (!hook.isInit) {
|
||||||
injectUpdater();
|
injectUpdater();
|
||||||
}
|
}
|
||||||
|
@ -390,7 +398,7 @@ export function launchUpdateFromVNode(vNode: VNode) {
|
||||||
) {
|
) {
|
||||||
// 不是渲染阶段触发
|
// 不是渲染阶段触发
|
||||||
|
|
||||||
// 业务直接调用Horizon.render的时候会进入这个分支,同步渲染。
|
// 业务直接调用Inula.render的时候会进入这个分支,同步渲染。
|
||||||
// 不能改成下面的异步,否则会有时序问题,因为业务可能会依赖这个渲染的完成。
|
// 不能改成下面的异步,否则会有时序问题,因为业务可能会依赖这个渲染的完成。
|
||||||
renderFromRoot(treeRoot);
|
renderFromRoot(treeRoot);
|
||||||
} else {
|
} else {
|
||||||
|
@ -403,7 +411,7 @@ export function launchUpdateFromVNode(vNode: VNode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================== HorizonDOM使用 ==============================
|
// ============================== InulaDOM使用 ==============================
|
||||||
export function runDiscreteUpdates() {
|
export function runDiscreteUpdates() {
|
||||||
if (checkMode(ByAsync) || checkMode(InRender)) {
|
if (checkMode(ByAsync) || checkMode(InRender)) {
|
||||||
// 已经渲染,不能再同步执行待工作的任务,有可能是被生命周期或effect触发的事件导致的,如el.focus()
|
// 已经渲染,不能再同步执行待工作的任务,有可能是被生命周期或effect触发的事件导致的,如el.focus()
|
|
@ -13,6 +13,8 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { BELONG_CLASS_VNODE_KEY } from './vnode/VNode';
|
||||||
|
|
||||||
export { VNode } from './vnode/VNode';
|
export { VNode } from './vnode/VNode';
|
||||||
|
|
||||||
type Trigger<A> = (A) => void;
|
type Trigger<A> = (A) => void;
|
||||||
|
@ -32,7 +34,7 @@ export type JSXElement = {
|
||||||
key: any;
|
key: any;
|
||||||
ref: any;
|
ref: any;
|
||||||
props: any;
|
props: any;
|
||||||
belongClassVNode: any;
|
[BELONG_CLASS_VNODE_KEY]: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProviderType<T> = {
|
export type ProviderType<T> = {
|
||||||
|
@ -77,3 +79,5 @@ export type Source = {
|
||||||
fileName: string;
|
fileName: string;
|
||||||
lineNumber: number;
|
lineNumber: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Callback = () => void;
|
|
@ -13,7 +13,7 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* 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';
|
import { FlagUtils, ShouldCapture } from './vnode/VNodeFlags';
|
||||||
|
|
||||||
export type Update = {
|
export type Update = {
|
||||||
|
@ -22,8 +22,6 @@ export type Update = {
|
||||||
callback: Callback | null;
|
callback: Callback | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Callback = () => any;
|
|
||||||
|
|
||||||
export type Updates = Array<Update> | null;
|
export type Updates = Array<Update> | null;
|
||||||
|
|
||||||
export enum UpdateState {
|
export enum UpdateState {
|
||||||
|
@ -37,8 +35,8 @@ export enum UpdateState {
|
||||||
export function newUpdate(): Update {
|
export function newUpdate(): Update {
|
||||||
return {
|
return {
|
||||||
type: UpdateState.Update, // 更新的类型
|
type: UpdateState.Update, // 更新的类型
|
||||||
content: null, // ClassComponent的content是setState第一个参数,TreeRoot的content是HorizonDOM.render的第一个参数
|
content: null, // ClassComponent的content是setState第一个参数,TreeRoot的content是InulaDOM.render的第一个参数
|
||||||
callback: null, // setState的第二个参数,HorizonDOM.render的第三个参数
|
callback: null, // setState的第二个参数,InulaDOM.render的第三个参数
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {Callback} from '../Types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component的api setState和forceUpdate在实例生成阶段实现
|
* Component的api setState和forceUpdate在实例生成阶段实现
|
||||||
*/
|
*/
|
||||||
|
@ -29,9 +31,9 @@ class Component<P, S, C> {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(state: S) {
|
setState(state: S, callback?: Callback) {
|
||||||
if (isDev) {
|
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';
|
import { TYPE_MEMO } from '../../external/JSXElementType';
|
||||||
|
|
||||||
export function memo<Props>(type, compare?: (oldProps: Props, newProps: Props) => boolean) {
|
export function memo<Props>(type, compare?: (oldProps: Props, newProps: Props) => boolean) {
|
||||||
return {
|
const memoJSXElement = {
|
||||||
vtype: TYPE_MEMO,
|
vtype: TYPE_MEMO,
|
||||||
|
$$typeof: TYPE_MEMO, // 规避三方件hoist-non-react-statics中,通过$$typeof获取类型,但获取不到,导致type被覆盖
|
||||||
type: type,
|
type: type,
|
||||||
compare: compare === undefined ? null : compare,
|
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 { isSameType, getIteratorFn, isTextType, isIteratorType, isObjectType } from './DiffTools';
|
||||||
import { travelChildren } from '../vnode/VNodeUtils';
|
import { travelChildren } from '../vnode/VNodeUtils';
|
||||||
import { markVNodePath } from '../utils/vNodePath';
|
import { markVNodePath } from '../utils/vNodePath';
|
||||||
|
import { BELONG_CLASS_VNODE_KEY } from '../vnode/VNode';
|
||||||
|
|
||||||
enum DiffCategory {
|
enum DiffCategory {
|
||||||
TEXT_NODE = 'TEXT_NODE',
|
TEXT_NODE = 'TEXT_NODE',
|
||||||
|
@ -166,11 +167,11 @@ function getNewNode(parentNode: VNode, newChild: any, oldNode: VNode | null) {
|
||||||
if (oldNode === null || !isSameType(oldNode, newChild)) {
|
if (oldNode === null || !isSameType(oldNode, newChild)) {
|
||||||
resultNode = createVNodeFromElement(newChild);
|
resultNode = createVNodeFromElement(newChild);
|
||||||
resultNode.ref = newChild.ref;
|
resultNode.ref = newChild.ref;
|
||||||
resultNode.belongClassVNode = newChild.belongClassVNode;
|
resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY];
|
||||||
} else {
|
} else {
|
||||||
resultNode = updateVNode(oldNode, newChild.props);
|
resultNode = updateVNode(oldNode, newChild.props);
|
||||||
resultNode.ref = newChild.ref;
|
resultNode.ref = newChild.ref;
|
||||||
resultNode.belongClassVNode = newChild.belongClassVNode;
|
resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
} else if (newChild.vtype === TYPE_PORTAL) {
|
} else if (newChild.vtype === TYPE_PORTAL) {
|
||||||
|
@ -181,6 +182,10 @@ function getNewNode(parentNode: VNode, newChild: any, oldNode: VNode | null) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,6 +236,19 @@ function getOldNodeFromMap(nodeMap: Map<string | number, VNode>, newIdx: number,
|
||||||
return null;
|
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数组类型的节点,核心算法
|
// diff数组类型的节点,核心算法
|
||||||
function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newChildren: Array<any>): VNode | null {
|
function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newChildren: Array<any>): VNode | null {
|
||||||
let resultingFirstChild: VNode | null = null;
|
let resultingFirstChild: VNode | null = null;
|
||||||
|
@ -360,7 +378,7 @@ function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newC
|
||||||
// 4. 新节点还有一部分,但是老节点已经没有了
|
// 4. 新节点还有一部分,但是老节点已经没有了
|
||||||
if (oldNode === null) {
|
if (oldNode === null) {
|
||||||
let isDirectAdd = false;
|
let isDirectAdd = false;
|
||||||
// TODO: 是否可以扩大至非dom类型节点
|
// 是否可以扩大至非dom类型节点待确认
|
||||||
// 如果dom节点在上次添加前没有节点,说明本次添加时,可以直接添加到最后,不需要通过 getSiblingDom 函数找到 before 节点
|
// 如果dom节点在上次添加前没有节点,说明本次添加时,可以直接添加到最后,不需要通过 getSiblingDom 函数找到 before 节点
|
||||||
if (
|
if (
|
||||||
parentNode.tag === DomComponent &&
|
parentNode.tag === DomComponent &&
|
||||||
|
@ -478,19 +496,6 @@ function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newC
|
||||||
return resultingFirstChild;
|
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(
|
function diffIteratorNodesHandler(
|
||||||
parentNode: VNode,
|
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;
|
let newTextNode: VNode | null = null;
|
||||||
|
|
||||||
// 第一个vNode是Text,则复用
|
// 第一个vNode是Text,则复用
|
||||||
|
@ -559,7 +564,7 @@ function diffObjectNodeHandler(
|
||||||
}
|
}
|
||||||
|
|
||||||
let resultNode: VNode | null = null;
|
let resultNode: VNode | null = null;
|
||||||
let startDelVNode = firstChildVNode;
|
let startDelVNode: VNode | null = firstChildVNode;
|
||||||
if (newChild.vtype === TYPE_COMMON_ELEMENT) {
|
if (newChild.vtype === TYPE_COMMON_ELEMENT) {
|
||||||
if (canReuseNode) {
|
if (canReuseNode) {
|
||||||
// 可以复用
|
// 可以复用
|
||||||
|
@ -570,7 +575,7 @@ function diffObjectNodeHandler(
|
||||||
} else if (isSameType(canReuseNode, newChild)) {
|
} else if (isSameType(canReuseNode, newChild)) {
|
||||||
resultNode = updateVNode(canReuseNode, newChild.props);
|
resultNode = updateVNode(canReuseNode, newChild.props);
|
||||||
resultNode.ref = newChild.ref;
|
resultNode.ref = newChild.ref;
|
||||||
resultNode.belongClassVNode = newChild.belongClassVNode;
|
resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY];
|
||||||
startDelVNode = resultNode.next;
|
startDelVNode = resultNode.next;
|
||||||
resultNode.next = null;
|
resultNode.next = null;
|
||||||
}
|
}
|
||||||
|
@ -583,7 +588,7 @@ function diffObjectNodeHandler(
|
||||||
} else {
|
} else {
|
||||||
resultNode = createVNodeFromElement(newChild);
|
resultNode = createVNodeFromElement(newChild);
|
||||||
resultNode.ref = newChild.ref;
|
resultNode.ref = newChild.ref;
|
||||||
resultNode.belongClassVNode = newChild.belongClassVNode;
|
resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (newChild.vtype === TYPE_PORTAL) {
|
} else if (newChild.vtype === TYPE_PORTAL) {
|
|
@ -34,7 +34,7 @@ export function setCurrentHook(hook: Hook<any, any> | null) {
|
||||||
currentHook = hook;
|
currentHook = hook;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function throwNotInFuncError() {
|
export function throwNotInFuncError(): never {
|
||||||
throw Error('Hooks should be used inside function component.');
|
throw Error('Hooks should be used inside function component.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export function createHook(state: any = null): Hook<any, any> {
|
||||||
return currentHook;
|
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;
|
return hooks[hook.hIndex + 1] || null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,22 +27,25 @@ import { getProcessingVNode } from '../GlobalVar';
|
||||||
import { Ref, Trigger } from './HookType';
|
import { Ref, Trigger } from './HookType';
|
||||||
|
|
||||||
type BasicStateAction<S> = ((S) => S) | S;
|
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 {
|
export function useContext<T>(Context: ContextType<T>): T {
|
||||||
const processingVNode = getProcessingVNode();
|
const processingVNode = getProcessingVNode();
|
||||||
return getNewContext(processingVNode!, Context, true);
|
return getNewContext(processingVNode!, Context, true);
|
||||||
}
|
}
|
||||||
|
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>>]
|
||||||
|
export function useState<S>(initialState?: (() => S) | S): [S, Dispatch<BasicStateAction<S>>] {
|
||||||
return useStateImpl(initialState);
|
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);
|
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);
|
return useRefImpl(initialValue);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,12 @@ import type { VNode } from '../Types';
|
||||||
import { getLastTimeHook, setLastTimeHook, setCurrentHook, getNextHook } from './BaseHook';
|
import { getLastTimeHook, setLastTimeHook, setCurrentHook, getNextHook } from './BaseHook';
|
||||||
import { HookStage, setHookStage } from './HookStage';
|
import { HookStage, setHookStage } from './HookStage';
|
||||||
|
|
||||||
|
function resetGlobalVariable() {
|
||||||
|
setHookStage(null);
|
||||||
|
setLastTimeHook(null);
|
||||||
|
setCurrentHook(null);
|
||||||
|
}
|
||||||
|
|
||||||
// hook对外入口
|
// hook对外入口
|
||||||
export function runFunctionWithHooks<Props extends Record<string, any>, Arg>(
|
export function runFunctionWithHooks<Props extends Record<string, any>, Arg>(
|
||||||
funcComp: (props: Props, arg: Arg) => any,
|
funcComp: (props: Props, arg: Arg) => any,
|
||||||
|
@ -57,9 +63,3 @@ export function runFunctionWithHooks<Props extends Record<string, any>, Arg>(
|
||||||
|
|
||||||
return comp;
|
return comp;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetGlobalVariable() {
|
|
||||||
setHookStage(null);
|
|
||||||
setLastTimeHook(null);
|
|
||||||
setCurrentHook(null);
|
|
||||||
}
|
|
|
@ -57,4 +57,4 @@ export type Ref<V> = {
|
||||||
current: 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 { isArrayEqual } from '../utils/compare';
|
||||||
import { getProcessingVNode } from '../GlobalVar';
|
import { getProcessingVNode } from '../GlobalVar';
|
||||||
|
|
||||||
export function useEffectImpl(effectFunc: () => (() => void) | void, deps?: Array<any> | null): void {
|
function createEffect(effectFunc, removeFunc, deps, effectConstant): Effect {
|
||||||
// 异步触发的effect
|
const effect: Effect = {
|
||||||
useEffect(effectFunc, deps, EffectConstant.Effect);
|
effect: effectFunc,
|
||||||
}
|
removeEffect: removeFunc,
|
||||||
|
dependencies: deps,
|
||||||
|
effectConstant: effectConstant,
|
||||||
|
};
|
||||||
|
|
||||||
export function useLayoutEffectImpl(effectFunc: () => (() => void) | void, deps?: Array<any> | null): void {
|
getProcessingVNode().effectList.push(effect);
|
||||||
// 同步触发的effect
|
|
||||||
useEffect(effectFunc, deps, EffectConstant.LayoutEffect);
|
|
||||||
}
|
|
||||||
|
|
||||||
function useEffect(effectFunc: () => (() => void) | void, deps: Array<any> | void | null, effectType: number): void {
|
return effect;
|
||||||
const stage = getHookStage();
|
|
||||||
if (stage === null) {
|
|
||||||
throwNotInFuncError();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stage === HookStage.Init) {
|
|
||||||
useEffectForInit(effectFunc, deps, effectType);
|
|
||||||
} else if (stage === HookStage.Update) {
|
|
||||||
useEffectForUpdate(effectFunc, deps, effectType);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEffectForInit(effectFunc, deps, effectType): void {
|
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);
|
hook.state = createEffect(effectFunc, removeFunc, nextDeps, EffectConstant.DepsChange | effectType);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createEffect(effectFunc, removeFunc, deps, effectConstant): Effect {
|
function useEffect(effectFunc: () => (() => void) | void, deps: Array<any> | void | null, effectType: number): void {
|
||||||
const effect: Effect = {
|
const stage = getHookStage();
|
||||||
effect: effectFunc,
|
if (stage === null) {
|
||||||
removeEffect: removeFunc,
|
throwNotInFuncError();
|
||||||
dependencies: deps,
|
}
|
||||||
effectConstant: effectConstant,
|
|
||||||
};
|
|
||||||
|
|
||||||
getProcessingVNode().effectList.push(effect);
|
if (stage === HookStage.Init) {
|
||||||
|
useEffectForInit(effectFunc, deps, effectType);
|
||||||
return effect;
|
} 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 { getHookStage } from './HookStage';
|
||||||
import { throwNotInFuncError } from './BaseHook';
|
import { throwNotInFuncError } from './BaseHook';
|
||||||
import type { Ref } from './HookType';
|
import type { Ref } from './HookType';
|
||||||
|
import { isNotNull } from '../../dom/utils/Common';
|
||||||
|
|
||||||
export function useImperativeHandleImpl<R>(
|
function effectFunc<R>(func: () => R, ref: Ref<R> | ((any) => any) | null): (() => void) | null {
|
||||||
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 {
|
|
||||||
if (typeof ref === 'function') {
|
if (typeof ref === 'function') {
|
||||||
const value = func();
|
const value = func();
|
||||||
ref(value);
|
ref(value);
|
||||||
|
@ -51,4 +34,19 @@ function effectFunc<R>(func: () => R, ref: Ref<R> | ((any) => any) | null): (()
|
||||||
ref.current = 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 type { VNode } from '../Types';
|
||||||
import { getProcessingVNode } from '../GlobalVar';
|
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数组
|
// 构造新的Update数组
|
||||||
function insertUpdate<S, A>(action: A, hook: Hook<S, A>): Update<S, A> {
|
function insertUpdate<S, A>(action: A, hook: Hook<S, A>): Update<S, A> {
|
||||||
const newUpdate: 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];
|
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
|
// 更新hook.state
|
||||||
function updateReducerHookState<S, A>(currentHookUpdates, currentHook, reducer): [S, Trigger<A>] {
|
function updateReducerHookState<S, A>(currentHookUpdates, currentHook, reducer): [S, Trigger<A>] {
|
||||||
if (currentHookUpdates !== null) {
|
if (currentHookUpdates !== null) {
|
||||||
|
@ -135,21 +131,25 @@ function updateReducerHookState<S, A>(currentHookUpdates, currentHook, reducer):
|
||||||
return [currentHook.state.stateValue, currentHook.state.trigger];
|
return [currentHook.state.stateValue, currentHook.state.trigger];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算stateValue值
|
export function useReducerImpl<S, P, A>(
|
||||||
function calculateNewState<S, A>(currentHookUpdates: Array<Update<S, A>>, currentHook, reducer: (S, A) => S) {
|
reducer: (S, A) => S,
|
||||||
const reducerObj = currentHook.state;
|
initArg: P,
|
||||||
let state = reducerObj.stateValue;
|
init?: (P) => S,
|
||||||
|
isUseState?: boolean
|
||||||
|
): [S, Trigger<A>] | void {
|
||||||
|
const stage = getHookStage();
|
||||||
|
if (stage === null) {
|
||||||
|
throwNotInFuncError();
|
||||||
|
}
|
||||||
|
|
||||||
// 循环遍历更新数组,计算新的状态值
|
if (stage === HookStage.Init) {
|
||||||
currentHookUpdates.forEach(update => {
|
return useReducerForInit(reducer, initArg, init, isUseState);
|
||||||
// 1. didCalculated = true 说明state已经计算过; 2. 如果来自 isUseState
|
} else if (stage === HookStage.Update) {
|
||||||
if (update.didCalculated && reducerObj.isUseState) {
|
// 获取当前的hook
|
||||||
state = update.state;
|
const currentHook = getCurrentHook();
|
||||||
} else {
|
// 获取currentHook的更新数组
|
||||||
const action = update.action;
|
const currentHookUpdates = (currentHook.state as Reducer<S, A>).updates;
|
||||||
state = reducer(state, action);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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