import { createLogger } from '@gongt/ts-stl-library/debug/create-logger'; import { LOG_LEVEL } from '@gongt/ts-stl-library/debug/levels'; import { GlobalVariable } from '@gongt/ts-stl-library/pattern/global-page-data'; import { applyMiddleware, compose, createStore, Dispatch, Middleware, Reducer, Store, Unsubscribe, } from 'redux'; import { ArrayOrSingle } from '../global'; import { IAction, IAData } from './action'; import { MyCombineReducers } from './combine-reducers'; import { IState } from './preload-state'; import { defaultMiddlewares } from './store.internal-middleware'; import { IVirtualStore, IVirtualStoreConstructor } from './virtual-store'; export type AppStore = Store; export type ReduxStoreType = ReduxStore; export type LogicFunction = (store: ReduxStore) => void; export type BorderLogicFunction = ( subscribe: (listener: (state: T) => void) => Unsubscribe, dispatch: Dispatch, ) => void; export type SimpleMiddleware = (store: AppStore, action: IAction) => void|IAction|Promise>; export const REDUX_PRELOAD_NAME = '__REDUX_PRELOAD_DATA__'; const debug = createLogger(LOG_LEVEL.SILLY, 'redux-store'); export interface MiddlewareCreator { (global: GlobalVariable): Middleware; } export interface IPlugin { __redux_plugin(reduxStore: ReduxStore): void; } /** * 根据逻辑函数(构造函数的参数)创建Store */ export class ReduxStore { public middlewares: Middleware[]; public processObject: BorderLogicFunction[] = []; public subStore: {[id: string]: IVirtualStore} = {}; protected readonly composeEnhancers = compose; private finished = false; private middleware_callbacks: MiddlewareCreator[] = []; constructor(logicRegister?: ArrayOrSingle>) { this.middlewares = defaultMiddlewares.slice(); if (logicRegister) { if (Array.isArray(logicRegister)) { logicRegister.forEach((fn) => { fn(this); }); } else { logicRegister(this); } } } createStore(global: GlobalVariable): AppStore { if (global.has(REDUX_PRELOAD_NAME)) { const preloadOrStoreObject = global.get(REDUX_PRELOAD_NAME); if (typeof preloadOrStoreObject.getState === 'function') { debug(' create store: re-call, return last object.'); return preloadOrStoreObject; } } this.finished = true; const applicationLogic = this.combineReducers(); const dynamicMiddleware = this.middleware_callbacks.map((cb) => { return cb(global); }); const appliedMiddleware = applyMiddleware(...this.middlewares, ...dynamicMiddleware); const createStoreEnhancer = this.composeEnhancers(appliedMiddleware); const enhancedCreateStore = createStoreEnhancer(createStore); // TODO: <- this any const preloadState = this.getPreloadState(global); Object.keys(this.subStore).forEach((key) => { const st = this.subStore[key]; // console.log('%s: ', key, preloadState.hasOwnProperty(key), st.hasOwnProperty('defaultValue'), st); if (!preloadState.hasOwnProperty(key) && st.hasOwnProperty('defaultValue')) { preloadState[key] = st.defaultValue; } }); if (debug.enabled) { debug(' preloadState='); for (let name of Object.keys(preloadState)) { const v = preloadState[name]; debug(' %s -> %s', name, v.name || v.constructor.name || v); } } const store: Store = enhancedCreateStore(applicationLogic, preloadState); store['toJSON'] = store.getState; this.processObject.forEach((fn) => { fn((f) => { f(store.getState()); return store.subscribe(() => { f(store.getState()); }); }, (act: any) => { if (act && act['toJSON']) { act = act['toJSON'](); } return store.dispatch(act); }); }); global.set(REDUX_PRELOAD_NAME, store); return store; } /** 添加预处理 */ public logicDisplayBorder(middleware: BorderLogicFunction) { if (this.finished) { throw new Error('ReduxStore: use middleware too late'); } this.processObject.push(middleware); } public plugin(plugin: IPlugin>) { plugin.__redux_plugin(this); } /** 添加子存储 */ public register(Constructor: IVirtualStoreConstructor) { if (this.finished) { throw new Error('ReduxStore: register sub store too late'); } const name = Constructor.name; // console.log('register sub store: ', name); if (this.subStore[name]) { throw new TypeError('duplicate store: ' + name); } this.subStore[name] = new Constructor(); } /** 添加中间件 */ public use(middleware: Middleware) { if (this.finished) { throw new Error('ReduxStore: use middleware too late'); } this.middlewares.push(middleware); } public useDynamic(callback: MiddlewareCreator) { if (this.finished) { throw new Error('ReduxStore: use middleware too late'); } this.middleware_callbacks.push(callback); } public useSimple(middleware: SimpleMiddleware) { if (this.finished) { throw new Error('ReduxStore: use middleware too late'); } const md: Middleware = (state) => next => action => { const ret = middleware(state, action); if (!ret) { console.log('an action has been dropped: %s', action.type); return null; } else if (ret instanceof Promise) { ret.then((act) => { if (act) { next(act); } else { console.log('%can action has been dropped (after sync): %s', 'color:grey', action.type); } }, (e) => { console.error(e); throw new Error('simple middleware promise rejected.'); }); return null; } else { return next(action); } }; this.middlewares.push(md); } protected combineReducers(): Reducer { const pass = []; for (const key of Object.keys(this.subStore)) { const st = this.subStore[key]; pass.push(...st.getReducers()); } return MyCombineReducers(pass); } protected getPreloadState(global: GlobalVariable) { if (global.has(REDUX_PRELOAD_NAME)) { const pl = global.get(REDUX_PRELOAD_NAME); global.unset(REDUX_PRELOAD_NAME); return pl; } return {}; } }