usm icon indicating copy to clipboard operation
usm copied to clipboard

🏖 A concise & flexible state model for Redux/MobX/Vuex, etc.

USM

Node CI npm

USM is a universal state modular library, supports Redux(4.x), MobX(6.x), Vuex(4.x) and Angular(2.0+).

Support

Libraries/Frameworks None Redux MobX Vuex Angular2+
Status

Installation

To install usm:

yarn add usm # npm install --save usm

And if you want to use Redux/MobX/Vuex, you just install usm-redux/usm-mobx/usm-vuex.

Usage

  • Use @state to decorate a module state.

  • Use @action to decorate a module method for state changes.

  • Use createStore to create a store.

import { state, action, createStore } from 'usm';
// You can also use `usm-redux`, `usm-mobx`, or`usm-vuex`.

class Counter {
  @state
  count = { sum: 0 };

  @action
  increase() {
    this.count.sum += 1;
  }
}

const counter = new Counter();

const store = createStore({
  modules: [counter],
});

counter.increase();

const newState = Object.values(store.getState())[0] as Counter;
expect(newState.count).toEqual({ sum: 1 });

Examples

APIs

@state

Define a shared state for a module, and you can use @state for decoration. When use usm-redux, the state is not allowed to be undefined.

For example,

class Counter {
  @state
  number = 0;
}

@action

All operations that change state must be in a method decorated by @action.

For example,

class Counter {
  @state
  number = 0;

  @action
  increase() {
    this.number += 1;
  }
}

@computed/@computed()

It is used for computing derived data.

  • When use usm or usm-redux, you should use @computed(depsCallback), The return value of the depsCallback is an array of dependent value collections that tells the module that its getter will recompute when there is a change in any of the values in the value collections:

For example,

class Counter {
  @state
  count = { sum: 0 };

  @state
  number = 0;

  @action
  increase() {
    this.number += 1;
  }

  @computed((that) => [that.count.sum, that.number])
  get sum() {
    return this.count.sum + this.number;
  }
}
  • When use usm-mobx or usm-vuex, you just use @computed, Since it is an observable model, its dependency collection is automatic:

For example,

class Counter {
  @state
  count = { sum: 0 };

  @state
  number = 0;

  @action
  increase() {
    this.number += 1;
  }

- @computed((that) => [that.count.sum, that.number])
+ @computed
  get sum() {
    return this.count.sum + this.number;
  }
}

createStore()

Creates a usm store that holds the complete shared state.

Arguments

  • options(object)
    • modules(array): an array with all modules instances
    • [strict] (boolean): enable strict mode
  • [preloadedState] (any): preloaded state
  • [plugins/middleware] (any[]): vuex's plugins or redux's middleware

For example,

class Counter {
  @state
  number = 0;

  @action
  increase() {
    this.number += 1;
  }
}

const counter = new Counter();

const store = createStore({
  modules: [counter],
});

subscribe()

You can use subscribe() to subscribe state changes in any class module.

For example,

class Counter {
  constructor() {
    subscribe(this, () => {
      //
    });
  }

  @state
  count = { sum: 0 };
}

watch()

You can use watch() to observe a specific state changes in any class module.

For example,

class Counter {
  constructor() {
    watch(
      this,
      () => this.count.sum,
      (newValue, oldValue) => {
        //
      }
    );
  }

  @state
  count = { sum: 0 };
}

You can pass the option { multiple: true }, which will support watching multiple values.

For example,

class Counter {
  constructor() {
    watch(
      this,
      () => [this.count0, this.count1],
      ([newCount0, newCount1], [oldCount0, oldCount0]) => {
        //
      },
      {
        multiple: true,
      }
    );
  }

  @state
  count0 = 0;

  @state
  count1 = 0;
}

watch option supports passing in isEqual function for custom equal.