@mikhail.shtanko/redux-oop
Version:
Powerful extensitions for building class-based Redux architecture powered by TypeScript.
80 lines (72 loc) • 3.57 kB
text/typescript
import 'reflect-metadata';
import State from './state';
import Store from './store';
import Action from './action';
import { Middleware as ReduxMiddleware, MiddlewareAPI, Dispatch } from 'redux';
import { METADATA_KEY_ACTION, METADATA_KEY_MIDDLEWARE_TYPE } from './constants';
export class Middleware<S extends State> {
preActionsMap: Map<string, Array<(Store: Store<S>, action: Action) => Store<S>>> = new Map();
postActionsMap: Map<string, Array<(Store: Store<S>, action: Action, prevState: S) => Store<S>>> = new Map();
private executePre(store: Store<S>, action: Action, actionType?: string) {
const preFuncs = this.preActionsMap.get(actionType || action.type);
if (preFuncs != null) {
for (const preFunc of preFuncs) {
preFunc.bind(this)(store, action);
}
}
}
private executePost(store: Store<S>, action: Action, prevState: S, actionType?: string) {
const postFuncs = this.postActionsMap.get(actionType || action.type);
if (postFuncs != null) {
for (const postFunc of postFuncs) {
postFunc.bind(this)(store, action, prevState);
}
}
}
protected call(store: Store<S>): any {
return ((reduxStore: MiddlewareAPI) => {
return (next: Dispatch) => {
return (action: Action) => {
this.executePre(store, action);
// for all listeners with 'any' type of action
this.executePre(store, action, Object.name);
const prevState = store.getState();
next(action);
this.executePost(store, action, prevState);
// for all listeners with 'any' type of action
this.executePost(store, action, prevState, Object.name);
};
};
}) as ReduxMiddleware;
}
create(store: Store<S>): ReduxMiddleware {
// idk why all methods are in prototype class
let proto = Object.getPrototypeOf(this);
while (proto.constructor.name != Middleware.name) {
const methodNames: string[] = Object.getOwnPropertyNames(proto).filter(x => x != 'constructor' && !x.startsWith('_'));
for (const key of methodNames) {
const metaActionType: string = Reflect.getMetadata(METADATA_KEY_ACTION, this, key);
if (metaActionType == null)
continue;
const handlerType: string = Reflect.getMetadata(METADATA_KEY_MIDDLEWARE_TYPE, this, key);
const func: (state: Store<S>, action: any) => Store<S> = (this as any)[key];
if (handlerType === 'pre') {
if (!this.preActionsMap.has(metaActionType))
this.preActionsMap.set(metaActionType, []);
this.preActionsMap.get(metaActionType)!.push(func);
} else {
if (!this.postActionsMap.has(metaActionType))
this.postActionsMap.set(metaActionType, []);
this.postActionsMap.get(metaActionType)!.push(func);
}
}
proto = Object.getPrototypeOf(proto);
}
return this.call(store).bind(this);
}
}
export class IsolatedMiddleware<S extends State, SRoot extends State> extends Middleware<S> {
constructor(protected getState: (state: SRoot) => S) {
super();
}
}