add reactions to the updux
This commit is contained in:
parent
0f3aeff396
commit
702a3e76d4
68
src/Updux.js
68
src/Updux.js
@ -4,7 +4,7 @@ import { createStore as reduxCreateStore, applyMiddleware } from 'redux';
|
|||||||
|
|
||||||
import { buildSelectors } from './selectors.js';
|
import { buildSelectors } from './selectors.js';
|
||||||
import { buildUpreducer } from './upreducer.js';
|
import { buildUpreducer } from './upreducer.js';
|
||||||
import { buildMiddleware } from './middleware.js';
|
import { buildMiddleware, augmentMiddlewareApi } from './middleware.js';
|
||||||
import { action, isActionGen } from './actions.js';
|
import { action, isActionGen } from './actions.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,6 +21,7 @@ export class Updux {
|
|||||||
#config = {};
|
#config = {};
|
||||||
#selectors = {};
|
#selectors = {};
|
||||||
#effects = [];
|
#effects = [];
|
||||||
|
#localReactions = [];
|
||||||
|
|
||||||
constructor(config = {}) {
|
constructor(config = {}) {
|
||||||
this.#config = config;
|
this.#config = config;
|
||||||
@ -49,6 +50,8 @@ export class Updux {
|
|||||||
} else if (R.isObject(config.effects)) {
|
} else if (R.isObject(config.effects)) {
|
||||||
this.#effects = Object.entries(config.effects);
|
this.#effects = Object.entries(config.effects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.#localReactions = config.reactions ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
#addSubduxActions(_slice, subdux) {
|
#addSubduxActions(_slice, subdux) {
|
||||||
@ -171,7 +174,7 @@ export class Updux {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//this.subscribeAll(store);
|
this.subscribeAll(store);
|
||||||
|
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
@ -179,6 +182,67 @@ export class Updux {
|
|||||||
addEffect(action, effect) {
|
addEffect(action, effect) {
|
||||||
this.#effects = [...this.#effects, [action, effect]];
|
this.#effects = [...this.#effects, [action, effect]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addReaction( reaction ) {
|
||||||
|
this.#localReactions = [ ...this.#localReactions, reaction ]
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeTo(store, subscription) {
|
||||||
|
const localStore = augmentMiddlewareApi(
|
||||||
|
{
|
||||||
|
...store,
|
||||||
|
subscribe: (subscriber) =>
|
||||||
|
this.subscribeTo(store, subscriber), // TODO not sure
|
||||||
|
},
|
||||||
|
this.actions,
|
||||||
|
this.selectors
|
||||||
|
);
|
||||||
|
|
||||||
|
const subscriber = subscription(localStore);
|
||||||
|
|
||||||
|
let previous;
|
||||||
|
|
||||||
|
const memoSub = () => {
|
||||||
|
const state = store.getState();
|
||||||
|
if (state === previous) return;
|
||||||
|
let p = previous;
|
||||||
|
previous = state;
|
||||||
|
subscriber(state, p, unsub);
|
||||||
|
};
|
||||||
|
|
||||||
|
let ret = store.subscribe(memoSub);
|
||||||
|
const unsub = typeof ret === 'function' ? ret : ret.unsub;
|
||||||
|
return {
|
||||||
|
unsub,
|
||||||
|
subscriberMemoized: memoSub,
|
||||||
|
subscriber,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeAll(store) {
|
||||||
|
let results = this.#localReactions.map((sub) =>
|
||||||
|
this.subscribeTo(store, sub)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const subdux in this.#subduxes) {
|
||||||
|
if (subdux !== '*') {
|
||||||
|
const localStore = {
|
||||||
|
...store,
|
||||||
|
getState: () => store.getState()[subdux],
|
||||||
|
};
|
||||||
|
results.push(this.#subduxes[subdux].subscribeAll(localStore));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
unsub: () => results.forEach(({ unsub }) => unsub()),
|
||||||
|
subscriberMemoized: () =>
|
||||||
|
results.forEach(({ subscriberMemoized}) => subscriberMemoized()),
|
||||||
|
subscriber: () =>
|
||||||
|
results.forEach(({ subscriber }) => subscriber()
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dux = (config) => new Updux(config);
|
export const dux = (config) => new Updux(config);
|
||||||
|
50
src/reactions.test.js
Normal file
50
src/reactions.test.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { test, expect, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { Updux } from './Updux.js';
|
||||||
|
|
||||||
|
test('basic reactions', async () => {
|
||||||
|
const spyA = vi.fn();
|
||||||
|
const spyB = vi.fn();
|
||||||
|
const foo = new Updux({
|
||||||
|
initial: { i: 0 },
|
||||||
|
reactions: [() => spyA],
|
||||||
|
actions: { inc: null },
|
||||||
|
mutations: {
|
||||||
|
inc: () => (state) => ({ ...state, i: state.i + 1 }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
foo.addReaction((api) => spyB);
|
||||||
|
|
||||||
|
const store = foo.createStore();
|
||||||
|
store.dispatch.inc();
|
||||||
|
|
||||||
|
expect(spyA).toHaveBeenCalledOnce();
|
||||||
|
expect(spyB).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('subduxes reactions', async () => {
|
||||||
|
const spyA = vi.fn();
|
||||||
|
const spyB = vi.fn();
|
||||||
|
const foo = new Updux({
|
||||||
|
subduxes: {
|
||||||
|
a: new Updux({
|
||||||
|
initial: 1,
|
||||||
|
reactions: [() => state => spyA(state)],
|
||||||
|
actions: { inc: null },
|
||||||
|
mutations: {
|
||||||
|
inc: () => (state) => state + 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
b: new Updux({ initial: 10, reactions: [() => spyB] }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = foo.createStore();
|
||||||
|
store.dispatch.inc();
|
||||||
|
store.dispatch.inc();
|
||||||
|
|
||||||
|
expect(spyA).toHaveBeenCalledTimes(2);
|
||||||
|
expect(spyA).toHaveBeenCalledWith(3)
|
||||||
|
expect(spyB).toHaveBeenCalledOnce(); // the original inc initialized the state
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user