Match-id-bae8261fcf2fb4e5c1748e8ce921736fe3bd9ae4

This commit is contained in:
* 2023-02-13 14:51:35 +08:00
parent 40160ad11b
commit 2f7787c1cf
44 changed files with 1977 additions and 326 deletions

View File

@ -26,8 +26,10 @@ module.exports = {
testEnvironment: 'jest-environment-jsdom-sixteen', testEnvironment: 'jest-environment-jsdom-sixteen',
testMatch: [ testMatch: [
// '<rootDir>/scripts/__tests__/HorizonXTest/edgeCases/deepVariableObserver.test.tsx',
// '<rootDir>/scripts/__tests__/HorizonXTest/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',

View File

@ -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';
@ -121,7 +120,24 @@ export function resolveMutation(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 !== '_horizonObserver');
const res = {}; const res = {};
let found = false; let found = false;
keys.forEach(key => { keys.forEach(key => {
@ -142,8 +158,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 +165,9 @@ export function resolveMutation(from, to) {
} }
} }
} }
export function omit(obj, ...attrs) {
let res = { ...obj };
attrs.forEach(attr => delete res[attr]);
return res;
}

View File

@ -1,3 +1,5 @@
import { isDomVNode } from '../../renderer/vnode/VNodeUtils';
import { isMap, isSet, isWeakMap, isWeakSet } from '../CommonUtils';
import { getStore, getAllStores } from '../store/StoreHandler'; import { getStore, getAllStores } from '../store/StoreHandler';
import { OBSERVED_COMPONENTS } from './constants'; import { OBSERVED_COMPONENTS } from './constants';
@ -24,28 +26,87 @@ function makeStoreSnapshot({ type, data }) {
} }
// safely serializes variables containing values wrapped in Proxy object // 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) { function makeProxySnapshot(obj) {
const type = getType(obj);
let clone; let clone;
try { try {
if (!obj) { //NULLISH VALUE
if (type === 'nullish') {
return obj; return obj;
} }
if (obj.nativeEvent) return obj.type + 'Event'; //EVENT
if (typeof obj === 'function') { if (type === 'event') return obj.type + 'Event';
// FUNCTION
if (type === 'function') {
return obj.toString(); return obj.toString();
} }
if (Array.isArray(obj)) { // VNODE
if (type === 'vnode') {
return {
_type: 'VNode',
id: window['__HORIZON_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') {
clone = []; clone = [];
obj.forEach(item => clone.push(makeProxySnapshot(item))); obj.forEach(item => clone.push(makeProxySnapshot(item)));
return clone; return clone;
} else if (typeof obj === 'object') { }
// OBJECT
if (type === 'object') {
clone = {}; clone = {};
Object.entries(obj).forEach(([id, value]) => (clone[id] = makeProxySnapshot(value))); Object.entries(obj).forEach(([id, value]) => (clone[id] = makeProxySnapshot(value)));
return clone; return clone;
} }
// PRIMITIVE
return obj; return obj;
} catch (err) { } catch (err) {
throw console.log('cannot serialize object. ' + err); console.error('cannot serialize object. ', { err, obj, type });
} }
} }
@ -76,7 +137,7 @@ function getAffectedComponents() {
const subRes = new Set(); const subRes = new Set();
const process = Array.from(allStores[key].$config.state._horizonObserver.keyVNodes.values()); const process = Array.from(allStores[key].$config.state._horizonObserver.keyVNodes.values());
while (process.length) { while (process.length) {
let pivot = process.shift(); let pivot = process.shift() as { tag: 'string' };
if (pivot?.tag) subRes.add(pivot); if (pivot?.tag) subRes.add(pivot);
if (pivot?.toString() === '[object Set]') Array.from(pivot).forEach(item => process.push(item)); if (pivot?.toString() === '[object Set]') Array.from(pivot).forEach(item => process.push(item));
} }
@ -117,4 +178,37 @@ window.addEventListener('message', messageEvent => {
const params = data.params; const params = data.params;
action(...params); action(...params);
} }
// queues store action
if (messageEvent.data.payload.type === 'horizonx 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 === 'horizonx 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);
}
}
// TODO:implement add and delete element
}
}); });

View File

@ -31,6 +31,10 @@ 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);
} }

View File

@ -27,7 +27,7 @@ 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 createProxy(rawObj: any, isHookObserver = true, listener: { current: (...args) => any }): any {
// 不是对象(是原始数据类型)不用代理 // 不是对象(是原始数据类型)不用代理
if (!(rawObj && isObject(rawObj))) { if (!(rawObj && isObject(rawObj))) {
return rawObj; return rawObj;
@ -56,16 +56,32 @@ 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, true, {
current: change => {
listener.current(change);
},
});
} 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, true, {
current: change => {
listener.current(change);
},
});
} else { } else {
// 原生对象 或 函数 // 原生对象 或 函数
proxyObj = createObjectProxy(rawObj); proxyObj = createObjectProxy(rawObj, false, {
current: change => {
listener?.current(change);
},
});
} }
proxyMap.set(rawObj, proxyObj); proxyMap.set(rawObj, proxyObj);

View File

@ -0,0 +1,85 @@
/*
* 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 { getObserver } from '../ProxyHandler';
import { isSame, isValidIntegerKey } from '../../CommonUtils';
import { get as objectGet } from './ObjectProxyHandler';
import { resolveMutation } from '../../CommonUtils';
import { isPanelActive } from '../../devtools';
export function createArrayProxy(rawObj: any[]): any[] {
const handle = {
get,
set,
};
return new Proxy(rawObj, handle);
}
function get(rawObj: any[], key: string, receiver: any) {
if (key === 'watch') {
const observer = getObserver(rawObj);
return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => {
if (!observer.watchers[prop]) {
observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[];
}
observer.watchers[prop].push(handler);
return () => {
observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler);
};
};
}
if (isValidIntegerKey(key) || key === 'length') {
return objectGet(rawObj, key, receiver);
}
return Reflect.get(rawObj, key, receiver);
}
function set(rawObj: any[], key: string, value: any, receiver: any) {
const oldValue = rawObj[key];
const oldLength = rawObj.length;
const newValue = value;
const oldArray = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
const ret = Reflect.set(rawObj, key, newValue, receiver);
const newLength = rawObj.length;
const observer = getObserver(rawObj);
const mutation = isPanelActive() ? resolveMutation(oldArray, rawObj) : { mutation: true, from: [], to: rawObj };
if (!isSame(newValue, oldValue)) {
// 值不一样,触发监听器
if (observer.watchers?.[key]) {
observer.watchers[key].forEach(cb => {
cb(key, oldValue, newValue, mutation);
});
}
// 触发属性变化
observer.setProp(key, mutation);
}
if (oldLength !== newLength) {
// 触发数组的大小变化
observer.setProp('length', mutation);
}
return ret;
}

View File

@ -0,0 +1,235 @@
/*
* 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 { isMap, isWeakMap, isSame } from '../../CommonUtils';
import { resolveMutation } from '../../CommonUtils';
import { isPanelActive } from '../../devtools';
const COLLECTION_CHANGE = '_collectionChange';
const handler = {
get,
set,
add,
delete: deleteFun,
clear,
has,
entries,
forEach,
keys,
values,
// 判断Symbol类型兼容IE
[typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']: forOf,
};
export function createCollectionProxy(rawObj: Object, hookObserver = true): Object {
const boundHandler = {};
Object.entries(handler).forEach(([id, val]) => {
boundHandler[id] = (...args: any[]) => {
return (val as any)(...args, hookObserver);
};
});
return new Proxy(rawObj, { ...boundHandler });
}
function get(rawObj: { size: number }, key: any, receiver: any): any {
if (key === 'size') {
return size(rawObj);
} else if (key === 'get') {
return getFun.bind(null, rawObj);
} else if (Object.prototype.hasOwnProperty.call(handler, key)) {
const value = Reflect.get(handler, key, receiver);
return value.bind(null, rawObj);
} else if (key === 'watch') {
const observer = getObserver(rawObj);
return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => {
if (!observer.watchers[prop]) {
observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[];
}
observer.watchers[prop].push(handler);
return () => {
observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler);
};
};
}
return Reflect.get(rawObj, key, receiver);
}
function getFun(rawObj: { get: (key: any) => any }, key: any) {
const observer = getObserver(rawObj);
observer.useProp(key);
const value = rawObj.get(key);
// 对于value也需要进一步代理
const valProxy = createProxy(value, hookObserverMap.get(rawObj));
return valProxy;
}
// Map的set方法
function set(
rawObj: { get: (key: any) => any; set: (key: any, value: any) => any; has: (key: any) => boolean },
key: any,
value: any
) {
const oldValue = rawObj.get(key);
const newValue = value;
rawObj.set(key, newValue);
const valChange = !isSame(newValue, oldValue);
const observer = getObserver(rawObj);
const mutation = isPanelActive() ? resolveMutation(oldValue, rawObj) : { mutation: true, from: null, to: rawObj };
if (valChange || !rawObj.has(key)) {
observer.setProp(COLLECTION_CHANGE, mutation);
}
if (valChange) {
if (observer.watchers?.[key]) {
observer.watchers[key].forEach(cb => {
cb(key, oldValue, newValue, mutation);
});
}
observer.setProp(key, mutation);
}
return rawObj;
}
// Set的add方法
function add(rawObj: { add: (any) => void; set: (string, any) => any; has: (any) => boolean }, value: any): Object {
const oldCollection = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
if (!rawObj.has(value)) {
rawObj.add(value);
const observer = getObserver(rawObj);
const mutation = isPanelActive()
? resolveMutation(oldCollection, rawObj)
: { mutation: true, from: null, to: rawObj };
observer.setProp(value, mutation);
observer.setProp(COLLECTION_CHANGE, mutation);
}
return rawObj;
}
function has(rawObj: { has: (string) => boolean }, key: any): boolean {
const observer = getObserver(rawObj);
observer.useProp(key);
return rawObj.has(key);
}
function clear(rawObj: { size: number; clear: () => void }) {
const oldSize = rawObj.size;
rawObj.clear();
if (oldSize > 0) {
const observer = getObserver(rawObj);
observer.allChange();
}
}
function deleteFun(rawObj: { has: (key: any) => boolean; delete: (key: any) => void }, key: any) {
const oldCollection = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
if (rawObj.has(key)) {
rawObj.delete(key);
const observer = getObserver(rawObj);
const mutation = isPanelActive()
? resolveMutation(oldCollection, rawObj)
: { mutation: true, from: null, to: rawObj };
observer.setProp(key, mutation);
observer.setProp(COLLECTION_CHANGE, mutation);
return true;
}
return false;
}
function size(rawObj: { size: number }) {
const observer = getObserver(rawObj);
observer.useProp(COLLECTION_CHANGE);
return rawObj.size;
}
function keys(rawObj: { keys: () => { next: () => { value: any; done: boolean } } }) {
return wrapIterator(rawObj, rawObj.keys());
}
function values(rawObj: { values: () => { next: () => { value: any; done: boolean } } }) {
return wrapIterator(rawObj, rawObj.values());
}
function entries(rawObj: { entries: () => { next: () => { value: any; done: boolean } } }) {
return wrapIterator(rawObj, rawObj.entries(), true);
}
function forOf(rawObj: {
entries: () => { next: () => { value: any; done: boolean } };
values: () => { next: () => { value: any; done: boolean } };
}) {
const isMapType = isMap(rawObj) || isWeakMap(rawObj);
const iterator = isMapType ? rawObj.entries() : rawObj.values();
return wrapIterator(rawObj, iterator, isMapType);
}
function forEach(
rawObj: { forEach: (callback: (value: any, key: any) => void) => void },
callback: (valProxy: any, keyProxy: any, rawObj: any) => void
) {
const observer = getObserver(rawObj);
observer.useProp(COLLECTION_CHANGE);
rawObj.forEach((value, key) => {
const valProxy = createProxy(value, hookObserverMap.get(rawObj));
const keyProxy = createProxy(key, hookObserverMap.get(rawObj));
// 最后一个参数要返回代理对象
return callback(valProxy, keyProxy, rawObj);
});
}
function wrapIterator(rawObj: Object, rawIt: { next: () => { value: any; done: boolean } }, isPair = false) {
const observer = getObserver(rawObj);
const hookObserver = hookObserverMap.get(rawObj);
observer.useProp(COLLECTION_CHANGE);
return {
next() {
const { value, done } = rawIt.next();
if (done) {
return { value: createProxy(value, hookObserver), done };
}
observer.useProp(COLLECTION_CHANGE);
let newVal;
if (isPair) {
newVal = [createProxy(value[0], hookObserver), createProxy(value[1], hookObserver)];
} else {
newVal = createProxy(value, hookObserver);
}
return { value: newVal, done };
},
// 判断Symbol类型兼容IE
[typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']() {
return this;
},
};
}

View File

@ -0,0 +1,92 @@
/*
* 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 { 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);
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;
}

View File

@ -13,43 +13,112 @@
* See the Mulan PSL v2 for more details. * See the Mulan PSL v2 for more details.
*/ */
import { getObserver } from '../ProxyHandler'; import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
import { isSame, isValidIntegerKey } from '../../CommonUtils'; import { isSame, isValidIntegerKey } from '../../CommonUtils';
import { get as objectGet } from './ObjectProxyHandler';
import { resolveMutation } from '../../CommonUtils'; import { resolveMutation } from '../../CommonUtils';
import { isPanelActive } from '../../devtools'; import { isPanelActive } from '../../devtools';
import { OBSERVER_KEY } from '../../Constants';
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 ('_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 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, hookObserverMap.get(rawObj), {
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));
},
});
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);
}
return Reflect.get(rawObj, key, receiver);
}
export function createArrayProxy(rawObj: any[]): any[] {
const handle = { const handle = {
get, get,
set, 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); 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) { function set(rawObj: any[], key: string, value: any, receiver: any) {
const oldValue = rawObj[key]; const oldValue = rawObj[key];
const oldLength = rawObj.length; const oldLength = rawObj.length;
@ -62,7 +131,7 @@ function set(rawObj: any[], key: string, value: any, receiver: any) {
const newLength = rawObj.length; const newLength = rawObj.length;
const observer = getObserver(rawObj); const observer = getObserver(rawObj);
const mutation = isPanelActive() ? resolveMutation(oldArray, rawObj) : { mutation: true, from: [], to: rawObj }; const mutation = isPanelActive() ? resolveMutation(oldArray, rawObj) : resolveMutation(null, rawObj);
if (!isSame(newValue, oldValue)) { if (!isSame(newValue, oldValue)) {
// 值不一样,触发监听器 // 值不一样,触发监听器

View File

@ -13,223 +13,25 @@
* See the Mulan PSL v2 for more details. * See the Mulan PSL v2 for more details.
*/ */
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler'; import { isWeakMap, isWeakSet, isSet } from '../../CommonUtils';
import { isMap, isWeakMap, isSame } from '../../CommonUtils'; import { createWeakSetProxy } from './WeakSetProxy';
import { resolveMutation } from '../../CommonUtils'; import { createSetProxy } from './SetProxy';
import { isPanelActive } from '../../devtools'; import { createWeakMapProxy } from './WeakMapProxy';
import { createMapProxy } from './MapProxy';
const COLLECTION_CHANGE = '_collectionChange'; export function createCollectionProxy(
const handler = { rawObj: Object,
get, hookObserver = true,
set, listener: { current: (...args) => any }
add, ): Object {
delete: deleteFun, if (isWeakSet(rawObj)) {
clear, return createWeakSetProxy(rawObj, hookObserver, listener);
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);
};
};
} }
if (isSet(rawObj)) {
return Reflect.get(rawObj, key, receiver); return createSetProxy(rawObj, hookObserver, listener);
}
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 (isWeakMap(rawObj)) {
if (valChange) { return createWeakMapProxy(rawObj, hookObserver, listener);
if (observer.watchers?.[key]) {
observer.watchers[key].forEach(cb => {
cb(key, oldValue, newValue, mutation);
});
}
observer.setProp(key, mutation);
} }
return createMapProxy(rawObj, hookObserver, listener);
return rawObj;
}
// Set的add方法
function add(rawObj: { add: (any) => void; set: (string, any) => any; has: (any) => boolean }, value: any): Object {
const oldCollection = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
if (!rawObj.has(value)) {
rawObj.add(value);
const observer = getObserver(rawObj);
const mutation = isPanelActive()
? resolveMutation(oldCollection, rawObj)
: { mutation: true, from: null, to: rawObj };
observer.setProp(value, mutation);
observer.setProp(COLLECTION_CHANGE, mutation);
}
return rawObj;
}
function has(rawObj: { has: (string) => boolean }, key: any): boolean {
const observer = getObserver(rawObj);
observer.useProp(key);
return rawObj.has(key);
}
function clear(rawObj: { size: number; clear: () => void }) {
const oldSize = rawObj.size;
rawObj.clear();
if (oldSize > 0) {
const observer = getObserver(rawObj);
observer.allChange();
}
}
function deleteFun(rawObj: { has: (key: any) => boolean; delete: (key: any) => void }, key: any) {
const oldCollection = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
if (rawObj.has(key)) {
rawObj.delete(key);
const observer = getObserver(rawObj);
const mutation = isPanelActive()
? resolveMutation(oldCollection, rawObj)
: { mutation: true, from: null, to: rawObj };
observer.setProp(key, mutation);
observer.setProp(COLLECTION_CHANGE, mutation);
return true;
}
return false;
}
function size(rawObj: { size: number }) {
const observer = getObserver(rawObj);
observer.useProp(COLLECTION_CHANGE);
return rawObj.size;
}
function keys(rawObj: { keys: () => { next: () => { value: any; done: boolean } } }) {
return wrapIterator(rawObj, rawObj.keys());
}
function values(rawObj: { values: () => { next: () => { value: any; done: boolean } } }) {
return wrapIterator(rawObj, rawObj.values());
}
function entries(rawObj: { entries: () => { next: () => { value: any; done: boolean } } }) {
return wrapIterator(rawObj, rawObj.entries(), true);
}
function forOf(rawObj: {
entries: () => { next: () => { value: any; done: boolean } };
values: () => { next: () => { value: any; done: boolean } };
}) {
const isMapType = isMap(rawObj) || isWeakMap(rawObj);
const iterator = isMapType ? rawObj.entries() : rawObj.values();
return wrapIterator(rawObj, iterator, isMapType);
}
function forEach(
rawObj: { forEach: (callback: (value: any, key: any) => void) => void },
callback: (valProxy: any, keyProxy: any, rawObj: any) => void
) {
const observer = getObserver(rawObj);
observer.useProp(COLLECTION_CHANGE);
rawObj.forEach((value, key) => {
const valProxy = createProxy(value, hookObserverMap.get(rawObj));
const keyProxy = createProxy(key, hookObserverMap.get(rawObj));
// 最后一个参数要返回代理对象
return callback(valProxy, keyProxy, rawObj);
});
}
function wrapIterator(rawObj: Object, rawIt: { next: () => { value: any; done: boolean } }, isPair = false) {
const observer = getObserver(rawObj);
const hookObserver = hookObserverMap.get(rawObj);
observer.useProp(COLLECTION_CHANGE);
return {
next() {
const { value, done } = rawIt.next();
if (done) {
return { value: createProxy(value, hookObserver), done };
}
observer.useProp(COLLECTION_CHANGE);
let newVal;
if (isPair) {
newVal = [createProxy(value[0], hookObserver), createProxy(value[1], hookObserver)];
} else {
newVal = createProxy(value, hookObserver);
}
return { value: newVal, done };
},
// 判断Symbol类型兼容IE
[typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']() {
return this;
},
};
} }

View File

@ -0,0 +1,386 @@
/*
* 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';
const COLLECTION_CHANGE = '_collectionChange';
export function createMapProxy(rawObj: Object, hookObserver = true, listener: { current: (...args) => any }): Object {
let listeners: ((mutation) => {})[] = [];
let oldData: [any, any][] = [];
let proxies = new Map();
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);
};
}
return Reflect.get(rawObj, key, receiver);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function getFun(rawObj: { get: (key: any) => any; has: (key: any) => boolean }, key: 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, hookObserverMap.get(rawObj), {
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 }));
},
});
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
) {
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, hookObserverMap.get(rawObj), {
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 }));
},
});
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 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');
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
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, hookObserverMap.get(rawObj), {
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 }));
},
});
const valProxy = createProxy(key, hookObserverMap.get(rawObj), {
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 }));
},
});
// 最后一个参数要返回代理对象
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, hookObserver, {
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 }));
},
}),
done,
};
}
observer.useProp(COLLECTION_CHANGE);
let newVal;
if (type === 'entries') {
//ENTRY CHANGED
newVal = [
createProxy(value[0], hookObserver, {
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 }));
},
}),
createProxy(value[1], hookObserver, {
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 }));
},
}),
];
} else {
// SINGLE VALUE CHANGED
newVal = createProxy(value, hookObserver, {
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 }));
},
});
}
return { value: newVal, done };
},
// 判断Symbol类型兼容IE
[typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']() {
return this;
},
};
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const handler = {
get,
set,
delete: deleteFun,
clear,
has,
entries,
forEach,
keys,
values,
// 判断Symbol类型兼容IE
[typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']: forOf,
};
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const boundHandler = {};
Object.entries(handler).forEach(([id, val]) => {
boundHandler[id] = (...args: any[]) => {
return (val as any)(...args, hookObserver);
};
});
getObserver(rawObj).addListener(change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
listener.current(change);
listeners.forEach(lst => lst(change));
});
return new Proxy(rawObj, { ...boundHandler });
}

View File

@ -18,70 +18,97 @@ import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
import { OBSERVER_KEY } from '../../Constants'; import { OBSERVER_KEY } from '../../Constants';
import { isPanelActive } from '../../devtools'; import { isPanelActive } from '../../devtools';
export function createObjectProxy<T extends object>(rawObj: T, singleLevel = false): ProxyHandler<T> { export function createObjectProxy<T extends object>(
rawObj: T,
singleLevel = false,
listener: { current: (...args) => any }
): ProxyHandler<T> {
let listeners = [] as ((...args) => void)[];
function get(rawObj: object, key: string | symbol, receiver: any): 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 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, hookObserverMap.get(rawObj), {
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 }));
},
});
return valProxy;
}
return value;
}
const proxy = new Proxy(rawObj, { const proxy = new Proxy(rawObj, {
get: (...args) => get(...args, singleLevel), get,
set, set,
}); });
getObserver(rawObj).addListener(change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
listener.current(change);
listeners.forEach(lst => lst(change));
});
return proxy; return proxy;
} }
export function get(rawObj: object, key: string | symbol, receiver: any, singleLevel = false): any { function set(rawObj: object, key: string, value: any, receiver: any): boolean {
// 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 oldObject = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null;
const observer = getObserver(rawObj); const observer = getObserver(rawObj);
if (value && key == 'removeListener') {
observer.removeListener(value);
}
const oldValue = rawObj[key]; const oldValue = rawObj[key];
const newValue = value; const newValue = value;
const ret = Reflect.set(rawObj, key, newValue, receiver); const ret = Reflect.set(rawObj, key, newValue, receiver);
const mutation = isPanelActive() ? resolveMutation(oldObject, rawObj) : { mutation: true, from: null, to: rawObj }; const mutation = isPanelActive() ? resolveMutation(oldObject, rawObj) : resolveMutation(null, rawObj);
if (!isSame(newValue, oldValue)) { if (!isSame(newValue, oldValue)) {
if (observer.watchers?.[key]) { if (observer.watchers?.[key]) {

View File

@ -0,0 +1,297 @@
/*
* 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';
const COLLECTION_CHANGE = '_collectionChange';
export function createSetProxy<T extends object>(
rawObj: T,
hookObserver = true,
listener: { current: (...args) => any }
): ProxyHandler<T> {
let listeners: ((mutation) => {})[] = [];
let proxies = new WeakMap();
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);
};
};
}
return Reflect.get(rawObj, key, receiver);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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, hookObserverMap.get(rawObj), {
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 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;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
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 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, hookObserver, currentListener), done };
}
observer.useProp(COLLECTION_CHANGE);
let newVal;
newVal = createProxy(value, hookObserver, currentListener);
return { value: newVal, done };
},
// 判断Symbol类型兼容IE
[typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']() {
return this;
},
};
}
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, hookObserverMap.get(rawObj), currentListener);
const keyProxy = createProxy(key, hookObserverMap.get(rawObj), currentListener);
// 最后一个参数要返回代理对象
return callback(valProxy, keyProxy, rawObj);
});
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const handler = {
get,
add,
delete: deleteFun,
has,
clear,
forEach,
forOf,
entries,
keys,
values,
[typeof Symbol === 'function' ? Symbol.iterator : '@@iterator']: forOf,
};
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
getObserver(rawObj).addListener(change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
listener.current(change);
listeners.forEach(lst => lst(change));
});
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const boundHandler = {};
Object.entries(handler).forEach(([id, val]) => {
boundHandler[id] = (...args: any[]) => {
return (val as any)(...args, hookObserver);
};
});
return new Proxy(rawObj, { ...boundHandler });
}

View File

@ -0,0 +1,197 @@
/*
* 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';
const COLLECTION_CHANGE = '_collectionChange';
export function createWeakMapProxy(
rawObj: Object,
hookObserver = true,
listener: { current: (...args) => any }
): Object {
let listeners: ((mutation) => {})[] = [];
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);
};
}
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), {
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 }));
},
});
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) : 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;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const handler = {
get,
set,
add,
delete: deleteFun,
clear,
has,
};
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
getObserver(rawObj).addListener(change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
listener.current(change);
listeners.forEach(lst => lst(change));
});
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const boundHandler = {};
Object.entries(handler).forEach(([id, val]) => {
boundHandler[id] = (...args: any[]) => {
return (val as any)(...args, hookObserver);
};
});
return new Proxy(rawObj, { ...boundHandler });
}

View File

@ -0,0 +1,133 @@
/*
* 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';
export function createWeakSetProxy<T extends object>(
rawObj: T,
hookObserver = true,
listener: { current: (...args) => any }
): ProxyHandler<T> {
let listeners: ((mutation) => {})[] = [];
let proxies = new WeakMap();
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);
};
};
}
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, hookObserverMap.get(rawObj), {
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 }));
},
});
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;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const handler = {
get,
add,
delete: deleteFun,
has,
};
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
getObserver(rawObj).addListener(change => {
if (!change.parents) change.parents = [];
change.parents.push(rawObj);
listener.current(change);
listeners.forEach(lst => lst(change));
});
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const boundHandler = {};
Object.entries(handler).forEach(([id, val]) => {
boundHandler[id] = (...args: any[]) => {
return (val as any)(...args, hookObserver);
};
});
return new Proxy(rawObj, { ...boundHandler });
}

View File

@ -38,6 +38,7 @@ import {
ACTION_QUEUED, ACTION_QUEUED,
INITIALIZED, INITIALIZED,
QUEUE_FINISHED, QUEUE_FINISHED,
QUEUE_PENDING,
STATE_CHANGE, STATE_CHANGE,
SUBSCRIBED, SUBSCRIBED,
UNSUBSCRIBED, UNSUBSCRIBED,
@ -62,7 +63,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, !config.options?.isReduxAdapter, listener);
proxyObj.$pending = false; proxyObj.$pending = false;
@ -76,16 +81,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 +121,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,20 +213,22 @@ 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 // 通过该方法执行store.$queue中的action
function tryNextAction(storeObj, proxyObj, config, plannedActions) { function tryNextAction(storeObj, proxyObj, config, plannedActions) {
if (!plannedActions.length) { if (!plannedActions.length) {
proxyObj.$pending = false; 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; return;
} }

View File

@ -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]> };

View File

@ -73,13 +73,13 @@ describe('测试store中的Map', () => {
function Parent(props) { function Parent(props) {
const userStore = useUserStore(); const userStore = useUserStore();
const addOnePerson = function() { const addOnePerson = function () {
userStore.addOnePerson(newPerson); userStore.addOnePerson(newPerson);
}; };
const delOnePerson = function() { const delOnePerson = function () {
userStore.delOnePerson(newPerson); userStore.delOnePerson(newPerson);
}; };
const clearPersons = function() { const clearPersons = function () {
userStore.clearPersons(); userStore.clearPersons();
}; };

View File

@ -64,7 +64,6 @@ describe('Mutation resolve', () => {
it('should resolve mutation same type types, same object', () => { it('should resolve mutation same type types, same object', () => {
const mutation = resolveMutation({ a: 1, b: 2 }, { a: 1, b: 2 }); const mutation = resolveMutation({ a: 1, b: 2 }, { a: 1, b: 2 });
console.log(mutation);
expect(mutation.mutation).toBe(false); expect(mutation.mutation).toBe(false);
}); });
@ -78,3 +77,17 @@ describe('Mutation resolve', () => {
expect(mutation.attributes.c.to).toBe(2); expect(mutation.attributes.c.to).toBe(2);
}); });
}); });
describe('Mutation collections', () => {
it('should resolve mutation of two sets', () => {
const values = [{ a: 1 }, { b: 2 }, { c: 3 }];
const source = new Set([values[0], values[1], values[2]]);
const target = new Set([values[0], values[1]]);
const mutation = resolveMutation(source, target);
expect(mutation.mutation).toBe(true);
});
});

View File

@ -17,7 +17,7 @@ import { createStore } from '@cloudsop/horizon/src/horizonx/store/StoreHandler';
import { watch } from '@cloudsop/horizon/src/horizonx/proxy/watch'; import { watch } from '@cloudsop/horizon/src/horizonx/proxy/watch';
describe('watch', () => { describe('watch', () => {
it('shouhld watch promitive state variable', async () => { it('shouhld watch primitive state variable', async () => {
const useStore = createStore({ const useStore = createStore({
state: { state: {
variable: 'x', variable: 'x',

View File

@ -0,0 +1,155 @@
import { createStore, useStore } from '@cloudsop/horizon/src/horizonx/store/StoreHandler';
import { describe, beforeEach, afterEach, it, expect } from '@jest/globals';
describe('Using deep variables', () => {
it('should listen to object variable change', () => {
let counter = 0;
const useTestStore = createStore({
state: { a: { b: { c: 1 } } },
});
const testStore = useTestStore();
testStore.$subscribe(() => {
counter++;
});
testStore.a.b.c = 0;
expect(counter).toBe(1);
});
it('should listen to deep variable change', () => {
let counter = 0;
const useTestStore = createStore({
state: { color: [{ a: 1 }, 255, 255] },
});
const testStore = useTestStore();
testStore.$subscribe(() => {
counter++;
});
for (let i = 0; i < 5; i++) {
testStore.color[0].a = i;
}
testStore.color = 'x';
expect(counter).toBe(6);
});
it('should use set', () => {
const useTestStore = createStore({
state: { data: new Set() },
});
const testStore = useTestStore();
const a = { a: true };
testStore.data.add(a);
expect(testStore.data.has(a)).toBe(true);
testStore.data.add(a);
testStore.data.add(a);
testStore.data.delete(a);
expect(testStore.data.has(a)).toBe(false);
testStore.data.add(a);
const values = Array.from(testStore.data.values());
expect(values.length).toBe(1);
let counter = 0;
testStore.$subscribe(mutation => {
counter++;
});
values.forEach(val => {
val.a = !val.a;
});
expect(testStore.data.has(a)).toBe(true);
expect(counter).toBe(1);
});
it('should use map', () => {
const useTestStore = createStore({
state: { data: new Map() },
});
const testStore = useTestStore();
const data = { key: { a: 1 }, value: { b: 2 } };
testStore.data.set(data.key, data.value);
const key = Array.from(testStore.data.keys())[0];
expect(testStore.data.has(key)).toBe(true);
testStore.data.set(data.key, data.value);
testStore.data.set(data.key, data.value);
testStore.data.delete(key);
expect(testStore.data.get(key)).toBe();
testStore.data.set(data.key, data.value);
const entries = Array.from(testStore.data.entries());
expect(entries.length).toBe(1);
let counter = 0;
testStore.$subscribe(mutation => {
counter++;
});
entries.forEach(([key, value]) => {
key.a++;
value.b++;
});
expect(counter).toBe(2);
});
it('should use weakSet', () => {
const useTestStore = createStore({
state: { data: new WeakSet() },
});
const testStore = useTestStore();
const a = { a: true };
testStore.data.add(a);
expect(testStore.data.has(a)).toBe(true);
testStore.data.add(a);
testStore.data.add(a);
testStore.data.delete(a);
expect(testStore.data.has(a)).toBe(false);
testStore.data.add(a);
expect(testStore.data.has(a)).toBe(true);
});
it('should use weakMap', () => {
const useTestStore = createStore({
state: { data: new WeakMap() },
});
const testStore = useTestStore();
const data = { key: { a: 1 }, value: { b: 2 } };
testStore.data.set(data.key, data.value);
let counter = 0;
testStore.$subscribe(mutation => {
counter++;
});
testStore.data.get(data.key).b++;
expect(counter).toBe(1);
});
});