documentation, actions
This commit is contained in:
parent
2e357b71e2
commit
9778e04a8d
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -89,4 +89,4 @@ well with <a href="https://immerjs.github.io/immer/docs/introduction">Immer</a>.
|
||||
can be used to wrap all mutations with it:</p>
|
||||
<pre><code><span class="hl-0">import</span><span class="hl-1"> </span><span class="hl-2">Updux</span><span class="hl-1"> </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">'updux'</span><span class="hl-1">;</span><br/><span class="hl-0">import</span><span class="hl-1"> { </span><span class="hl-2">produce</span><span class="hl-1"> } </span><span class="hl-0">from</span><span class="hl-1"> </span><span class="hl-3">'Immer'</span><span class="hl-1">;</span><br/><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-5">updux</span><span class="hl-1"> = </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Updux</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">initial:</span><span class="hl-1"> { </span><span class="hl-2">counter:</span><span class="hl-1"> </span><span class="hl-7">0</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-6">groomMutations</span><span class="hl-2">:</span><span class="hl-1"> </span><span class="hl-2">mutation</span><span class="hl-1"> </span><span class="hl-4">=></span><span class="hl-1"> (...</span><span class="hl-2">args</span><span class="hl-1">) </span><span class="hl-4">=></span><span class="hl-1"> </span><span class="hl-6">produce</span><span class="hl-1">( </span><span class="hl-6">mutation</span><span class="hl-1">(...</span><span class="hl-2">args</span><span class="hl-1">) ),</span><br/><span class="hl-1"> </span><span class="hl-2">mutations:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-6">add</span><span class="hl-2">:</span><span class="hl-1"> (</span><span class="hl-2">inc</span><span class="hl-1">=</span><span class="hl-7">1</span><span class="hl-1">) </span><span class="hl-4">=></span><span class="hl-1"> </span><span class="hl-2">draft</span><span class="hl-1"> </span><span class="hl-4">=></span><span class="hl-1"> </span><span class="hl-2">draft</span><span class="hl-1">.</span><span class="hl-2">counter</span><span class="hl-1"> += </span><span class="hl-2">inc</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">});</span><br/>
|
||||
</code></pre>
|
||||
</div></div><div class="col-4 col-menu menu-sticky-wrap menu-highlight"><nav class="tsd-navigation primary"><ul><li class="current"><a href="modules.html">Exports</a></li><li class=" tsd-kind-namespace"><a href="modules/_updux_.html">"updux"</a></li></ul></nav><nav class="tsd-navigation secondary menu-sticky"><ul><li class="tsd-kind-type-alias tsd-has-type-parameter"><a href="modules.html#Dict" class="tsd-kind-icon">Dict</a></li></ul></nav></div></div></div><footer class="with-border-bottom"><div class="container"><h2>Settings</h2><p>Theme <select id="theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></p></div></footer><div class="container tsd-generator"><p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></div><div class="overlay"></div><script src="assets/main.js"></script></body></html>
|
||||
</div></div><div class="col-4 col-menu menu-sticky-wrap menu-highlight"><nav class="tsd-navigation primary"><ul><li class="current"><a href="modules.html">Exports</a></li><li class=" tsd-kind-namespace"><a href="modules/_updux_.html">"updux"</a></li></ul></nav><nav class="tsd-navigation secondary menu-sticky"><ul><li class="tsd-kind-type-alias"><a href="modules.html#ActionGenerator" class="tsd-kind-icon">Action<wbr/>Generator</a></li><li class="tsd-kind-type-alias tsd-has-type-parameter"><a href="modules.html#Dict" class="tsd-kind-icon">Dict</a></li><li class="tsd-kind-type-alias tsd-has-type-parameter"><a href="modules.html#Mutation" class="tsd-kind-icon">Mutation</a></li></ul></nav></div></div></div><footer class="with-border-bottom"><div class="container"><h2>Settings</h2><p>Theme <select id="theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></p></div></footer><div class="container tsd-generator"><p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></div><div class="overlay"></div><script src="assets/main.js"></script></body></html>
|
File diff suppressed because one or more lines are too long
@ -1,23 +1,21 @@
|
||||
|
||||
# What's Updux?
|
||||
## What's Updux?
|
||||
|
||||
So, I'm a fan of [Redux](https://redux.js.org).
|
||||
|
||||
As I was looking into tools to help cut on its boilerplate,
|
||||
I came across [rematch](https://rematch.github.io/rematch).
|
||||
It has some pretty darn good ideas.
|
||||
Keeping mutations and asynchronous effects close to the
|
||||
reducer definition? Nice. Automatically infering the
|
||||
actions from the said mutations and effects? Genius!
|
||||
|
||||
But it also enforces a flat hierarchy of reducers -- where
|
||||
is the fun in that? And I'm also having a strong love for
|
||||
[Updeep](https://github.com/substantial/updeep), so I want reducer state updates to leverage the heck out of it.
|
||||
is the fun in that?
|
||||
|
||||
I'm also having a strong love for
|
||||
[Updeep](https://github.com/substantial/updeep), so I wanted a framework where
|
||||
I could use it to define reducer state updates.
|
||||
|
||||
Hence: `Updux`. Heavily inspired by `rematch`, but twisted
|
||||
to work with `updeep` and to fit my peculiar needs. It offers features such as
|
||||
|
||||
* Mimic the way VueX has mutations (reducer reactions to specific actions) and
|
||||
* Mimic the way VueX has mutations (per-action reducer logic) and
|
||||
effects (middleware reacting to actions that can be asynchronous and/or
|
||||
have side-effects), so all things pertaining to a store are defined
|
||||
in the space place.
|
||||
@ -26,9 +24,7 @@ to work with `updeep` and to fit my peculiar needs. It offers features such as
|
||||
store.
|
||||
* Mutations have a signature that is friendly to Updux and Immer.
|
||||
* Mutations auto-unwrapping the payload of actions for you.
|
||||
* TypeScript types.
|
||||
* Leverage [ts-action](https://www.npmjs.com/package/ts-action) for action
|
||||
creation.
|
||||
* TypeScript support.
|
||||
|
||||
**Fair warning**: this package is still very new, likely to go through
|
||||
big changes before I find the perfect balance between ease of use and sanity.
|
||||
@ -36,70 +32,36 @@ Caveat Emptor.
|
||||
|
||||
# Synopsis
|
||||
|
||||
```
|
||||
import Updux from 'updux';
|
||||
import { action, payload } from 'ts-action';
|
||||
```js
|
||||
import { Updux } from 'updux';
|
||||
import u from 'updeep';
|
||||
import add from 'lodash/fp/add.js';
|
||||
|
||||
import otherDux from './otherUpdux';
|
||||
|
||||
const inc = action( 'INC', payload<int>() );
|
||||
|
||||
const updux = new Updux({
|
||||
const dux = new Updux({
|
||||
initial: {
|
||||
counter: 0,
|
||||
},
|
||||
actions: {
|
||||
inc
|
||||
inc: null
|
||||
},
|
||||
subduxes: {
|
||||
otherDux,
|
||||
}
|
||||
});
|
||||
|
||||
updux.addMutation( inc, increment => u({counter: s => s + increment }));
|
||||
dux.setMutation('inc', (increment) => u({ counter: add(increment) }));
|
||||
|
||||
updux.addEffect( '*', api => next => action => {
|
||||
dux.addEffect( '*', api => next => action => {
|
||||
console.log( "hey, look, an action zoomed by!", action );
|
||||
next(action);
|
||||
} );
|
||||
|
||||
const myDux = updux.asDux;
|
||||
const store = dux.createStore();
|
||||
|
||||
const store = myDux.createStore();
|
||||
store.dispatch.inc(1);
|
||||
|
||||
store.dispatch( myDux.actions.inc(3) );
|
||||
console.log( store.getState().counter ); // prints 1
|
||||
```
|
||||
|
||||
# Description
|
||||
|
||||
The formal documentation of the class Updux and its associated functions and
|
||||
types can be found over [here](https://yanick.github.io/updux/docs/classes/updux.html).
|
||||
|
||||
## Exporting upduxes
|
||||
|
||||
If you are creating upduxes that will be used as subduxes
|
||||
by other upduxes, or as
|
||||
[ducks](https://github.com/erikras/ducks-modular-redux)-like containers, I
|
||||
recommend that you export the "compiled" (as in, no more editable and with all its properties resolved) output of the Updux instance via its `asDux()` getter:
|
||||
|
||||
```
|
||||
import Updux from 'updux';
|
||||
|
||||
const updux = new Updux({ ... });
|
||||
|
||||
export default updux.asDux;
|
||||
```
|
||||
|
||||
Then you can use them as subduxes like this:
|
||||
|
||||
```
|
||||
import Updux from 'updux';
|
||||
import foo from './foo'; // foo is a dux
|
||||
import bar from './bar'; // bar is a dux as well
|
||||
|
||||
const updux = new Updux({
|
||||
subduxes: {
|
||||
foo, bar
|
||||
}
|
||||
});
|
||||
```
|
||||
|
@ -1,4 +1,4 @@
|
||||
- [README](/README.md)
|
||||
- [Intro](/README.md)
|
||||
- [Tutorial](/tutorial.md)
|
||||
- [Concepts](/concepts.md)
|
||||
- [Recipes](/recipes.md)
|
||||
|
@ -3,72 +3,45 @@
|
||||
This tutorial walks you through the features of `Updux` using the
|
||||
time-honored example of the implementation of Todo list store.
|
||||
|
||||
This tutorial assumes that our project is written in TypeScript, and
|
||||
that we are using [updeep](https://www.npmjs.com/package/updeep) to
|
||||
help with immutability and deep merging and [ts-action][] to manage our
|
||||
actions. This is the recommended setup, but
|
||||
neither of those two architecture
|
||||
decisions are mandatory; Updux is equally usable in a pure-JavaScript setting,
|
||||
and `updeep` can easily be substitued with, say, [immer][], [lodash][], or even
|
||||
just plain JavaScript. Eventually, I plan to write a version of this tutorial
|
||||
with all those different configurations.
|
||||
|
||||
Also, the code used here is also available in the project repository, in the
|
||||
`src/tutorial` directory.
|
||||
We'll be using
|
||||
[updeep](https://www.npmjs.com/package/updeep) to
|
||||
help with immutability and deep merging,
|
||||
but that's totally optional. If `updeep` is not your bag,
|
||||
it can easily be substitued with, say, [immer][], [lodash][], or even
|
||||
plain JavaScript.
|
||||
|
||||
## Definition of the state
|
||||
|
||||
First thing first: let's define the type of our store:
|
||||
To begin with, let's define that has nothing but an initial state.
|
||||
|
||||
```
|
||||
type Todo = {
|
||||
id: number;
|
||||
description: string;
|
||||
done: boolean;
|
||||
};
|
||||
```js
|
||||
import { Updux } from 'updux';
|
||||
|
||||
type TodoStore = {
|
||||
next_id: number;
|
||||
todos: Todo[];
|
||||
};
|
||||
```
|
||||
|
||||
With that, let's create our very first Updux:
|
||||
|
||||
```
|
||||
import Updux from 'updux';
|
||||
|
||||
const todosUpdux = new Updux({
|
||||
const todosDux = new Updux({
|
||||
initial: {
|
||||
next_id: 1,
|
||||
todos: [],
|
||||
} as TodoStore
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Note that we explicitly cast the initial state as `as TodoStore`. This lets
|
||||
Updux know what is the store's state.
|
||||
|
||||
This being said, congrats! You have written your first Updux object. It
|
||||
Congrats! You have written your first Updux object. It
|
||||
doesn't do a lot, but you can already create a store out of it, and its
|
||||
initial state will be automatically set:
|
||||
|
||||
```
|
||||
const store = todosUpdux.createStore();
|
||||
```js
|
||||
const store = todosDux.createStore();
|
||||
|
||||
console.log(store.getState());
|
||||
// { next_id: 1, todos: [] }
|
||||
console.log(store.getState()); // prints { next_id: 1, todos: [] }
|
||||
```
|
||||
|
||||
## Add actions
|
||||
|
||||
This is all good, but a little static. Let's add actions!
|
||||
|
||||
```
|
||||
import { action, payload } from 'ts-action';
|
||||
|
||||
const add_todo = action('add_todo', payload<string>() );
|
||||
const todo_done = action('todo_done', payload<number>() );
|
||||
```js
|
||||
todosDux.setAction( 'addTodo' );
|
||||
todosDux.setAction( 'todoDone' );
|
||||
```
|
||||
|
||||
Now, there is a lot of ways to add actions to a Updux object.
|
||||
|
@ -128,7 +128,7 @@ export class Updux {
|
||||
this.#subscriptions = [...this.#subscriptions, subscription];
|
||||
}
|
||||
|
||||
addAction(type, payloadFunc) {
|
||||
setAction(type, payloadFunc) {
|
||||
const theAction = action(type, payloadFunc);
|
||||
|
||||
this.#actions = { ...this.#actions, [type]: theAction };
|
||||
@ -144,7 +144,7 @@ export class Updux {
|
||||
return func;
|
||||
}
|
||||
|
||||
addMutation(name, mutation) {
|
||||
setMutation(name, mutation) {
|
||||
if (typeof name === 'function') name = name.type;
|
||||
|
||||
this.#mutations = {
|
||||
@ -152,7 +152,7 @@ export class Updux {
|
||||
[name]: mutation,
|
||||
};
|
||||
|
||||
return this;
|
||||
return mutation;
|
||||
}
|
||||
|
||||
addEffect(action, effect) {
|
||||
@ -304,6 +304,3 @@ export class Updux {
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
const x = new Updux();
|
||||
x.selectors;
|
||||
|
@ -45,13 +45,13 @@ test('basic actions', async (t) => {
|
||||
t.same(Object.keys(dux.actions).sort(), ['bar', 'foo']);
|
||||
});
|
||||
|
||||
test('addAction', async (t) => {
|
||||
test('setAction', async (t) => {
|
||||
const dux = new Updux({
|
||||
actions: {
|
||||
bar: action('bar'),
|
||||
},
|
||||
});
|
||||
dux.addAction('foo');
|
||||
dux.setAction('foo');
|
||||
|
||||
t.same(Object.keys(dux.actions).sort(), ['bar', 'foo']);
|
||||
});
|
||||
@ -81,7 +81,7 @@ test('basic selectors', async (t) => {
|
||||
(add) =>
|
||||
add + foo
|
||||
);
|
||||
dux.addAction('stuff');
|
||||
dux.setAction('stuff');
|
||||
|
||||
t.equal(dux.selectors.getBar({ bar: 3 }), 3);
|
||||
t.equal(dux.selectors.getFoo({ foo: 3 }), 3);
|
||||
@ -100,8 +100,8 @@ test('mutations', async (t) => {
|
||||
const alpha = new Updux({
|
||||
initial: { quux: 3 },
|
||||
});
|
||||
alpha.addAction('add');
|
||||
alpha.addMutation('add', (toAdd) => (state) => ({
|
||||
alpha.setAction('add');
|
||||
alpha.setMutation('add', (toAdd) => (state) => ({
|
||||
quux: state.quux + toAdd,
|
||||
}));
|
||||
|
||||
@ -112,12 +112,12 @@ test('mutations', async (t) => {
|
||||
},
|
||||
subduxes: { alpha },
|
||||
});
|
||||
dux.addAction('subtract');
|
||||
dux.addMutation('add', (toAdd) => (state) => ({
|
||||
dux.setAction('subtract');
|
||||
dux.setMutation('add', (toAdd) => (state) => ({
|
||||
...state,
|
||||
foo: state.foo + toAdd,
|
||||
}));
|
||||
dux.addMutation('subtract', (toSubtract) => (state) => ({
|
||||
dux.setMutation('subtract', (toSubtract) => (state) => ({
|
||||
...state,
|
||||
bar: state.bar - toSubtract,
|
||||
}));
|
||||
|
48
src/documentation.test.js
Normal file
48
src/documentation.test.js
Normal file
@ -0,0 +1,48 @@
|
||||
import { test } from 'tap';
|
||||
import u from 'updeep';
|
||||
import add from 'lodash/fp/add.js';
|
||||
|
||||
import { Updux } from './index.js';
|
||||
|
||||
test('README.md', async (t) => {
|
||||
const otherDux = new Updux({});
|
||||
|
||||
const dux = new Updux({
|
||||
initial: {
|
||||
counter: 0,
|
||||
},
|
||||
actions: {
|
||||
inc: null,
|
||||
},
|
||||
subduxes: {
|
||||
otherDux,
|
||||
},
|
||||
});
|
||||
|
||||
dux.setMutation('inc', (increment) => u({ counter: add(increment) }));
|
||||
|
||||
dux.addEffect('*', (api) => (next) => (action) => {
|
||||
next(action);
|
||||
});
|
||||
|
||||
const store = dux.createStore();
|
||||
|
||||
store.dispatch.inc(1);
|
||||
|
||||
t.equal(store.getState().counter, 1);
|
||||
});
|
||||
|
||||
|
||||
test('tutorial', async (t) => {
|
||||
|
||||
const todosDux = new Updux({
|
||||
initial: {
|
||||
next_id: 1,
|
||||
todos: [],
|
||||
}
|
||||
});
|
||||
|
||||
todosDux.setAction( 'addTodo' );
|
||||
todosDux.setAction( 'todoDone' );
|
||||
|
||||
})
|
16
src/index.js
16
src/index.js
@ -1,16 +1,2 @@
|
||||
import Updux from './Updux.js';
|
||||
export { Updux } from './Updux.js';
|
||||
|
||||
export {
|
||||
/**
|
||||
* The {@link Updux} class
|
||||
*/
|
||||
Updux
|
||||
};
|
||||
|
||||
/**
|
||||
* `Updux` is a way to minimize and simplify the boilerplate associated with the
|
||||
* creation of a `Redux` store. It takes a shorthand configuration
|
||||
* object, and generates the appropriate reducer, actions, middleware, etc.
|
||||
* In true `Redux`-like fashion, upduxes can be made of sub-upduxes (`subduxes` for short) for different slices of the root state.
|
||||
* @module updux
|
||||
*/
|
||||
|
@ -81,7 +81,7 @@ test('order of processing', async (t) => {
|
||||
t.same(dux.reducer(undefined, foo()), { x: ['subdux', 'main'] });
|
||||
});
|
||||
|
||||
test('addMutation', async (t) => {
|
||||
test('setMutation', async (t) => {
|
||||
const foo = action('foo');
|
||||
|
||||
const dux = new Updux({
|
||||
@ -90,13 +90,13 @@ test('addMutation', async (t) => {
|
||||
|
||||
t.equal(dux.reducer(undefined, foo()), '', 'noop');
|
||||
|
||||
dux.addMutation('foo', () => () => 'foo');
|
||||
dux.setMutation('foo', () => () => 'foo');
|
||||
|
||||
t.equal(dux.reducer(undefined, foo()), 'foo', 'foo was added');
|
||||
|
||||
await t.test('name as function', async (t) => {
|
||||
const bar = action('bar');
|
||||
dux.addMutation(bar, () => () => 'bar');
|
||||
dux.setMutation(bar, () => () => 'bar');
|
||||
|
||||
t.equal(dux.reducer(undefined, bar()), 'bar', 'bar was added');
|
||||
});
|
||||
|
37
types/index.d.ts
vendored
37
types/index.d.ts
vendored
@ -1,5 +1,15 @@
|
||||
type Dict<T> = Record<string, T>;
|
||||
|
||||
type Mutation<TState = unknown> = (
|
||||
payload: unknown,
|
||||
action: Record<string, unknown>
|
||||
) => (state: TState) => TState;
|
||||
|
||||
type ActionGenerator = { type: string } & ((...args: any[]) => {
|
||||
type: string;
|
||||
payload?: unknown;
|
||||
});
|
||||
|
||||
declare module 'updux' {
|
||||
/**
|
||||
* Configuration object typically passed to the constructor of the class Updux.
|
||||
@ -14,7 +24,7 @@ declare module 'updux' {
|
||||
/**
|
||||
* Subduxes to be merged to this dux.
|
||||
*/
|
||||
subduxes?: Dict<Updux|UpduxConfig>;
|
||||
subduxes?: Dict<Updux | UpduxConfig>;
|
||||
|
||||
/**
|
||||
* Local actions.
|
||||
@ -59,5 +69,30 @@ declare module 'updux' {
|
||||
|
||||
get initial(): TState;
|
||||
get selectors(): unknown;
|
||||
|
||||
/**
|
||||
* Sets the local mutation for the given action.
|
||||
*
|
||||
* The action can be specified via its type
|
||||
* or its generator.
|
||||
*
|
||||
* Returns the same mutation function.
|
||||
*/
|
||||
setMutation<TMutation extends Mutation>(
|
||||
actionType: string,
|
||||
mutation: TMutation
|
||||
): TMutation;
|
||||
setMutation<TMutation extends Mutation>(
|
||||
action: ActionGenerator,
|
||||
mutation: TMutation
|
||||
): TMutation;
|
||||
|
||||
/**
|
||||
* Registers the action for the dux.
|
||||
* If no payload function is provided, whatever is
|
||||
* given as an argument to the action generator will
|
||||
* be set as-is in the action's payload.
|
||||
*/
|
||||
setAction(actionType: string, payloadFunc?: Function);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user