UNPKG

6.32 kBPlain TextView Raw
1import { createLogger } from '@gongt/ts-stl-library/debug/create-logger';
2import { LOG_LEVEL } from '@gongt/ts-stl-library/debug/levels';
3import { GlobalVariable } from '@gongt/ts-stl-library/pattern/global-page-data';
4import { applyMiddleware, compose, createStore, Dispatch, Middleware, Reducer, Store, Unsubscribe, } from 'redux';
5import { ArrayOrSingle } from '../global';
6import { IAction, IAData } from './action';
7import { MyCombineReducers } from './combine-reducers';
8import { IState } from './preload-state';
9import { defaultMiddlewares } from './store.internal-middleware';
10import { IVirtualStore, IVirtualStoreConstructor } from './virtual-store';
11
12export type AppStore<StateInterface extends IState> = Store<StateInterface>;
13
14export type ReduxStoreType = ReduxStore<any>;
15export type LogicFunction<T> = (store: ReduxStore<T>) => void;
16export type BorderLogicFunction<T> = (
17 subscribe: (listener: (state: T) => void) => Unsubscribe,
18 dispatch: Dispatch<T>,
19) => void;
20
21export type SimpleMiddleware<T, D extends IAData> = (store: AppStore<T>, action: IAction<D>)
22 => void|IAction<D>|Promise<void|IAction<D>>;
23
24export const REDUX_PRELOAD_NAME = '__REDUX_PRELOAD_DATA__';
25
26const debug = createLogger(LOG_LEVEL.SILLY, 'redux-store');
27
28export interface MiddlewareCreator {
29 (global: GlobalVariable): Middleware;
30}
31
32export interface IPlugin<State extends IState> {
33 __redux_plugin(reduxStore: ReduxStore<State>): void;
34}
35
36/**
37 * 根据逻辑函数(构造函数的参数)创建Store
38 */
39export class ReduxStore<StateInterface extends IState> {
40 public middlewares: Middleware[];
41 public processObject: BorderLogicFunction<StateInterface>[] = [];
42
43 public subStore: {[id: string]: IVirtualStore<any>} = {};
44 protected readonly composeEnhancers = compose;
45 private finished = false;
46 private middleware_callbacks: MiddlewareCreator[] = [];
47
48 constructor(logicRegister?: ArrayOrSingle<LogicFunction<StateInterface>>) {
49 this.middlewares = defaultMiddlewares.slice();
50 if (logicRegister) {
51 if (Array.isArray(logicRegister)) {
52 logicRegister.forEach((fn) => {
53 fn(this);
54 });
55 } else {
56 logicRegister(this);
57 }
58 }
59 }
60
61 createStore(global: GlobalVariable): AppStore<StateInterface> {
62 if (global.has(REDUX_PRELOAD_NAME)) {
63 const preloadOrStoreObject = global.get(REDUX_PRELOAD_NAME);
64 if (typeof preloadOrStoreObject.getState === 'function') {
65 debug(' create store: re-call, return last object.');
66 return preloadOrStoreObject;
67 }
68 }
69 this.finished = true;
70 const applicationLogic = this.combineReducers();
71 const dynamicMiddleware = this.middleware_callbacks.map((cb) => {
72 return cb(global);
73 });
74 const appliedMiddleware = applyMiddleware(...this.middlewares, ...dynamicMiddleware);
75
76 const createStoreEnhancer = this.composeEnhancers(appliedMiddleware);
77 const enhancedCreateStore = createStoreEnhancer<any>(createStore); // TODO: <- this any
78
79 const preloadState = this.getPreloadState(global);
80
81 Object.keys(this.subStore).forEach((key) => {
82 const st = this.subStore[key];
83 // console.log('%s: ', key, preloadState.hasOwnProperty(key), st.hasOwnProperty('defaultValue'), st);
84 if (!preloadState.hasOwnProperty(key) && st.hasOwnProperty('defaultValue')) {
85 preloadState[key] = st.defaultValue;
86 }
87 });
88
89 if (debug.enabled) {
90 debug(' preloadState=');
91 for (let name of Object.keys(preloadState)) {
92 const v = preloadState[name];
93 debug(' %s -> %s', name, v.name || v.constructor.name || v);
94 }
95 }
96
97 const store: Store<StateInterface> = enhancedCreateStore(applicationLogic, preloadState);
98 store['toJSON'] = store.getState;
99
100 this.processObject.forEach((fn) => {
101 fn((f) => {
102 f(store.getState());
103 return store.subscribe(() => {
104 f(store.getState());
105 });
106 }, (act: any) => {
107 if (act && act['toJSON']) {
108 act = act['toJSON']();
109 }
110 return store.dispatch(act);
111 });
112 });
113
114 global.set(REDUX_PRELOAD_NAME, store);
115 return store;
116 }
117
118 /** 添加预处理 */
119 public logicDisplayBorder(middleware: BorderLogicFunction<StateInterface>) {
120 if (this.finished) {
121 throw new Error('ReduxStore: use middleware too late');
122 }
123 this.processObject.push(middleware);
124 }
125
126 public plugin(plugin: IPlugin<Partial<StateInterface>>) {
127 plugin.__redux_plugin(this);
128 }
129
130 /** 添加子存储 */
131 public register<T>(Constructor: IVirtualStoreConstructor<T>) {
132 if (this.finished) {
133 throw new Error('ReduxStore: register sub store too late');
134 }
135 const name = Constructor.name;
136 // console.log('register sub store: ', name);
137 if (this.subStore[name]) {
138 throw new TypeError('duplicate store: ' + name);
139 }
140 this.subStore[name] = new Constructor();
141 }
142
143 /** 添加中间件 */
144 public use(middleware: Middleware) {
145 if (this.finished) {
146 throw new Error('ReduxStore: use middleware too late');
147 }
148 this.middlewares.push(middleware);
149 }
150
151 public useDynamic(callback: MiddlewareCreator) {
152 if (this.finished) {
153 throw new Error('ReduxStore: use middleware too late');
154 }
155 this.middleware_callbacks.push(callback);
156 }
157
158 public useSimple<D extends IAData>(middleware: SimpleMiddleware<StateInterface, D>) {
159 if (this.finished) {
160 throw new Error('ReduxStore: use middleware too late');
161 }
162 const md: Middleware = <StateInterface>(state) => next => action => {
163 const ret = middleware(state, action);
164 if (!ret) {
165 console.log('an action has been dropped: %s', action.type);
166 return null;
167 } else if (ret instanceof Promise) {
168 ret.then((act) => {
169 if (act) {
170 next(act);
171 } else {
172 console.log('%can action has been dropped (after sync): %s', 'color:grey', action.type);
173 }
174 }, (e) => {
175 console.error(e);
176 throw new Error('simple middleware promise rejected.');
177 });
178 return null;
179 } else {
180 return next(action);
181 }
182 };
183 this.middlewares.push(md);
184 }
185
186 protected combineReducers(): Reducer<StateInterface> {
187 const pass = [];
188 for (const key of Object.keys(this.subStore)) {
189 const st = this.subStore[key];
190 pass.push(...st.getReducers());
191 }
192 return MyCombineReducers(pass);
193 }
194
195 protected getPreloadState(global: GlobalVariable) {
196 if (global.has(REDUX_PRELOAD_NAME)) {
197 const pl = global.get(REDUX_PRELOAD_NAME);
198 global.unset(REDUX_PRELOAD_NAME);
199 return pl;
200 }
201 return {};
202 }
203}