Match-id-94f7e1b22b072209adf7133a84ee33f407a77222
This commit is contained in:
parent
e200fe1b4a
commit
dd99cba265
|
@ -45,6 +45,7 @@ module.exports = {
|
|||
},
|
||||
globals: {
|
||||
isDev: true,
|
||||
isTest: true,
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
区分是否开发者模式
|
||||
*/
|
||||
declare var isDev: boolean;
|
||||
declare var isTest: boolean;
|
||||
declare const __VERSION__: string;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// The two constants must be the same as those in horizon.
|
||||
export const FunctionComponent = 'FunctionComponent';
|
||||
export const ClassComponent = 'ClassComponent';
|
||||
|
||||
export const OBSERVER_KEY = Symbol('_horizonObserver');
|
||||
|
|
|
@ -10,7 +10,7 @@ export class HooklessObserver implements IObserver {
|
|||
|
||||
listeners:(() => void)[] = [];
|
||||
|
||||
useProp(key: string): void {
|
||||
useProp(key: string | symbol): void {
|
||||
}
|
||||
|
||||
addListener(listener: () => void) {
|
||||
|
@ -21,7 +21,7 @@ export class HooklessObserver implements IObserver {
|
|||
this.listeners = this.listeners.filter(item => item != listener);
|
||||
}
|
||||
|
||||
setProp(key: string): void {
|
||||
setProp(key: string | symbol): void {
|
||||
this.triggerChangeListeners();
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ export class Observer implements IObserver {
|
|||
|
||||
listeners:(()=>void)[] = [];
|
||||
|
||||
useProp(key: string): void {
|
||||
useProp(key: string | symbol): void {
|
||||
const processingVNode = getProcessingVNode();
|
||||
if (processingVNode === null || !processingVNode.observers) {
|
||||
return;
|
||||
|
@ -50,7 +50,7 @@ export class Observer implements IObserver {
|
|||
this.listeners = this.listeners.filter(item => item != listener);
|
||||
}
|
||||
|
||||
setProp(key: string): void {
|
||||
setProp(key: string | symbol): void {
|
||||
const vNodes = this.keyVNodes.get(key);
|
||||
vNodes?.forEach((vNode: VNode) => {
|
||||
if (vNode.isStoreChange) {
|
||||
|
@ -85,7 +85,7 @@ export class Observer implements IObserver {
|
|||
}
|
||||
}
|
||||
|
||||
clearByVNode(vNode: Vnode): void {
|
||||
clearByVNode(vNode: VNode): void {
|
||||
const keys = this.vNodeKeys.get(vNode);
|
||||
if (keys) {
|
||||
keys.forEach((key: any) => {
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import {createObjectProxy} from './handlers/ObjectProxyHandler';
|
||||
import {Observer} from './Observer';
|
||||
import {HooklessObserver} from './HooklessObserver';
|
||||
import {isArray, isCollection, isObject} from '../CommonUtils';
|
||||
import {createArrayProxy} from './handlers/ArrayProxyHandler';
|
||||
import {createCollectionProxy} from './handlers/CollectionProxyHandler';
|
||||
import { createObjectProxy } from './handlers/ObjectProxyHandler';
|
||||
import { Observer } from './Observer';
|
||||
import { HooklessObserver } from './HooklessObserver';
|
||||
import { isArray, isCollection, isObject } from '../CommonUtils';
|
||||
import { createArrayProxy } from './handlers/ArrayProxyHandler';
|
||||
import { createCollectionProxy } from './handlers/CollectionProxyHandler';
|
||||
import { IObserver } from '../types';
|
||||
|
||||
const OBSERVER_KEY = Symbol('_horizonObserver');
|
||||
import { OBSERVER_KEY } from '../Constants';
|
||||
|
||||
const proxyMap = new WeakMap();
|
||||
|
||||
|
@ -29,7 +28,7 @@ export function createProxy(rawObj: any, hookObserver = true): any {
|
|||
}
|
||||
|
||||
// 创建Observer
|
||||
let observer:IObserver = getObserver(rawObj);
|
||||
let observer: IObserver = getObserver(rawObj);
|
||||
if (!observer) {
|
||||
observer = hookObserver ? new Observer() : new HooklessObserver();
|
||||
rawObj[OBSERVER_KEY] = observer;
|
||||
|
@ -59,4 +58,3 @@ export function createProxy(rawObj: any, hookObserver = true): any {
|
|||
export function getObserver(rawObj: any): Observer {
|
||||
return rawObj[OBSERVER_KEY];
|
||||
}
|
||||
|
||||
|
|
|
@ -26,14 +26,14 @@ function set(rawObj: any[], key: string, value: any, receiver: any) {
|
|||
const ret = Reflect.set(rawObj, key, newValue, receiver);
|
||||
|
||||
const newLength = rawObj.length;
|
||||
const tracker = getObserver(rawObj);
|
||||
const observer = getObserver(rawObj);
|
||||
|
||||
if (!isSame(newValue, oldValue)) {
|
||||
tracker.setProp(key);
|
||||
observer.setProp(key);
|
||||
}
|
||||
|
||||
if (oldLength !== newLength) {
|
||||
tracker.setProp('length');
|
||||
observer.setProp('length');
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
@ -40,8 +40,8 @@ function get(rawObj: { size: number }, key: any, receiver: any): any {
|
|||
}
|
||||
|
||||
function getFun(rawObj: { get: (key: any) => any }, key: any) {
|
||||
const tracker = getObserver(rawObj);
|
||||
tracker.useProp(key);
|
||||
const observer = getObserver(rawObj);
|
||||
observer.useProp(key);
|
||||
|
||||
const value = rawObj.get(key);
|
||||
// 对于value也需要进一步代理
|
||||
|
@ -60,14 +60,14 @@ function set(
|
|||
const newValue = value;
|
||||
rawObj.set(key, newValue);
|
||||
const valChange = !isSame(newValue, oldValue);
|
||||
const tracker = getObserver(rawObj);
|
||||
const observer = getObserver(rawObj);
|
||||
|
||||
if (valChange || !rawObj.has(key)) {
|
||||
tracker.setProp(COLLECTION_CHANGE);
|
||||
observer.setProp(COLLECTION_CHANGE);
|
||||
}
|
||||
|
||||
if (valChange) {
|
||||
tracker.setProp(key);
|
||||
observer.setProp(key);
|
||||
}
|
||||
|
||||
return rawObj;
|
||||
|
@ -78,17 +78,17 @@ function add(rawObj: { add: (any) => void; set: (string, any) => any; has: (any)
|
|||
if (!rawObj.has(value)) {
|
||||
rawObj.add(value);
|
||||
|
||||
const tracker = getObserver(rawObj);
|
||||
tracker.setProp(value);
|
||||
tracker.setProp(COLLECTION_CHANGE);
|
||||
const observer = getObserver(rawObj);
|
||||
observer.setProp(value);
|
||||
observer.setProp(COLLECTION_CHANGE);
|
||||
}
|
||||
|
||||
return rawObj;
|
||||
}
|
||||
|
||||
function has(rawObj: { has: (string) => boolean }, key: any): boolean {
|
||||
const tracker = getObserver(rawObj);
|
||||
tracker.useProp(key);
|
||||
const observer = getObserver(rawObj);
|
||||
observer.useProp(key);
|
||||
|
||||
return rawObj.has(key);
|
||||
}
|
||||
|
@ -98,8 +98,8 @@ function clear(rawObj: { size: number; clear: () => void }) {
|
|||
rawObj.clear();
|
||||
|
||||
if (oldSize > 0) {
|
||||
const tracker = getObserver(rawObj);
|
||||
tracker.allChange();
|
||||
const observer = getObserver(rawObj);
|
||||
observer.allChange();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,9 +107,9 @@ function deleteFun(rawObj: { has: (key: any) => boolean; delete: (key: any) => v
|
|||
if (rawObj.has(key)) {
|
||||
rawObj.delete(key);
|
||||
|
||||
const tracker = getObserver(rawObj);
|
||||
tracker.setProp(key);
|
||||
tracker.setProp(COLLECTION_CHANGE);
|
||||
const observer = getObserver(rawObj);
|
||||
observer.setProp(key);
|
||||
observer.setProp(COLLECTION_CHANGE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -118,8 +118,8 @@ function deleteFun(rawObj: { has: (key: any) => boolean; delete: (key: any) => v
|
|||
}
|
||||
|
||||
function size(rawObj: { size: number }) {
|
||||
const tracker = getObserver(rawObj);
|
||||
tracker.useProp(COLLECTION_CHANGE);
|
||||
const observer = getObserver(rawObj);
|
||||
observer.useProp(COLLECTION_CHANGE);
|
||||
return rawObj.size;
|
||||
}
|
||||
|
||||
|
@ -148,8 +148,8 @@ function forEach(
|
|||
rawObj: { forEach: (callback: (value: any, key: any) => void) => void },
|
||||
callback: (valProxy: any, keyProxy: any, rawObj: any) => void
|
||||
) {
|
||||
const tracker = getObserver(rawObj);
|
||||
tracker.useProp(COLLECTION_CHANGE);
|
||||
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));
|
||||
|
@ -159,9 +159,9 @@ function forEach(
|
|||
}
|
||||
|
||||
function wrapIterator(rawObj: Object, rawIt: { next: () => { value: any; done: boolean } }, isPair = false) {
|
||||
const tracker = getObserver(rawObj);
|
||||
const observer = getObserver(rawObj);
|
||||
const hookObserver = hookObserverMap.get(rawObj);
|
||||
tracker.useProp(COLLECTION_CHANGE);
|
||||
observer.useProp(COLLECTION_CHANGE);
|
||||
|
||||
return {
|
||||
next() {
|
||||
|
@ -170,7 +170,7 @@ function wrapIterator(rawObj: Object, rawIt: { next: () => { value: any; done: b
|
|||
return { value: createProxy(value, hookObserver), done };
|
||||
}
|
||||
|
||||
tracker.useProp(COLLECTION_CHANGE);
|
||||
observer.useProp(COLLECTION_CHANGE);
|
||||
|
||||
let newVal;
|
||||
if (isPair) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { isSame } from '../../CommonUtils';
|
||||
import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler';
|
||||
import { OBSERVER_KEY } from '../../Constants';
|
||||
|
||||
export function createObjectProxy<T extends object>(rawObj: T): ProxyHandler<T> {
|
||||
const proxy = new Proxy(rawObj, {
|
||||
|
@ -10,7 +11,12 @@ export function createObjectProxy<T extends object>(rawObj: T): ProxyHandler<T>
|
|||
return proxy;
|
||||
}
|
||||
|
||||
export function get(rawObj: object, key: string, receiver: any): any {
|
||||
export 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 === 'addListener') {
|
||||
|
@ -25,10 +31,15 @@ export function get(rawObj: object, key: string, receiver: any): any {
|
|||
|
||||
const value = Reflect.get(rawObj, key, receiver);
|
||||
|
||||
// 对于value也需要进一步代理
|
||||
const valProxy = createProxy(value, hookObserverMap.get(rawObj));
|
||||
// 对于prototype不做代理
|
||||
if (key !== 'prototype') {
|
||||
// 对于value也需要进一步代理
|
||||
const valProxy = createProxy(value, hookObserverMap.get(rawObj));
|
||||
|
||||
return valProxy;
|
||||
return valProxy;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function set(rawObj: object, key: string, value: any, receiver: any): boolean {
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
export interface IObserver {
|
||||
|
||||
useProp: (key: string) => void;
|
||||
useProp: (key: string | symbol) => void;
|
||||
|
||||
addListener: (listener: () => void) => void;
|
||||
|
||||
removeListener: (listener: () => void) => void;
|
||||
|
||||
setProp: (key: string) => void;
|
||||
setProp: (key: string | symbol) => void;
|
||||
|
||||
triggerChangeListeners: () => void;
|
||||
|
||||
triggerUpdate: (vNode: any) => void;
|
||||
|
||||
allChange: () => void;
|
||||
|
||||
|
||||
clearByVNode: (vNode: any) => void;
|
||||
}
|
||||
|
||||
type RemoveFirstFromTuple<T extends any[]> =
|
||||
type RemoveFirstFromTuple<T extends any[]> =
|
||||
T['length'] extends 0 ? [] :
|
||||
(((...b: T) => void) extends (a, ...b: infer I) => void ? I : [])
|
||||
|
||||
|
@ -36,7 +36,7 @@ type ComputedValues<S extends object,C extends UserComputedValues<S>> = { [K in
|
|||
type PostponedAction = (state: object, ...args: any[]) => Promise<any>;
|
||||
type PostponedActions = { [key:string]: PostponedAction }
|
||||
|
||||
export type StoreHandler<S extends object,A extends UserActions<S>,C extends UserComputedValues<S>> =
|
||||
export type StoreHandler<S extends object,A extends UserActions<S>,C extends UserComputedValues<S>> =
|
||||
{$subscribe: ((listener: () => void) => void),
|
||||
$unsubscribe: ((listener: () => void) => void),
|
||||
$state: S,
|
||||
|
@ -78,4 +78,4 @@ type ReduxMiddleware = (store:ReduxStoreHandler, extraArgument?:any) =>
|
|||
(action:(
|
||||
ReduxAction|
|
||||
((dispatch:(action:ReduxAction)=>void,store:ReduxStoreHandler,extraArgument?:any)=>any)
|
||||
)) => ReduxStoreHandler
|
||||
)) => ReduxStoreHandler
|
||||
|
|
|
@ -15,7 +15,7 @@ import {updateShouldUpdateOfTree} from './vnode/VNodeShouldUpdate';
|
|||
import {BuildErrored, setBuildResult} from './GlobalVar';
|
||||
|
||||
function consoleError(error: any): void {
|
||||
if (isDev) {
|
||||
if (isTest) {
|
||||
// 只打印message为了让测试用例能pass
|
||||
console['error']('The codes throw the error: ' + error.message);
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
import * as Horizon from '@cloudsop/horizon/index.ts';
|
||||
import { clearStore, createStore, useStore } from '../../../../libs/horizon/src/horizonx/store/StoreHandler';
|
||||
import { OBSERVER_KEY } from '../../../../libs/horizon/src/horizonx/Constants';
|
||||
import { App, Text, triggerClickEvent } from '../../jest/commonComponents';
|
||||
|
||||
describe('测试对store.state对象进行深度克隆', () => {
|
||||
const { unmountComponentAtNode } = Horizon;
|
||||
let container = null;
|
||||
beforeEach(() => {
|
||||
// 创建一个 DOM 元素作为渲染目标
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
createStore({
|
||||
id: 'user',
|
||||
state: {
|
||||
type: 'bing dun dun',
|
||||
persons: [
|
||||
{ name: 'p1', age: 1 },
|
||||
{ name: 'p2', age: 2 },
|
||||
],
|
||||
},
|
||||
actions: {
|
||||
addOnePerson: (state, person) => {
|
||||
state.persons.push(person);
|
||||
},
|
||||
delOnePerson: state => {
|
||||
state.persons.pop();
|
||||
},
|
||||
clearPersons: state => {
|
||||
state.persons = null;
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// 退出时进行清理
|
||||
unmountComponentAtNode(container);
|
||||
container.remove();
|
||||
container = null;
|
||||
|
||||
clearStore('user');
|
||||
});
|
||||
|
||||
const newPerson = { name: 'p3', age: 3 };
|
||||
|
||||
function Parent({ children }) {
|
||||
const userStore = useStore('user');
|
||||
const addOnePerson = function() {
|
||||
userStore.addOnePerson(newPerson);
|
||||
};
|
||||
const delOnePerson = function() {
|
||||
userStore.delOnePerson();
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<button id={'addBtn'} onClick={addOnePerson}>
|
||||
add person
|
||||
</button>
|
||||
<button id={'delBtn'} onClick={delOnePerson}>
|
||||
delete person
|
||||
</button>
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
it('The observer object of symbol (\'_horizonObserver\') cannot be accessed to from Proxy', () => {
|
||||
let userStore = null;
|
||||
function Child(props) {
|
||||
userStore = useStore('user');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text id={'hasPerson'} text={`has new person: ${userStore.persons.length}`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Horizon.render(<App parent={Parent} child={Child} />, container);
|
||||
|
||||
// The observer object of symbol ('_horizonObserver') cannot be accessed to from Proxy prevent errors caused by clonedeep.
|
||||
expect(userStore.persons[0][OBSERVER_KEY]).toBe(undefined);
|
||||
});
|
||||
|
||||
it('The observer object of symbol (\'_horizonObserver\') cannot be accessed to from Proxy', () => {
|
||||
let userStore = null;
|
||||
function Child(props) {
|
||||
userStore = useStore('user');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text id={'hasPerson'} text={`has new person: ${userStore.persons.length}`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Horizon.render(<App parent={Parent} child={Child} />, container);
|
||||
|
||||
// NO throw this Exception, TypeError: 'get' on proxy: property 'prototype' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value
|
||||
const proxyObj = userStore.persons[0].constructor;
|
||||
expect(proxyObj.prototype !== undefined).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
|
@ -5,6 +5,7 @@ import { getLogUtils } from './testUtils';
|
|||
//failOnConsole();
|
||||
const LogUtils = getLogUtils();
|
||||
global.isDev = process.env.NODE_ENV === 'development';
|
||||
global.isTest = true;
|
||||
global.container = null;
|
||||
global.beforeEach(() => {
|
||||
LogUtils.clear();
|
||||
|
|
Loading…
Reference in New Issue