import { applyMiddleware, compose, legacy_createStore as createStore, Middleware, PreloadedState, Reducer, Store, StoreEnhancer, } from 'redux'; import { Topic } from 'topic'; import { actionRefresh, RefreshAction } from '../actions/refresh'; import { modelInterceptor } from '../middleware/modelInterceptor'; import type { PersistOptions } from '../persist/PersistItem'; import { PersistManager } from '../persist/PersistManager'; import { combine } from './proxyStore'; import { OBJECT } from '../utils/isType'; import { StoreBasic } from './StoreBasic'; type Compose = typeof compose | ((enhancer: StoreEnhancer) => StoreEnhancer); interface CreateStoreOptions { preloadedState?: PreloadedState<any>; compose?: 'redux-devtools' | Compose; middleware?: Middleware[]; persist?: PersistOptions[]; } class ModelStore extends StoreBasic<Record<string, any>> { public topic: Topic<{ init: []; ready: []; refresh: []; unmount: []; }> = new Topic(); protected _isReady: boolean = false; protected consumers: Record<string, Reducer> = {}; protected reducerKeys: string[] = []; /** * @protected */ public persistor: PersistManager | null = null; protected reducer!: Reducer; constructor() { super(); this.topic.keep('ready', () => this._isReady); } get isReady(): boolean { return this._isReady; } init(options: CreateStoreOptions = {}) { const prevStore = this.origin; const firstInitialize = !prevStore; if (!firstInitialize) { if (process.env.NODE_ENV === 'production') { throw new Error(`[store] 请勿多次执行'store.init()'`); } } this._isReady = false; this.reducer = this.combineReducers(); const persistOptions = options.persist; let persistor = this.persistor; persistor && persistor.destroy(); if (persistOptions && persistOptions.length) { persistor = this.persistor = new PersistManager(persistOptions); this.reducer = persistor.combineReducer(this.reducer); } else { persistor = this.persistor = null; } let store: Store; if (firstInitialize) { const enhancer: StoreEnhancer<any> = applyMiddleware.apply( null, (options.middleware || []).concat(modelInterceptor), ); store = this.origin = createStore( this.reducer, options.preloadedState, this.getCompose(options.compose)(enhancer), ); this.topic.publish('init'); combine(store); } else { // 重新创建store会导致组件里的subscription都失效 store = prevStore; store.replaceReducer(this.reducer); } if (persistor) { persistor.init(store, firstInitialize).then(() => { this.ready(); }); } else { this.ready(); } return this; } refresh(force: boolean = false): RefreshAction { const action = this.dispatch(actionRefresh(force)); this.topic.publish('refresh'); return action; } unmount() { this.origin = null; this._isReady = false; this.topic.publish('unmount'); } onInitialized(): Promise<void> { return new Promise((resolve) => { if (this._isReady) { resolve(); } else { this.topic.subscribeOnce('ready', resolve); } }); } protected ready() { this._isReady = true; this.topic.publish('ready'); } protected getCompose(customCompose: CreateStoreOptions['compose']): Compose { if (customCompose === 'redux-devtools') { if (process.env.NODE_ENV !== 'production') { return ( /** @ts-expect-error */ (typeof window === OBJECT ? window : /* istanbul ignore next */ typeof global === OBJECT ? global : {})['__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'] || compose ); } return compose; } return customCompose || compose; } protected combineReducers(): Reducer<Record<string, object>> { return (state, action) => { if (state === void 0) state = {}; const reducerKeys = this.reducerKeys; const consumers = this.consumers; const keyLength = reducerKeys.length; const nextState: Record<string, any> = {}; let hasChanged = false; let i = keyLength; while (i-- > 0) { const key = reducerKeys[i]!; const prevForKey = state[key]; const nextForKey = (nextState[key] = consumers[key]!( prevForKey, action, )); hasChanged ||= nextForKey !== prevForKey; } return hasChanged || keyLength !== Object.keys(state).length ? nextState : state; }; } /** * @protected */ public appendReducer(key: string, consumer: Reducer): void { const store = this.origin; const consumers = this.consumers; const exists = store && consumers.hasOwnProperty(key); consumers[key] = consumer; this.reducerKeys = Object.keys(consumers); store && !exists && store.replaceReducer(this.reducer); } /** * @protected */ public removeReducer(key: string): void { const store = this.origin; const consumers = this.consumers; if (consumers.hasOwnProperty(key)) { delete consumers[key]; this.reducerKeys = Object.keys(consumers); store && store.replaceReducer(this.reducer); } } } export const modelStore = new ModelStore();