@gongt/ts-stl-client
Version:
204 lines (177 loc) • 6.32 kB
text/typescript
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<StateInterface extends IState> = Store<StateInterface>;
export type ReduxStoreType = ReduxStore<any>;
export type LogicFunction<T> = (store: ReduxStore<T>) => void;
export type BorderLogicFunction<T> = (
subscribe: (listener: (state: T) => void) => Unsubscribe,
dispatch: Dispatch<T>,
) => void;
export type SimpleMiddleware<T, D extends IAData> = (store: AppStore<T>, action: IAction<D>)
=> void|IAction<D>|Promise<void|IAction<D>>;
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<State extends IState> {
__redux_plugin(reduxStore: ReduxStore<State>): void;
}
/**
* 根据逻辑函数(构造函数的参数)创建Store
*/
export class ReduxStore<StateInterface extends IState> {
public middlewares: Middleware[];
public processObject: BorderLogicFunction<StateInterface>[] = [];
public subStore: {[id: string]: IVirtualStore<any>} = {};
protected readonly composeEnhancers = compose;
private finished = false;
private middleware_callbacks: MiddlewareCreator[] = [];
constructor(logicRegister?: ArrayOrSingle<LogicFunction<StateInterface>>) {
this.middlewares = defaultMiddlewares.slice();
if (logicRegister) {
if (Array.isArray(logicRegister)) {
logicRegister.forEach((fn) => {
fn(this);
});
} else {
logicRegister(this);
}
}
}
createStore(global: GlobalVariable): AppStore<StateInterface> {
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<any>(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<StateInterface> = 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<StateInterface>) {
if (this.finished) {
throw new Error('ReduxStore: use middleware too late');
}
this.processObject.push(middleware);
}
public plugin(plugin: IPlugin<Partial<StateInterface>>) {
plugin.__redux_plugin(this);
}
/** 添加子存储 */
public register<T>(Constructor: IVirtualStoreConstructor<T>) {
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<D extends IAData>(middleware: SimpleMiddleware<StateInterface, D>) {
if (this.finished) {
throw new Error('ReduxStore: use middleware too late');
}
const md: Middleware = <StateInterface>(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<StateInterface> {
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 {};
}
}