Skip to content

Commit

Permalink
feat: replace enhancer with middleware
Browse files Browse the repository at this point in the history
createAssistantEnhancer method was removed. Use createAssistantMiddleware instead

BREAKING CHANGE: removed createAssistantEnhancer method
  • Loading branch information
megazazik committed Apr 14, 2020
1 parent b2ee63a commit 18f4f6a
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 83 deletions.
36 changes: 18 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,18 @@ class IntervalAssistant extends Assistant {

## Configure redux store

The `createAssistantEnhancer` function is used to setup store to work with assistants. It create a store enhancer with an `applyAssistants` method which receives an array of assistant consctructors or `AssistanConfig` objects (see [Assistant config](#assistant-config) section).
The `createAssistantMiddleware` function is used to setup store to work with assistants. It create a middleware with an `applyAssistants` method which receives an array of assistant consctructors or `AssistanConfig` objects (see [Assistant config](#assistant-config) section).

```typescript
import { createStore } from 'redux';
import { createAssistantEnhancer } from 'reducer-assistant/redux';
import { createStore, applyMiddleware } from 'redux';
import { createAssistantMiddleware } from 'reducer-assistant/redux';
import { assistants } from './assistants';

const assistantEnhancer = createAssistantEnhancer();
const assistantMiddleware = createAssistantMiddleware();

const store = createStore(reducer, assistantEnhancer);
const store = createStore(reducer, applyMiddleware(assistantMiddleware));

assistantEnhancer.applyAssistants(assistants);
assistantMiddleware.applyAssistants(assistants);
```

You can invoke the `applyAssistants` method many times. All previous assistants will be destroyed.
Expand Down Expand Up @@ -262,7 +262,7 @@ There is no need to remove listeners of the base assistant class events such as

## Assistant config

To create assistants you can use the `applyAssistants` method of a store enhancer or the `createAssistant` method of an assistant. They receives `AssistantConfigs` values.
To create assistants you can use the `applyAssistants` method of a middleware or the `createAssistant` method of an assistant. They receives `AssistantConfigs` values.

The simplified `AssistantConfigs` type has the following form:

Expand All @@ -286,7 +286,7 @@ The simplest version of `AssistantConfigs` is an assistant's constructor.
```ts
class MyAssistant extends Assustant {}

enhancer.applyAssistants([MyAssistant]);
middleware.applyAssistants([MyAssistant]);
```

### Constructor with select
Expand Down Expand Up @@ -325,13 +325,13 @@ type PageState = {
When you create an assistant you can specify a part of state which will be managed by the assistant via `select` function of `AssistantConfig`.
```typescript
enhancer.applyAssistants([
middleware.applyAssistants([
{
Constructor: TimerAssistant,
/** select part of the PageState for TimerAssistant */
select: (fullstate) => fullstate.timer,
},
]); // instead of enhancer.applyAssistants([TimerAssistant])
]); // instead of middleware.applyAssistants([TimerAssistant])
```

Now the `state` property of a `TimerAssistant`'s instance will return the `timer` field value of the page state. And listeners of the `onChange` event will be invoked only after the `timer` field changed.
Expand Down Expand Up @@ -360,19 +360,19 @@ import { ofStatePart } from 'reducer-assistant';

/** all these calls are equal */

enhancer.applyAssistants([
middleware.applyAssistants([
{
Constructor: TimerAssistant,
/** select part of the PageState for TimerAssistant */
select: (fullstate) => fullstate.timer,
},
]);

enhancer.applyAssistants([
middleware.applyAssistants([
ofStatePart((fullstate) => fullstate.timer, TimerAssistant),
]);

enhancer.applyAssistants([ofStatePart('timer', TimerAssistant)]);
middleware.applyAssistants([ofStatePart('timer', TimerAssistant)]);
```

The first parameter of the `ofStatePart` is a `select` function or a field name of a whole state. The second parameter is an `AssistantConfig`.
Expand All @@ -383,7 +383,7 @@ You can pass an array of configs to it. Then the `ofStatePart` returns an array
```ts
import { ofStatePart } from 'reducer-assistant';

enhancer.applyAssistants(
middleware.applyAssistants(
ofStatePart(
(fullstate) => fullstate.timer,
[Assistant1, Assistant2, ...]
Expand All @@ -392,7 +392,7 @@ enhancer.applyAssistants(

// or

enhancer.applyAssistants(
middleware.applyAssistants(
ofStatePart(
'timer',
[Assistant1, Assistant2, ...]
Expand Down Expand Up @@ -420,14 +420,14 @@ ofStatePart(
Another form of `AssistantConfig` is the object with a `create` method instead of `Constructor`. A `create` method should return a new instance of `Assistant`. The following examples are equal.

```ts
enhancer.applyAssistants([
middleware.applyAssistants([
{
Constructor: TimerAssistant,
select: (fullstate) => fullstate.timer,
},
]);

enhancer.applyAssistants([
middleware.applyAssistants([
{
create: () => new TimerAssistant(),
select: (fullstate) => fullstate.timer,
Expand Down Expand Up @@ -475,5 +475,5 @@ function getAssistantConfig(url) {
And then can use this helper.

```ts
enhancer.applyAssistants([getAssistantConfig(url)]);
middleware.applyAssistants([getAssistantConfig(url)]);
```
27 changes: 12 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
const getStateSymbol = Symbol('getState');
const dispatchSymbol = Symbol('dispatch');
const subscribeSymbol = Symbol('subscribe');
const actionsEventEmitterSymbol = Symbol('actionsEe');
const initSymbol = Symbol('init');
const onDestroySymbol = Symbol('onDestroy');

type Unsubscribe = () => void;

export type ActionEventName = 'before' | 'after';
export type ActionEventName = 'before' | 'after' | 'change';

export interface IEventEmitter {
on(eventName: ActionEventName, callback: (action: any) => void): void;
remove(eventName: ActionEventName, callback: (action: any) => void): void;
on(eventName: ActionEventName, callback: (action?: any) => void): void;
remove(eventName: ActionEventName, callback: (action?: any) => void): void;
}

type OnActionEvent =
Expand All @@ -27,7 +26,6 @@ export abstract class Assistant<S> {

public [getStateSymbol]: () => Readonly<S>;
public [dispatchSymbol]: { (action: any): void };
public [subscribeSymbol]: (callback: () => void) => Unsubscribe;
public [actionsEventEmitterSymbol]: IEventEmitter;
public [onDestroySymbol]: () => void;

Expand All @@ -40,13 +38,15 @@ export abstract class Assistant<S> {
}

protected onChange(callback: () => void) {
return this.addUnsubscribe(
this[subscribeSymbol](() => {
if (this.prevState !== this.state) {
callback();
}
})
);
const newCallback = () => {
if (this.prevState !== this.state) {
callback();
}
};
this[actionsEventEmitterSymbol].on('change', newCallback);
return this.addUnsubscribe(() => {
this[actionsEventEmitterSymbol].remove('change', newCallback);
});
}

protected afterAction(
Expand Down Expand Up @@ -125,7 +125,6 @@ export abstract class Assistant<S> {
config,
() => this.state,
this[dispatchSymbol],
this[subscribeSymbol],
this[actionsEventEmitterSymbol],
() => {
this.assistants.delete(newAssistant);
Expand Down Expand Up @@ -302,7 +301,6 @@ export function createAssistant<A extends Assistant<any>, S>(
config: AssistantConfig<A, S>,
getState: () => S,
dispatch: (action: any) => void,
subscribe: (callback: () => void) => Unsubscribe,
eventemitter: IEventEmitter,
onDestroy = () => {}
) {
Expand All @@ -311,7 +309,6 @@ export function createAssistant<A extends Assistant<any>, S>(

assistant[getStateSymbol] = () => select(getState());
assistant[dispatchSymbol] = dispatch;
assistant[subscribeSymbol] = subscribe;
assistant[actionsEventEmitterSymbol] = eventemitter;
assistant[onDestroySymbol] = onDestroy;

Expand Down
80 changes: 38 additions & 42 deletions src/redux.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StoreEnhancer, StoreEnhancerStoreCreator } from 'redux';
import { Middleware } from 'redux';
import {
Assistant,
AssistantConfig,
Expand All @@ -8,10 +8,6 @@ import {
ActionEventName,
} from '.';

export type AssistantEnhancer<S> = StoreEnhancer & {
applyAssistants(configs: Configs<S>): void;
};

class RootAssistant<S> extends Assistant<S> {
childrens: Array<Assistant<any>> = [];

Expand All @@ -26,6 +22,7 @@ class EventEmitter implements IEventEmitter {
listeners = {
before: new Set<(action: any) => void>(),
after: new Set<(action: any) => void>(),
change: new Set<(action?: any) => void>(),
};

on(eventName: ActionEventName, callback: (action: any) => void): void {
Expand All @@ -36,18 +33,22 @@ class EventEmitter implements IEventEmitter {
this.listeners[eventName].delete(callback);
}

emit(eventName: ActionEventName, action: any) {
emit(eventName: ActionEventName, action?: any) {
this.listeners[eventName].forEach((listener) => listener(action));
}
}

export const createAssistantEnhancer = <S>() => {
const config = createEnhancer(RootAssistant);
const storeEnhancer: AssistantEnhancer<S> = config.enhancer as AssistantEnhancer<
export type AssistantMiddleware<S> = Middleware & {
applyAssistants(configs: Configs<S>): void;
};

export const createAssistantMiddleware = <S>() => {
const config = createMiddleware(RootAssistant);
const middleware: AssistantMiddleware<S> = config.middleware as AssistantMiddleware<
S
>;

storeEnhancer.applyAssistants = (configs) => {
middleware.applyAssistants = (configs) => {
if (!config.rootAssistant) {
throw new Error(
'Could not apply assistants before state initialization'
Expand All @@ -57,49 +58,44 @@ export const createAssistantEnhancer = <S>() => {
config.rootAssistant.applyAssistants(configs);
};

return storeEnhancer;
return middleware;
};

const createEnhancer: <S>(
const createMiddleware: <S>(
config: AssistantConfig<any, S>
) => {
rootAssistant: RootAssistant<S>;
enhancer: StoreEnhancer;
middleware: Middleware;
} = (config) => {
const enhanceConfig: {
const middlewareConfig: {
rootAssistant: RootAssistant<any>;
enhancer: StoreEnhancer;
middleware: Middleware;
} = {
rootAssistant: null,
enhancer: (createStore) => {
const newCreateStore: StoreEnhancerStoreCreator<{}, {}> = (
reducer,
preloadedState
) => {
const store = createStore<any, any>(reducer, preloadedState);
const actionsEmitter = new EventEmitter();
const dispatch = (action: any, ...args: any[]) => {
actionsEmitter.emit('before', action);
const result = (store.dispatch as any)(action, ...args);
actionsEmitter.emit('after', action);
return result;
};

const enhancedStore = { ...store, dispatch };

enhanceConfig.rootAssistant = createAssistant(
config,
() => enhancedStore.getState(),
enhancedStore.dispatch,
enhancedStore.subscribe,
actionsEmitter
);

return enhancedStore;
middleware: ({ dispatch, getState }) => {
const actionsEmitter = new EventEmitter();
middlewareConfig.rootAssistant = createAssistant(
config,
getState,
dispatch,
actionsEmitter
);

return (next) => (action) => {
const prevState = getState();
actionsEmitter.emit('before', action);

const result = next(action);

if (prevState !== getState()) {
actionsEmitter.emit('change');
}
actionsEmitter.emit('after', action);

return result;
};
return newCreateStore;
},
};

return enhanceConfig;
return middlewareConfig;
};
Loading

0 comments on commit 18f4f6a

Please sign in to comment.