20

Practice OOP to front-end universal state module with Redux/MobX/Vuex

 5 years ago
source link: https://www.tuicool.com/articles/hit/AR7vIvV
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Practice OOP to front-end universal state module with Redux/MobX/Vuex

MNzInyA.jpg!webqeUvuiQ.jpg!web

This is a proposal of an universal state management module design rooted in the OOP paradigm.

Motivation

As front-end single-page application development becomes increasingly complex, we have to use some state management or state containers (collectively referred to as state library) in order to develop complex apps, and we need a model design that is easier to modularize.

State management libraries are quite abundant in the front-end space, there are Redux , MobX , Vuex , and the self-contained state management that comes with Angular to name a few. Redux is a predictable state container with immutable data structure. MobX is a observable state management. Vuex is centralized state management with observable pattern for Vue.js. As for modularization, Angular has its own implementations already, but the rest of the state management libraries only started to deal with this new requirement in complex systems in recent years.

In this article, let’s explore an OOP modular design that has universal support for popular state management libraries.

Universal state module

Object-oriented programming(OOP) is commonly used in the architecture design of large projects in the front-end. The following questions are often asked when deciding on a state management library:

  • Is it Redux or MobX more suitable for React?
  • Is Redux suitable for OOP?
  • What are the pros and cons of using MobX’s observable in React?
  • How to do OOP with Vuex?

Typically front-end architectures are tightly coupled with state management. Once a state management library is selected, it is difficult to switch to another without major refactoring. So any system that uses such architecture will also have to use the same state library.

Better front-end architecture design should be flexible and scalable. Especially for designs that aim to fulfill integration purposes, where adapting to the target environments and sdk architectures is very important. In order to create modules that work with popular frameworks like React+Redux, React+MobX, Vue+Vuex, and Angular, we need an universal state module design.

Design Goals

  • Object Oriented
  • Simple & Flexible
  • Dependency Detection & Module Lifecycle

Proposal

Based on the concept of universalization, we propose a new Universal State Module library —— usm .

Let’s start with a typical Redux example of a counter:

import { createStore } from 'redux';
function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
}
const store = createStore(counter)
store.dispatch({ type: 'INCREMENT' })
store.dispatch({ type: 'DECREMENT' })

USM supports Redux, MobX, Vuex and Angular. It provides usm , usm-redux , usm-mobx and usm-vuex packages. Here is the same counter using usm-redux :

import Module, { state, action } from 'usm-redux';
class Counter extends Module {
  @state count = 0;
@action
  increase(state) {
    state.count += 1;
  }
@action
  decrease(state) {
    state.count -= 1;
  }
}
const counter = Counter.create();
counter.increase();
counter.decrease();

The implementation of the same counter above is based on object-oriented paradigm. The use of ES6 class syntax is intuitive and concise. If this design can be universal to any state management library used, it will undoubtedly lead to more flexible and friendly development experience for developers, as well as better readability and maintainability.

USM has four sub-packages, namly usm , usm-redux , usm-mobx and usm-vuex . usm-redux is used in this example, which is based on the use of Immer that enables modifying the immutable redux state in a mutable manner.

The following code demonstrate how the usm-redux module can be used with the connector from react-redux :

// index.js
export const counter = Counter.create();
ReactDOM.render(
  <Provider store={counter.store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
// app.js
import { connect } from 'react-redux';
import { counter } from './';
export default connect(
  state => ({ count: state.count })
)( props => 
  <div>
    <button onClick={() => counter.increase()}>+</button>
    {props.count}
    <button onClick={() => counter.decrease()}>-</button>
  </div>
);

And here is the same counter working with mobx-react using usm-mobx :

// index.js
export const counter = Counter.create();
ReactDOM.render(
  <App />,
  document.getElementById('root')
);
// app.js
import { observer } from 'mobx-react';
import { counter } from './';
export default observer(() =>
  <div>
    <button onClick={() => counter.increase()}>+</button>
    {counter.count}
    <button onClick={() => counter.decrease()}>-</button>
  </div>
);

The use of usm-redux and usm-mobx to connect with react-redux and mobx-react respectfully demonstrated that the core implementations of the state module is the same even when the connectors used are different. This is the core principle of the Universal State Module that we propose.

** USM currently supports Redux, MobX, Vuex and Angular.**

Features

  • Universal State Management
  • Standardized Module Lifecycle
  • Optional Event System
  • Support Stateless Model
  • Support React/Vue/Angular

Decorators

usm provides decorator @state to wrap a variable with a state, and decorator @action is used to wrap a function that changes state.

class Shop extends Module {
  @state goods = [];
  @state status = 'close';
@action
  operate(item, status, state) {
    state.goods.push(item);
    state.status = status;
  }
  // this.operate({ name: 'fruits', amount: 10 }, 'open');
}

Module lifecycle

usm provides these lifecycle events:

moduleWillInitialize
moduleWillInitializeSuccess
moduleDidInitialize
moduleWillReset
moduleDidReset

The order in which they are run is shown in the following flow chart:

AfMviuV.png!webeQBzayN.png!web

These module lifecycles can be used to coordinate module initialization dependencies.

class TodoList extends Module {  
  async moduleWillInitialize() {
    console.log(
      'TodoList -> moduleWillInitialize',
      `pending: ${this.pending}`, `ready: ${this.ready}`
    );
  }
async moduleWillInitializeSuccess() {
    console.log(
      'TodoList -> moduleWillInitializeSuccess',
      `pending: ${this.pending}`, `ready: ${this.ready}`
      );
  }
async moduleDidInitialize() {
    console.log(
      'TodoList -> moduleDidInitialize',
      `pending: ${this.pending}`, `ready: ${this.ready}`
      );
  }
}

class App extends Module{
  async moduleWillInitialize() {
    console.log(
      'App -> moduleWillInitialize',
      `pending: ${this.pending}`, `ready: ${this.ready}`
    );
  }
async moduleWillInitializeSuccess() {
    console.log(
      'App -> moduleWillInitializeSuccess',
      `pending: ${this.pending}`, `ready: ${this.ready}`
    );
  }
async moduleDidInitialize() {
    console.log(
      'App -> moduleDidInitialize',
      `pending: ${this.pending}`, `ready: ${this.ready}`
    );
  }
}
const todoList = new TodoList();
const app = App.create({
  modules: {
    todoList
  }
});

console.log results:

App -> moduleWillInitialize pending: false ready: false
TodoList -> moduleWillInitialize pending: false ready: false
TodoList -> moduleWillInitializeSuccess pending: true ready: false
App -> moduleWillInitializeSuccess pending: true ready: false
TodoList -> moduleDidInitialize pending: false ready: true
App -> moduleDidInitialize pending: false ready: true

An ideal architecture

U7RZneA.png!webE3IraiJ.png!web

In a complex front-end application, a typical modular architecture may contain the following:

  • Lifecycle
  • Store Subscriber
  • Event System
  • State
  • Module dependency
  • Domain Models

Conclusion

usm is a module design that wants to bridge together the differences of using Redux, Mobx, and Vuex in conjunction with different view layers such as React, Vue and Angular. It is designed to help you build libraries that will work with any front-end architecture.

Modules built with usm should be free of boilerplates, especially the type that is introduced by state libraries like Redux. More importantly, the object-oriented nature of usm makes modules simple and intuitive. usm also makes your modules compatible with various state libraries and view layers, allowing you to share your business logic libraries across projects regardless of the frameworks they are using.

USM’s repo: https://github.com/unadlib/usm


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK