Practice OOP to front-end universal state module with Redux/MobX/Vuex
2019-03-25
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 injection
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 { 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();
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
ReactDOM.render(
<Provider store={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.
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 {
@state
goods = [];
@state
status = 'close';
@action
operate(item, status, state) {
this.goods.push(item);
this.status = status;
}
// call function -> this.operate({ name: 'fruits', amount: 10 }, 'open');
}
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