diff --git a/libs/horizon/src/horizonx/proxy/HooklessObserver.ts b/libs/horizon/src/horizonx/proxy/HooklessObserver.ts index 4192774d..9ea8937f 100644 --- a/libs/horizon/src/horizonx/proxy/HooklessObserver.ts +++ b/libs/horizon/src/horizonx/proxy/HooklessObserver.ts @@ -8,9 +8,7 @@ import {IObserver} from '../types'; */ export class HooklessObserver implements IObserver { - listeners = []; - vNodeKeys: null; - keyVNodes: null; + listeners:(() => void)[] = []; useProp(key: string): void { } diff --git a/libs/horizon/src/horizonx/proxy/Observer.ts b/libs/horizon/src/horizonx/proxy/Observer.ts index 588f3d17..be24d1c2 100644 --- a/libs/horizon/src/horizonx/proxy/Observer.ts +++ b/libs/horizon/src/horizonx/proxy/Observer.ts @@ -9,11 +9,12 @@ import { VNode } from '../../renderer/vnode/VNode'; import { IObserver } from '../types'; export class Observer implements IObserver { + vNodeKeys = new WeakMap(); keyVNodes = new Map(); - listeners = []; + listeners:(()=>void)[] = []; useProp(key: string): void { const processingVNode = getProcessingVNode(); @@ -84,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) => { diff --git a/libs/horizon/src/horizonx/proxy/ProxyHandler.ts b/libs/horizon/src/horizonx/proxy/ProxyHandler.ts index b74fd965..f2051b9d 100644 --- a/libs/horizon/src/horizonx/proxy/ProxyHandler.ts +++ b/libs/horizon/src/horizonx/proxy/ProxyHandler.ts @@ -1,9 +1,10 @@ -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'); @@ -28,7 +29,7 @@ export function createProxy(rawObj: any, hookObserver = true): any { } // 创建Observer - let observer = getObserver(rawObj); + let observer:IObserver = getObserver(rawObj); if (!observer) { observer = hookObserver ? new Observer() : new HooklessObserver(); rawObj[OBSERVER_KEY] = observer; diff --git a/libs/horizon/src/horizonx/store/StoreHandler.ts b/libs/horizon/src/horizonx/store/StoreHandler.ts index 5c9c48f7..9b746c96 100644 --- a/libs/horizon/src/horizonx/store/StoreHandler.ts +++ b/libs/horizon/src/horizonx/store/StoreHandler.ts @@ -3,77 +3,71 @@ import { useEffect, useRef } from '../../renderer/hooks/HookExternal'; import { getProcessingVNode } from '../../renderer/GlobalVar'; import { createProxy } from '../proxy/ProxyHandler'; import readonlyProxy from '../proxy/readonlyProxy'; -import { StoreHandler, StoreConfig, UserActions, UserComputedValues } from '../types'; +import { StoreHandler, StoreConfig, UserActions, UserComputedValues, StoreActions, ComputedValues, ActionFunction, Action, QueuedStoreActions } from '../types'; import { Observer } from '../proxy/Observer'; import { FunctionComponent, ClassComponent } from '../Constants'; -const storeMap = new Map(); +const storeMap = new Map>(); function isPromise(obj: any): boolean { return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function'; } -export function createStore, C extends UserComputedValues>( - config: StoreConfig -): () => StoreHandler { - let handler: any = { - $subscribe: null, - $unsubscribe: null, - $state: null, - $config: config, - $queue: null, - $actions: {}, - $computed: {}, - }; +type PlannedAction>={ + action:string, + payload: any[], + resolve: ReturnType +} - const obj = { - ...config, - config, - plannedActions: [], - rawState: config.state, - rawActions: { ...config.actions }, - }; +export function createStore,C extends UserComputedValues>(config: StoreConfig): () => StoreHandler { + //create a local shalow copy to ensure consistency (if user would change the config object after store creation) + config = { + id:config.id, + options: config.options, + state: config.state, + actions: config.actions ? {...config.actions}:undefined, + computed: config.computed ? {...config.computed}:undefined + } // 校验 - if (Object.prototype.toString.call(obj) !== '[object Object]') { + if (Object.prototype.toString.call(config) !== '[object Object]') { throw new Error('store obj must be pure object'); } - const proxyObj = createProxy(obj.state, !obj.options?.suppressHooks); + const proxyObj = createProxy(config.state, !config.options?.suppressHooks); + proxyObj.$pending = false; - handler.$subscribe = listener => { + + const $subscribe = (listener) => { proxyObj.addListener(listener); }; - handler.$unsubscribe = listener => { + + const $unsubscribe = (listener) => { proxyObj.removeListener(listener); }; - obj.rawState = obj.state; - obj.state = proxyObj; - handler.$state = obj.state; - - handler.$config = obj.config; - - // handles.$reset = ()=>{ - // const keys = Object.keys(obj.state); - // Object.entries(obj.defaultState).forEach(([key,value])=>{ - // obj.state[key]=value; - // }); - // keys.forEach(key => { - // if(!obj.defaultState[key]){ - // delete obj.state[key]; - // } - // }); - // }; + const plannedActions:PlannedAction>[] = []; + const $actions:Partial>={} + const $queue:Partial> = {}; + const $computed:Partial>={} + const handler = { + $subscribe, + $unsubscribe, + $actions:$actions as StoreActions, + $state:proxyObj, + $computed: $computed as ComputedValues, + $config:config, + $queue: $queue as QueuedStoreActions, + } as StoreHandler; function tryNextAction() { - if (!obj.plannedActions.length) { + if (!plannedActions.length) { proxyObj.$pending = false; return; } - const nextAction = obj.plannedActions.shift(); - const result = obj.rawActions[nextAction.action].bind(self, obj.state)(...nextAction.payload); + const nextAction = plannedActions.shift()!; + const result = config.actions ? config.actions[nextAction.action].bind(self, proxyObj)(...nextAction.payload) : undefined; if (isPromise(result)) { result.then(value => { @@ -87,62 +81,64 @@ export function createStore, C extend } // 包装actions - Object.keys(obj.actions).forEach(key => { - (obj.actions as any)[key] = handler[key] = function Wrapped(...payload) { - return obj.rawActions[key].bind(self, obj.state)(...payload); - }; - }); - - handler.$queue = {}; - Object.keys(obj.rawActions).forEach(action => { - handler.$queue[action] = (...payload) => { - return new Promise(resolve => { - if (!proxyObj.$pending) { - proxyObj.$pending = true; - const result = obj.rawActions[action].bind(self, obj.state)(...payload); - - if (isPromise(result)) { - result.then(value => { - resolve(value); + if(config.actions){ + Object.keys(config.actions).forEach(action => { + ($queue as any)[action] = (...payload) => { + return new Promise((resolve) => { + if (!proxyObj.$pending) { + proxyObj.$pending = true; + const result = config.actions![action].bind(self, proxyObj)(...payload); + + if (isPromise(result)) { + result.then((value) => { + resolve(value); + tryNextAction(); + }); + } else { + resolve(result); tryNextAction(); - }); + } } else { - resolve(result); - tryNextAction(); + plannedActions.push({ + action, + payload, + resolve + }); } - } else { - obj.plannedActions.push({ - action, - payload, - resolve, - }); - } + }); + }; + + ($actions as any)[action] = function Wrapped(...payload) { + return config.actions![action].bind(self, proxyObj)(...payload); + }; + + // direct store access + Object.defineProperty(handler, action, { + writable: false, + value: $actions[action] + }); + }); + } + + if (config.computed) { + Object.keys(config.computed).forEach((key) => { + ($computed as any)[key] = config.computed![key].bind(handler, readonlyProxy(proxyObj)); + + // direct store access + Object.defineProperty(handler, key, { + get: $computed[key] as ()=>any + }); + }); + } + + // direct state access + if(config.state){ + Object.keys(config.state).forEach(key => { + Object.defineProperty(handler, key, { + get: () => proxyObj[key] }); - }; - }); - - handler.$actions = obj.actions; - - // native getters - Object.keys(obj.state).forEach(key => { - Object.defineProperty(handler, key, { - get: () => obj.state[key], - }); - }); - - // computed - if (obj.computed) { - Object.keys(obj.computed).forEach(key => { - // supports access through attributes - Object.defineProperty(handler, key, { - get: obj.computed[key].bind(handler, readonlyProxy(obj.state)), - }); - - // supports access through function - (obj.computed as any)[key] = obj.computed[key].bind(handler, readonlyProxy(obj.state)); }); } - handler.$computed = obj.computed || {}; if (config.id) { storeMap.set(config.id, handler); @@ -213,13 +209,11 @@ export function useStore, C extends U ): StoreHandler { const storeObj = storeMap.get(id); - if (!storeObj.$config.options?.suppressHooks) { - hookStore(); - } + if (storeObj && !storeObj.$config.options?.suppressHooks) hookStore(); - return storeObj; + return storeObj as StoreHandler; } -export function clearStore(id: string): void { +export function clearStore(id:string):void { storeMap.delete(id); -} +} \ No newline at end of file diff --git a/libs/horizon/src/horizonx/types.d.ts b/libs/horizon/src/horizonx/types.d.ts index e0816e89..3fed3afe 100644 --- a/libs/horizon/src/horizonx/types.d.ts +++ b/libs/horizon/src/horizonx/types.d.ts @@ -1,9 +1,4 @@ export interface IObserver { - vNodeKeys: WeakMap; - - keyVNodes: Map; - - listeners: (() => void)[]; useProp: (key: string) => void; @@ -18,58 +13,69 @@ export interface IObserver { triggerUpdate: (vNode: any) => void; allChange: () => void; - + clearByVNode: (vNode: any) => void; } -type UserActions = { [K: string]: StateFunction }; -type UserComputedValues = { [K: string]: StateFunction }; +type RemoveFirstFromTuple = + T['length'] extends 0 ? [] : + (((...b: T) => void) extends (a, ...b: infer I) => void ? I : []) -type StateFunction = (state: S, ...args: any[]) => any; -type StoreActions> = { [K in keyof A]: A[K] }; -type ComputedValues> = { [K in keyof C]: C[K] }; + +type UserActions = { [K:string]: ActionFunction }; +type UserComputedValues = { [K:string]: ComputedFunction }; + +type ActionFunction = (state: S, ...args: any[]) => any; +type ComputedFunction = (state: S) => any; +type Action> = (...args:RemoveFirstFromTuple>)=>ReturnType +type AsyncAction> = (...args:RemoveFirstFromTuple>)=>Promise> + +type StoreActions> = { [K in keyof A]: Action }; +type QueuedStoreActions> = { [K in keyof A]: AsyncAction }; +type ComputedValues> = { [K in keyof C]: ReturnType }; type PostponedAction = (state: object, ...args: any[]) => Promise; -type PostponedActions = { [key: string]: PostponedAction }; +type PostponedActions = { [key:string]: PostponedAction } -export type StoreHandler, C extends UserComputedValues> = { - $subscribe: (listener: () => void) => void; - $unsubscribe: (listener: () => void) => void; - $state: S; - $config: StoreConfig; - $queue: StoreActions; - $actions: StoreActions; - $computed: StoreActions; - reduxHandler?: ReduxStoreHandler; -} & { [K in keyof S]: S[K] } & - { [K in keyof A]: A[K] } & - { [K in keyof C]: C[K] }; +export type StoreHandler,C extends UserComputedValues> = + {$subscribe: ((listener: () => void) => void), + $unsubscribe: ((listener: () => void) => void), + $state: S, + $config: StoreConfig, + $queue: QueuedStoreActions, + $actions: StoreActions, + $computed: ComputedValues, + reduxHandler?:ReduxStoreHandler} + & + {[K in keyof S]: S[K]} + & + {[K in keyof A]: Action} + & + {[K in keyof C]: ReturnType} -export type StoreConfig, C extends UserComputedValues> = { - state?: S; - options?: { suppressHooks?: boolean }; - actions?: A; - id?: string; - computed?: C; -}; +export type StoreConfig,C extends UserComputedValues> = { + state?: S, + options?:{suppressHooks?: boolean}, + actions?: A, + id?: string, + computed?: C +} type ReduxStoreHandler = { - reducer: (state: any, action: { type: string }) => any; - dispatch: (action: { type: string }) => void; - getState: () => any; - subscribe: (listener: () => void) => (listener: () => void) => void; -}; + reducer:(state:any,action:{type:string})=>any, + dispatch:(action:{type:string})=>void, + getState:()=>any, + subscribe:(listener:()=>void)=>((listener:()=>void)=>void) + replaceReducer: (reducer: (state:any,action:{type:string})=>any)=>void + _horizonXstore: StoreHandler +} type ReduxAction = { - type: string; -}; + type:string +} -type ReduxMiddleware = ( - store: ReduxStoreHandler, - extraArgument?: any -) => ( - next: (action: ReduxAction) => any -) => ( - action: - | ReduxAction - | ((dispatch: (action: ReduxAction) => void, store: ReduxStoreHandler, extraArgument?: any) => any) -) => ReduxStoreHandler; +type ReduxMiddleware = (store:ReduxStoreHandler, extraArgument?:any) => + (next:((action:ReduxAction)=>any)) => + (action:( + ReduxAction| + ((dispatch:(action:ReduxAction)=>void,store:ReduxStoreHandler,extraArgument?:any)=>any) + )) => ReduxStoreHandler \ No newline at end of file