Allow adding middlewares via a module (#8)

This commit is contained in:
NAVNEET GUPTA 2018-10-04 21:12:53 -07:00 коммит произвёл GitHub
Родитель f65ab303b8
Коммит 9d9f2445b8
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 5776 добавлений и 59 удалений

6
.vscode/settings.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,6 @@
{
"cSpell.words": [
"middlewares",
"reset"
]
}

5616
package-lock.json сгенерированный Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -48,6 +48,9 @@
},
"verbose": false,
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"testPathIgnorePatterns": [
".*\\.d\\.ts"
],
"moduleFileExtensions": [
"ts",
"tsx",
@ -61,4 +64,4 @@
"@types/redux": "^3.6.0",
"@types/redux-saga": "^0.10.5"
}
}
}

Просмотреть файл

@ -1,65 +1,71 @@
import { AnyAction, ReducersMapObject, Store } from "redux";
import { AnyAction, ReducersMapObject, Store, Middleware } from "redux";
/**
* Represents a module which is set of reducers, sagas, inital actions and final actions
* Represents a module which is set of reducers, sagas, initial actions and final actions
*/
export interface IModule<State> {
/**
* Id of the module
*/
id: string;
/**
* Id of the module
*/
id: string;
/**
* Reducers for the module
*/
reducerMap?: ReducersMapObject<State, AnyAction>;
/**
* Reducers for the module
*/
reducerMap?: ReducersMapObject<State, AnyAction>;
/**
* These sagas are executed immediatly after adding the module to the store (before dispatching initial actions)
*/
sagas?: ISagaRegistration<any>[];
/**
* Middlewares to add to the store
*/
middlewares?: Middleware[];
/**
* These actions are dispatched immediatly after adding the module in the store
*/
initialActions?: AnyAction[];
/**
* These sagas are executed immediately after adding the module to the store (before dispatching initial actions)
*/
sagas?: ISagaRegistration<any>[];
/**
* These actions are dispatched immediatly before removing the module from the store
*/
finalActions?: AnyAction[];
/**
* These actions are dispatched immediately after adding the module in the store
*/
initialActions?: AnyAction[];
/**
* These actions are dispatched immediatly before removing the module from the store
*/
finalActions?: AnyAction[];
}
export interface ISagaWithArguments<T> {
saga: (argument?: T) => Iterator<any>;
argument?: T;
saga: (argument?: T) => Iterator<any>;
argument?: T;
}
export type ISagaRegistration<T> = (() => Iterator<any>) | ISagaWithArguments<T>;
export interface IDynamicallyAddedModule {
/**
* Call to remove the module from the store
*/
remove: () => void;
/**
* Call to remove the module from the store
*/
remove: () => void;
}
export interface IModuleManager {
/**
* Add the given module to the store
*/
addModule: (...modules: IModule<any>[]) => IDynamicallyAddedModule
/**
* Add the given module to the store
*/
addModule: (...modules: IModule<any>[]) => IDynamicallyAddedModule
}
export type IModuleStore<State> = Store<State> & IModuleManager & {
/**
* Remove all the modules from the store
*/
dispose: () => void;
/**
* Remove all the modules from the store
*/
dispose: () => void;
};
export interface IItemManager<T> {
getItems: () => T[];
add: (items: T[]) => void;
remove: (item: T[]) => void;
getItems: () => T[];
add: (items: T[]) => void;
remove: (item: T[]) => void;
dispose: () => void;
}

Просмотреть файл

@ -0,0 +1,52 @@
//inspired from https://github.com/pofigizm/redux-dynamic-middlewares
import { compose, Middleware } from "redux";
import { IItemManager } from "../Contracts";
export interface IDynamicMiddlewareManager extends IItemManager<Middleware> {
dynamicMiddleware: Middleware;
resetMiddlewares: () => void;
}
export const getMiddlewareManager = (): IDynamicMiddlewareManager => {
let allDynamicMiddlewares: Middleware[] = [];
const dynamicMiddleware = store => next => (action) => {
const chain: Function[] = allDynamicMiddlewares.map(m => m(store))
return compose<(action) => any>(...chain)(next)(action);
}
const add = (middlewares: Middleware[]) => {
allDynamicMiddlewares = [...allDynamicMiddlewares, ...middlewares];
return middlewares;
}
const remove = (middlewares: Middleware[]) => {
middlewares.forEach(middleware => {
const index = allDynamicMiddlewares.findIndex(d => d === middleware)
if (index === -1) {
// eslint-disable-next-line no-console
console.error('Middleware does not exist!', middleware)
return
}
allDynamicMiddlewares = allDynamicMiddlewares.filter((_, mdwIndex) => mdwIndex !== index)
});
return middlewares;
}
const resetMiddlewares = () => {
allDynamicMiddlewares = []
}
return {
getItems: () => allDynamicMiddlewares,
dynamicMiddleware,
add,
remove,
resetMiddlewares,
dispose: () => { allDynamicMiddlewares = [] }
}
}

Просмотреть файл

@ -1,21 +1,25 @@
import { IModule, ISagaRegistration, IItemManager } from "../Contracts";
import { AnyAction, ReducersMapObject, Dispatch, Reducer } from "redux";
import { getReducerManager, IReducerManager, getRefCountedReducerManager } from "./ReducerManager";
import { getSagaManager } from "./SagaManager";
import { AnyAction, Dispatch, Middleware, Reducer, ReducersMapObject } from "redux";
import { SagaMiddleware } from "redux-saga";
import { getRefCountedManager } from "./RefCountedManager";
import { IItemManager, IModule, ISagaRegistration } from "../Contracts";
import { equals as sagaEquals } from "../Utils/SagaComparer";
import { getReducerManager, getRefCountedReducerManager, IReducerManager } from "./ReducerManager";
import { getRefCountedManager } from "./RefCountedManager";
import { getSagaManager } from "./SagaManager";
export interface IModuleManager<State> extends IItemManager<IModule<State>> {
setDispatch: (dispatch: Dispatch<AnyAction>) => void;
getReducer: (state: State, action: AnyAction) => State;
}
export function getModuleManager<SagaContext, State>(sagaMiddleware: SagaMiddleware<SagaContext>): IModuleManager<State> {
export function getModuleManager<SagaContext, State>(
sagaMiddleware: SagaMiddleware<SagaContext>,
middlewareManager: IItemManager<Middleware>): IModuleManager<State> {
let _dispatch = null;
let _reducerManager: IReducerManager<State>;
const _sagaManager: IItemManager<ISagaRegistration<any>> = getRefCountedManager(getSagaManager(sagaMiddleware), sagaEquals);
const _addedModules: Set<any> = new Set();
let modules: IModule<any>[] = [];
const _moduleIds = new Set();
const _dispatchActions = (moduleId: string, actions: AnyAction[]) => {
if (!actions) {
@ -43,6 +47,20 @@ export function getModuleManager<SagaContext, State>(sagaMiddleware: SagaMiddlew
_sagaManager.remove(sagas);
}
const _addMiddlewares = (middlewares: Middleware[]) => {
if (!middlewares) {
return;
}
middlewareManager.add(middlewares);
}
const _removeMiddlewares = (middlewares: Middleware[]) => {
if (!middlewares) {
return;
}
middlewareManager.remove(middlewares);
}
const _addReducers = (reducerMap: ReducersMapObject<Reducer, AnyAction>) => {
if (!reducerMap) {
return;
@ -72,7 +90,7 @@ export function getModuleManager<SagaContext, State>(sagaMiddleware: SagaMiddlew
return (s || null);
};
return {
const moduleManager = {
getReducer: _reduce,
setDispatch: (dispatch: Dispatch<AnyAction>) => {
_dispatch = dispatch;
@ -86,13 +104,17 @@ export function getModuleManager<SagaContext, State>(sagaMiddleware: SagaMiddlew
const justAddedModules: IModule<any>[] = [];
modulesToAdd.forEach(module => {
if (!_addedModules.has(module.id)) {
_addedModules.add(module.id);
if (!_moduleIds.has(module.id)) {
_moduleIds.add(module.id);
_addReducers(module.reducerMap);
const middlewares = module.middlewares;
if (middlewares) {
_addMiddlewares(middlewares);
}
justAddedModules.push(module);
}
});
_dispatch && _dispatch({ type: "@@Internal/ModuleManager/ReducerAdded" });
// add the sagas and dispatch actions at the end so all the reducers are registered
@ -110,16 +132,21 @@ export function getModuleManager<SagaContext, State>(sagaMiddleware: SagaMiddlew
modulesToRemove = modulesToRemove.filter(module => module);
modulesToRemove.forEach(module => {
if (_addedModules.has(module.id)) {
if (_moduleIds.has(module.id)) {
_dispatchActions(module.id, module.finalActions);
_removeReducers(module.reducerMap);
_removeMiddlewares(module.middlewares);
_removeSagas(module.sagas);
_addedModules.delete(module.id);
_moduleIds.delete(module.id);
modules = modules.filter(m => m.id !== module.id);
_dispatch && _dispatch({ type: "@@Internal/ModuleManager/ModuleRemoved" });
}
});
},
dispose: () => {
moduleManager.remove(modules);
}
};
return moduleManager;
}

Просмотреть файл

@ -31,7 +31,8 @@ export function getSagaManager(sagaMiddleware: SagaMiddleware<any>): IItemManage
task.cancel();
}
});
}
},
dispose: () => { }
};
}

Просмотреть файл

@ -3,6 +3,7 @@ import { default as createSagaMiddleware, SagaMiddleware } from "redux-saga";
import { IModule, IModuleStore } from "./Contracts";
import { getModuleManager } from "./Managers/ModuleManager";
import { getRefCountedManager } from "./Managers/RefCountedManager";
import { getMiddlewareManager } from './Managers/MiddlewareManager';
/**
* Configure the module store
@ -27,8 +28,9 @@ export function configureStore<SagaContext, State>(initialState: DeepPartial<Sta
);
const composeEnhancers = compose;
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware));
const modules = getRefCountedManager(getModuleManager<SagaContext, State>(sagaMiddleware), (a: IModule<any>, b: IModule<any>) => a.id === b.id);
const middlewareManager = getRefCountedManager(getMiddlewareManager(), (a, b) => a === b);
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware, middlewareManager.dynamicMiddleware));
const modules = getRefCountedManager(getModuleManager<SagaContext, State>(sagaMiddleware, middlewareManager), (a: IModule<any>, b: IModule<any>) => a.id === b.id);
// Create store
const store: IModuleStore<State> = createStore<State, any, {}, {}>(

Просмотреть файл

@ -1,4 +1,5 @@
import { getModuleManager } from "../../Managers/ModuleManager";
import { getMiddlewareManager } from '../../Managers/MiddlewareManager';
function getSagaMiddleware(callback) {
return {
run: () => {
@ -12,7 +13,8 @@ function getSagaMiddleware(callback) {
it("module manager tests", () => {
let taskCancellationCounter = 0;
const taskCancelCallback = () => taskCancellationCounter++;
const moduleManager = getModuleManager(getSagaMiddleware(taskCancelCallback) as any);
const middlewareManager = getMiddlewareManager();
const moduleManager = getModuleManager(getSagaMiddleware(taskCancelCallback) as any, middlewareManager);
let actionsDispatched = [];
moduleManager.setDispatch((action) => {

Просмотреть файл

@ -5,7 +5,8 @@ it("ref counted manager tests", () => {
const manager = {
getItems: () => Array.from(items.keys()),
add: (s: string[]) => s.length > 0 && s.forEach(s1 => items.add(s1)),
remove: (s: string[]) => s.forEach(s1 => items.delete(s1))
remove: (s: string[]) => s.forEach(s1 => items.delete(s1)),
dispose: () => { }
};
const refCounter = getRefCountedManager(manager, (a, b) => a === b);

Просмотреть файл

@ -6,3 +6,4 @@ export * from "./Utils/RefCounter";
export * from "./Utils/Registry";
export * from "./Utils/SagaComparer";
export * from "./Managers/PluginManager";
export * from "./Managers/MiddlewareManager";