1.4.1 • Published 7 years ago

coeux v1.4.1

Weekly downloads
2
License
MIT
Repository
github
Last release
7 years ago

coeux

Build Status npm version npm downloads

Coeux is a state manager to build clean data flow like redux. In fact, it is inspired by redux. The biggest difference from redux is that coeux supports Promise in reducers and listeners.

Install

npm install coeux

Quick Start

import createStore from 'coeux';

let store = createStore();

store.mountReducer({
  foo: function (foo = 0, action) {
    switch (action.type) {
      case 'ADD': return new Promise(function (resolve) {
        resolve(++foo);
      });
      default: return foo;
    }
  }
});

store.subscribe({
  foo: function (foo) {
    return new Promise(function (resolve) {
      console.log(foo);
      resolve();
    });
  }
});

// always remember to call initState() after mountReducer() or subscribe()
// to ensure reducers has initialized state and listeners has catched the
// initializaion of state
store.initState().then(function () {
  // when code reaches here, console has shown 0
});
store.dispatch({ type: 'ADD' }).then(function () {
  // when code reaches here, console has shown 1
});
store.dispatch({ type: 'ADD' }).then(function () {
  // when code reaches here, console has shown 2
});

Sequence

We should care about sequence when async operations is supported in dispatching. Take two points in your mind:

  1. No absolute sequence between any two reducers or listeners.
  2. store.dispatch() runs in an atomic cycle. A store.dispatch() in another store.dispatch() will never mess the reducing sequence for the state.
store.mountReducer({
  foo: (foo, { type }) => {
    if (foo === undefined) {
      store.dispatch({ type: 'FOO' }).then(() => {
        // see foo as [ 1, 2 ] here
      });

      return [ 1 ];
    }

    switch (type) {
      case 'FOO':
        return [ ...foo, 2 ];
      default:
        return foo;
    }
  },
  bar: (bar, { type }) => {
    if (bar === undefined) {
      setTimeout(() => {
        store.dispatch({ type: 'BAR' }).then(() => {
          // see bar as [ 20, 10 ] here
        });
      }, 10);

      return new Promise(function (resolve) {
        setTimeout(() => resolve([ 20 ]), 20);
      });
    }

    switch (type) {
      case 'BAR':
        return [ ...bar, 10 ];
      default:
        return bar;
    }
  }
});

store.initState().then(() => {
  // see the whole state as { foo: [ 1 ], bar: [ 20 ] } here
});

Error Handling

Catch all errors thrown in reducers and listeners with Promise.catch().

store.mountReducer({
  foo: function (foo = 0, action) {
    return new Promise(function (resolve, reject) {
      reject(new Error('catch me'));
      //or
      throw new Error('catch me');
    });
  }
});

store.subscribe({
  foo: function (foo) {
    throw new Error('catch me');
  }
});

store.initState().catch(function (e) {
  console.log(e); // show error
});

Advanced Usage

dynamic reducers

Use reducers to describe a state tree. Mount or unmount them whenever you want.

store.mountReducer({
  foo: (foo = 0, action) => (foo),
  bar: {
    x: (x = 0, action) => (x)
  }
});
unmount2 = store.mountReducer({
  bar: {
    y: (y = 0, action) => (y)
  }
});

store.initState().then(() => {
  console.log(store.getState()); // { foo: 0, bar: { x: 0, y: 0 } }
});

unmount2();
store.initState().then(() => {
  console.log(store.getState()); // { foo: 0, bar: { x: 0 } }
});

smart notifying

Only notify listeners when states have been changed.

store.mountReducer({
  foo: function (foo = 0, action) {
    switch (action.type) {
      case 'ADD': return ++foo;
      default: return foo;
    }
  }
});

store.subscribe({
  foo: function (foo) {
    console.log(foo);
  }
});

store.initState().then(function () {
  // foo is from undefined to 0, changed, so meet foo's value here
});
store.initState().then(function () {
  // foo is not changed, so won't meet foo's value here
});
store.dispatch({ type: 'ADD' }).then(function () {
  // foo is from 0 to 1, changed, so meet foo's value here
});

// furtherly, listen to state with dynamic reducers
store.subscribe({
  bar: function (bar) {
    console.log(bar);
  }
});
store.initState().then(function () {
  // bar is still undefined, not changed, so won't meet bar's value here
});

unmount = store.mountReducer({
  bar: function (bar = 0, action) {
    return bar;
  }
});
store.initState().then(function () {
  // bar is from undefined to 0, changed, so meet bar's value here
});

unmount();
store.initState().then(function () {
  // bar is from 0 to undefined, changed, so meet bar's value here
});

hierarchical listeners

Listen to different layers easily.

store.mountReducer({
  foo: function (foo = 0, action) {
    switch (action.type) {
      case 'ADD': return ++foo;
      default: return foo;
    }
  },
  bar: function (bar = 0, action) {
    return bar;
  }
});

store.subscribe({
  bar: function (bar) {
    console.log(bar);
  }
});
store.subscribe(function (state) {
  console.log(state);
});

store.initState().then(() => {
  // show 0 and { foo: 0, bar: 0 }
});
store.dispatch({ type: 'ADD' }).then(() => {
  // only show { foo: 1, bar: 0 }
});

multiplex listener

Listen to multiple points in state tree. Within a dispatching cycle, the listener will be notified when any these listened point has been changed.

store.mountReducer({
  foo: (foo = 0) => ++foo,
  bar: (bar = 0) => bar
});

store.subscribe({
  foo: 'fooTag',
  bar: 'barTag'
}, function ({ fooTag, barTag }) {
  console.log(fooTag);
  console.log(barTag);
});

store.initState().then(() => {
  // show fooTag => 1
  // show barTag => 0
});
store.initState().then(() => {
  // show fooTag => 2
  // show barTag => 0
});

Middlewares

Support middlewares in dispatching. Pass them when you call createStore(). Additionally, Promise is supported in middlewares(This should be clear because next always return a Promise object as you will see).

let middlewares = [
  function (action, next) {
    if (action.type == 'SHORTCUT') {
      return action;
    }
    return next(action);
  },
  function (action, next) {
    console.log('before');
    // next always returns a Promise object
    return next(action).then((action) => {
      console.log('after');
      return action;
    });
  }
];

let store = createStore(middlewares);
store.initState().then(function () {
  // show before and after
});
store.dispatch({ type: 'SHORTCUT' }).then(function () {
  // show nothing
});

Performance

Everything would be OK if you are trying to keep state shape as flat as possible. You might have noticed that mountReducer() maintains a tree with reducer functions as its leaves. Internally, it uses a depth-first tree merging algorithm. The deeper reducer tree is, the more overhead it pays. Accordingly, unmount() function returned by mountReducer() preforms similarly.

1.4.1

7 years ago

1.4.0

7 years ago

1.3.0

7 years ago

1.2.0

7 years ago

1.1.0

7 years ago

1.0.2

8 years ago

1.0.1

8 years ago

1.0.0

8 years ago