Match-id-94f7e1b22b072209adf7133a84ee33f407a77222

This commit is contained in:
* 2022-07-12 17:20:49 +08:00 committed by *
parent e200fe1b4a
commit dd99cba265
13 changed files with 171 additions and 51 deletions

View File

@ -45,6 +45,7 @@ module.exports = {
},
globals: {
isDev: true,
isTest: true,
},
overrides: [
{

View File

@ -2,4 +2,5 @@
*/
declare var isDev: boolean;
declare var isTest: boolean;
declare const __VERSION__: string;

View File

@ -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');

View File

@ -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();
}

View File

@ -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) => {

View File

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

View File

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

View File

@ -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) {

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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();
});
});

View File

@ -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();