From b20293076caec6184cab54f64d69f18c33bda9da Mon Sep 17 00:00:00 2001 From: * <*> Date: Wed, 31 Aug 2022 16:41:25 +0800 Subject: [PATCH] Match-id-0c7552eff73b3193eb58fd25a2dfe13e09f93e41 --- libs/horizon/index.ts | 3 + libs/horizon/src/horizonx/proxy/Observer.ts | 2 + .../proxy/handlers/ArrayProxyHandler.ts | 20 ++ .../proxy/handlers/CollectionProxyHandler.ts | 18 ++ .../proxy/handlers/ObjectProxyHandler.ts | 17 ++ .../src/horizonx/store/StoreHandler.ts | 2 +- .../src/renderer/render/BaseComponent.ts | 2 +- .../multipleStores.test.tsx | 186 ------------------ 8 files changed, 62 insertions(+), 188 deletions(-) delete mode 100644 scripts/__tests__/HorizonXText/StoreFunctionality/multipleStores.test.tsx diff --git a/libs/horizon/index.ts b/libs/horizon/index.ts index 9e29e71d..f8035375 100644 --- a/libs/horizon/index.ts +++ b/libs/horizon/index.ts @@ -33,6 +33,7 @@ import { runAsyncEffects } from './src/renderer/submit/HookEffectHandler'; import { createStore, useStore, clearStore } from './src/horizonx/store/StoreHandler'; import * as reduxAdapter from './src/horizonx/adapters/redux'; +import { watch } from './src/horizonx/proxy/watch'; // act用于测试,作用是:如果fun触发了刷新(包含了异步刷新),可以保证在act后面的代码是在刷新完成后才执行。 const act = fun => { @@ -85,6 +86,7 @@ const Horizon = { useStore, clearStore, reduxAdapter, + watch }; export const version = __VERSION__; @@ -125,6 +127,7 @@ export { useStore, clearStore, reduxAdapter, + watch }; export default Horizon; diff --git a/libs/horizon/src/horizonx/proxy/Observer.ts b/libs/horizon/src/horizonx/proxy/Observer.ts index d93c7a05..b00e942e 100644 --- a/libs/horizon/src/horizonx/proxy/Observer.ts +++ b/libs/horizon/src/horizonx/proxy/Observer.ts @@ -33,6 +33,8 @@ export class Observer implements IObserver { listeners:(()=>void)[] = []; + watchers={} as {[key:string]:((key:string, oldValue:any, newValue:any)=>void)[]} + useProp(key: string | symbol): void { const processingVNode = getProcessingVNode(); if (processingVNode === null || !processingVNode.observers) { diff --git a/libs/horizon/src/horizonx/proxy/handlers/ArrayProxyHandler.ts b/libs/horizon/src/horizonx/proxy/handlers/ArrayProxyHandler.ts index a6812bde..dd3fdb0b 100644 --- a/libs/horizon/src/horizonx/proxy/handlers/ArrayProxyHandler.ts +++ b/libs/horizon/src/horizonx/proxy/handlers/ArrayProxyHandler.ts @@ -12,6 +12,20 @@ export function createArrayProxy(rawObj: any[]): any[] { } 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); } @@ -29,6 +43,12 @@ function set(rawObj: any[], key: string, value: any, receiver: any) { const observer = getObserver(rawObj); if (!isSame(newValue, oldValue)) { + if(observer.watchers?.[key]){ + observer.watchers[key].forEach(cb => { + cb(key, oldValue, newValue); + }); + } + observer.setProp(key); } diff --git a/libs/horizon/src/horizonx/proxy/handlers/CollectionProxyHandler.ts b/libs/horizon/src/horizonx/proxy/handlers/CollectionProxyHandler.ts index bd044b44..15aaa682 100644 --- a/libs/horizon/src/horizonx/proxy/handlers/CollectionProxyHandler.ts +++ b/libs/horizon/src/horizonx/proxy/handlers/CollectionProxyHandler.ts @@ -34,6 +34,18 @@ function get(rawObj: { size: number }, key: any, receiver: any): any { } 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); @@ -67,6 +79,12 @@ function set( } if (valChange) { + if(observer.watchers?.[key]){ + observer.watchers[key].forEach(cb => { + cb(key, oldValue, newValue); + }); + } + observer.setProp(key); } diff --git a/libs/horizon/src/horizonx/proxy/handlers/ObjectProxyHandler.ts b/libs/horizon/src/horizonx/proxy/handlers/ObjectProxyHandler.ts index 4dc27a0b..29ab2658 100644 --- a/libs/horizon/src/horizonx/proxy/handlers/ObjectProxyHandler.ts +++ b/libs/horizon/src/horizonx/proxy/handlers/ObjectProxyHandler.ts @@ -19,6 +19,18 @@ export function get(rawObj: object, key: string | symbol, receiver: any, singleL 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); } @@ -54,6 +66,11 @@ export function set(rawObj: object, key: string, value: any, receiver: any): boo const ret = Reflect.set(rawObj, key, newValue, receiver); if (!isSame(newValue, oldValue)) { + if(observer.watchers?.[key]){ + observer.watchers[key].forEach(cb => { + cb(key, oldValue, newValue); + }); + } observer.setProp(key); } diff --git a/libs/horizon/src/horizonx/store/StoreHandler.ts b/libs/horizon/src/horizonx/store/StoreHandler.ts index fc3ed6c9..00d4695e 100644 --- a/libs/horizon/src/horizonx/store/StoreHandler.ts +++ b/libs/horizon/src/horizonx/store/StoreHandler.ts @@ -220,7 +220,7 @@ function hookStore() { if (!processingVNode) { return; } - + if (!processingVNode.observers) { processingVNode.observers = new Set(); } diff --git a/libs/horizon/src/renderer/render/BaseComponent.ts b/libs/horizon/src/renderer/render/BaseComponent.ts index 46fd1e0a..4192e100 100644 --- a/libs/horizon/src/renderer/render/BaseComponent.ts +++ b/libs/horizon/src/renderer/render/BaseComponent.ts @@ -57,7 +57,7 @@ export function captureVNode(processing: VNode): VNode | null { setProcessingVNode(processing); - clearVNodeObservers(processing); + if(processing.observers) clearVNodeObservers(processing); const child = component.captureRender(processing, shouldUpdate); setProcessingVNode(null); diff --git a/scripts/__tests__/HorizonXText/StoreFunctionality/multipleStores.test.tsx b/scripts/__tests__/HorizonXText/StoreFunctionality/multipleStores.test.tsx deleted file mode 100644 index a9b933d9..00000000 --- a/scripts/__tests__/HorizonXText/StoreFunctionality/multipleStores.test.tsx +++ /dev/null @@ -1,186 +0,0 @@ -//@ts-ignore -import Horizon, { createStore } from '@cloudsop/horizon/index.ts'; -import { triggerClickEvent } from '../../jest/commonComponents'; -import { describe, beforeEach, afterEach, it, expect } from '@jest/globals'; - -const { unmountComponentAtNode } = Horizon; - -const useStore1 = createStore({ - state:{ counter:1 }, - actions:{ - add:(state)=>state.counter++, - reset: (state)=>state.counter=1 - } -}) - -const useStore2 = createStore({ - state:{ counter2:1 }, - actions:{ - add2:(state)=>state.counter2++, - reset: (state)=>state.counter2=1 - } -}) - -describe('Using multiple stores', () => { - let container: HTMLElement | null = null; - - const BUTTON_ID = 'btn'; - const BUTTON_ID2 = 'btn2'; - const RESULT_ID = 'result'; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - useStore1().reset(); - useStore2().reset(); - }); - - afterEach(() => { - unmountComponentAtNode(container); - container?.remove(); - container = null; - }); - - it('Should use multiple stores in class component', () => { - class App extends Horizon.Component{ - render(){ - const {counter,add} = useStore1(); - const store2 = useStore2(); - const {counter2, add2} = store2; - - return ( -
- - -

{counter*counter2}

-
- ) - } - } - - Horizon.render(, container); - - Horizon.act(() => { - triggerClickEvent(container, BUTTON_ID); - triggerClickEvent(container, BUTTON_ID); - triggerClickEvent(container, BUTTON_ID2); - triggerClickEvent(container, BUTTON_ID2); - }); - - expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('9'); - }); - - it('Should use use stores in cycles and multiple methods', () => { - interface App { - store:any, - store2:any - } - class App extends Horizon.Component{ - constructor(){ - super(); - this.store = useStore1(); - this.store2 = useStore2() - } - - render(){ - const {counter,add} = useStore1(); - const store2 = useStore2(); - const {counter2, add2} = store2; - - for(let i=0; i<100; i++){ - const {counter,add} = useStore1(); - const store2 = useStore2(); - const {counter2, add2} = store2; - } - - return ( -
- - -

{counter*counter2}

-
- ) - } - } - - Horizon.render(, container); - - Horizon.act(() => { - triggerClickEvent(container, BUTTON_ID); - triggerClickEvent(container, BUTTON_ID); - triggerClickEvent(container, BUTTON_ID2); - triggerClickEvent(container, BUTTON_ID2); - }); - - expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('9'); - }); - - it('Should use multiple stores in function component', () => { - function App() { - const {counter,add} = useStore1(); - const store2 = useStore2(); - const {counter2, add2} = store2; - - return ( -
- - -

{counter*counter2}

-
- ); - } - - Horizon.render(, container); - - Horizon.act(() => { - triggerClickEvent(container, BUTTON_ID); - triggerClickEvent(container, BUTTON_ID); - triggerClickEvent(container, BUTTON_ID2); - triggerClickEvent(container, BUTTON_ID2); - }); - - expect(document.getElementById(RESULT_ID)?.innerHTML).toBe('9'); - }); -}); \ No newline at end of file