in the past year, more and more projects continue or begin to use React and Redux development, which is a common front-end project solution in the front-end industry, but as there are more and more development projects, individuals have different feelings and ideas. Is it because we already have a relatively common and familiar technology stack of the project that we have been using it completely? is there a more suitable solution than it? Just as the team recently had a new project, bloggers began to wonder if it was possible to develop using alternative technologies. It can not only improve the development efficiency, but also expand the technology reserves and horizons. After research, we chose Mobx, and finally built a new project using React+Mobx. This article summarizes and shares the more complete process from technology selection to project implementation, hoping to make common progress.
Preface
when we use React to develop web applications, within React components, we can use this.setState ()
and this.state
to process or access intra-component states, but as the project grows larger and the states become more complex, we usually need to consider the problem of communication between components, which mainly includes the following two points:
- A state needs to be shared (accessed, updated) among multiple components;
- interaction within one component needs to trigger status updates for other components;
with regard to these issues, React component development practices recommend improving the state of common components:
Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor
usually multiple components need to deal with the same state, and we recommend that you promote the shared state to their common nearest ancestor component.
as the project becomes more complex, we find that just improving the state can no longer adapt to such complex state management, program states become more difficult to synchronize, operations, and there are callbacks, releases, and subscriptions everywhere. this means that we need a better way of state management, so the state management library is introduced. For example, Redux , Mobx Jumpsuit , Alt.js , etc.
status Management
State management libraries, whether Redux or Mobx, are essentially designed to solve the problem of chaotic state management and inability to effectively synchronize. They all support:
- maintain and manage the application status uniformly;
- A state has only one trusted data source (usually named store, which refers to the state container);
- the operation to update status is uniform and controllable (usually provides a way to update status in action);
- supports connecting store and React components, such as
react-redux
,mobx-react
. Usually, after using the state management library, we divide React components into two business categories:- Container component (Container Components): responsible for handling specific business and state data, passing business or state handling functions into display components;
- display component (Presentation Components): responsible for displaying the view, calling the incoming processing function in the interactive callback of the view;
Mobx VS Redux
at present, Redux is the dominant React application state management library, while Mobx is a vassal, so why should we choose Mobx instead of continuing to use Redux? we need to compare their similarities and differences.
Mobx and Redux are both JavaScript application state management libraries, which are suitable for frameworks or libraries such as React,Angular,VueJs, rather than being limited to a particular UI library.
Redux
to introduce Redux, we have to talk about Flux:
Flux is the application architecture that Facebook uses for building client-side web applications.It's more of a pattern rather than a formal framework
Flux is the application architecture that Facebook uses to develop client-server web applications. It is more of an architectural pattern than a specific framework.
while Redux is more of an implementation that follows the Flux pattern, it is a JavaScript library that focuses on the following aspects:
- Action: a JavaScript object that describes action-related information, including the type attribute and the payload attribute:
- type:action type;
- payload: payload data;
- Reducer: defines how the application state responds to different actions (action) and how to update the status;
- Store: objects that manage action and reducer and their relationships, mainly providing the following functions:
- maintain application state and support access state (getState ());
- supports monitoring action distribution and updating status (dispatch (action));
- supports changes to subscription store (subscribe (listener));
- Asynchronous flow: since all Redux changes to store status should be triggered through action, asynchronous tasks (usually business or data acquisition tasks) are no exception. In order not to mix business or data-related tasks into React components, you need to use other frameworks to manage asynchronous task flows, such as
redux-thunk
,redux-saga
, etc.
Mobx
Mobx is a transparent functional responsive programming (Transparently Functional Reactive Programming,TFRP) state management library that makes state management simple and scalable:
Anything that can be derived from the application state, should be derived. Automatically.
any data originating from the application state should be obtained automatically.
its principle is shown in figure
-
Action: defines the action function to change the state, including how to change the state;
-
Store: centralized management of module status (State) and actions (action);
-
Derivation (derivation): data derived from the application state without any other influence is called derivation, and derivation exists in the following cases:
-
user interface;
-
derived data;
there are two main types of derivation:
- Computed Values (calculated value): calculated value can always be obtained from the current observable state using pure function (pure function);
- Reactions (reaction): reaction refers to the side effect that occurs automatically when the state changes. In this case, we need to implement its read and write operations.
-
import {observable, autorun} from 'mobx';
var todoStore = observable({
/* some observable state */
todos: [],
/* a derived value */
get completedCount() {
return this.todos.filter(todo => todo.completed).length;
}
});
/* a function that observes the state */
autorun(function() {
console.log("Completed %d of %d items",
todoStore.completedCount,
todoStore.todos.length
);
});
/* ..and some actions that modify the state */
todoStore.todos[0] = {
title: "Take a walk",
completed: false
};
// -> synchronously prints: 'Completed 0 of 1 items'
todoStore.todos[0].completed = true;
// -> synchronously prints: 'Completed 1 of 1 items'
functional and object-oriented
Redux is more likely to follow the idea of functional programming (Functional Programming, FP), while Mobx is more concerned with problems from the point of view of face objects.
Redux advocates writing functional code, such as reducer is a pure function (pure function), as follows:
(state, action) => {
return Object.assign({}, state, {
...
})
}
Pure function, accept the input, and then output the result, except that there is no effect, including the parameters that do not affect the reception; always output the same result for the same input.
Mobx design is more object-oriented programming (OOP) and responsive programming (Reactive Programming), usually wrapping the state into observable objects, so we can use all the capabilities of observable objects, once the state object changes, we can automatically get updates.
single store and multiple store
store is the place where applications manage data. In Redux applications, we always centralize all shared application data in a large store, while Mobx usually divides application status by module and manages it in multiple independent store.
JavaScript objects and observable objects
Redux stores data as JavaScript native objects by default, while Mobx uses observable objects:
- Redux needs to manually track changes to all state objects;
- Mobx can listen for observable objects, which will be triggered automatically when they change;
immutable (Immutable) and variable (Mutable)
Redux state objects are usually immutable (Immutable):
switch (action.type) {
case REQUEST_POST:
return Object.assign({}, state, {
post: action.payload.post
});
default:
retur nstate;
}
We cannot directly manipulate the state object, but always return a new state object based on the original state object, so that we can easily return the last state of the application; while in Mobx, we can directly update the state object with the new value.
mobx-react and react-redux
when using Redux and React application connections, you need to use Provider
and connect
:
provided by react-redux
-
Provider
: responsible for injecting Store into React applications; -
connect
: responsible for injecting store state into the container component and selecting a specific state to pass as the container component props;
for Mobx, two steps are also required:
-
Provider
: inject all stores into the application usingProvider
provided bymobx-react
; - use
inject
to inject a specific store into a component, and store can pass a state or action; and then useobserver
to ensure that the component can respond to observable changes in the store, that is, store updates, component view responsive updates.
reasons for choosing Mobx
- the cost of learning is low: the basic knowledge of Mobx is very simple, and a new project instance is built after learning official documents and sample code for half an hour. While Redux is really cumbersome and has many processes, it needs to configure, create store, write reducer,action, and if asynchronous tasks are involved, you need to introduce
redux-thunk
orredux-saga
to write additional code. The Mobx process is much simpler than that, and no additional asynchronous processing libraries are needed. - object-oriented programming: Mobx supports object-oriented programming. We can use
@ observable
and@ observer
to make JavaScript objects responsive. Redux is most recommended to follow functional programming, and of course Mobx also supports functional programming. - template code is less: compared with various template codes of Redux, such as actionCreater,reducer,saga/thunk, etc., Mobx does not need to write such template code;
possible reasons for not choosing Mobx
- is too free: Mobx provides few conventions and template code, which makes the development code very free. If you do not make some conventions, it is easier to lead to inconsistent team code style, so when there are more team members, you do need to add some conventions;
- Extensibility, maintainability: you may be worried about whether Mobx will be able to adapt to the development of later projects. It is true that Mobx is more suitable for small and medium-sized projects, but this does not mean that it cannot support large projects. The key is that large projects usually need to pay special attention to scalability and maintainability. Compared with standard Redux, the standard Mobx has more advantages, while Mobx is freer. We need to make some rules to ensure the later expansion and maintenance of the project.
Code comparison
next, let's simply implement the same application using Redux and Mobx and compare their code to see how they each perform.
Architecture
in Redux applications, we first need to configure, create store, and use redux-thunk
or redux-saga
middleware to support asynchronous action, and then use Provider
to inject store into the application:
// src/store.js
import { applyMiddleware, createStore } from "redux";
import createSagaMiddleware from 'redux-saga'
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from "./reducers";
import App from './containers/App/';
const sagaMiddleware = createSagaMiddleware()
const middleware = composeWithDevTools(applyMiddleware(sagaMiddleware));
export default createStore(rootReducer, middleware);
// src/index.js
…
ReactDOM.render(
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>,
document.getElementById('app')
);
Mobx applications can directly inject all store into applications:
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'mobx-react';
import { BrowserRouter } from 'react-router-dom';
import { useStrict } from 'mobx';
import App from './containers/App/';
import * as stores from './flux/index';
// set strict mode for mobx
// must change store through action
useStrict(true);
render(
<Provider {...stores}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('app')
);
inject Props
Redux:
// src/containers/Company.js
…
class CompanyContainer extends Component {
componentDidMount () {
this.props.loadData({});
}
render () {
return <Company
infos={this.props.infos}
loading={this.props.loading}
/>
}
}
…
// function for injecting state into props
const mapStateToProps = (state) => {
return {
infos: state.companyStore.infos,
loading: state.companyStore.loading
}
}
const mapDispatchToProps = dispatch => {
return bindActionCreators({
loadData: loadData
}, dispatch);
}
// injecting both state and actions into props
export default connect(mapStateToProps, { loadData })(CompanyContainer);
Mobx:
@inject('companyStore')
@observer
class CompanyContainer extends Component {
componentDidMount () {
this.props.companyStore.loadData({});
}
render () {
const { infos, loading } = this.props.companyStore;
return <Company
infos={infos}
loading={loading}
/>
}
}
define Action/Reducer, etc.
Redux:
// src/flux/Company/action.js
…
export function fetchContacts(){
return dispatch => {
dispatch({
type: 'FREQUEST_COMPANY_INFO',
payload: {}
})
}
}
…
// src/flux/Company/reducer.js
const initialState = {};
function reducer (state = initialState, action) {
switch (action.type) {
case 'FREQUEST_COMPANY_INFO': {
return {
...state,
contacts: action.payload.data.data || action.payload.data,
loading: false
}
}
default:
return state;
}
}
Mobx:
// src/flux/Company/store.js
import { observable, action } from 'mobx';
class CompanyStore {
constructor () {
@observable infos = observable.map(infosModel);
}
@action
loadData = async(params) => {
this.loading = true;
this.errors = {};
return this.$fetchBasicInfo(params).then(action(({ data }) => {
let basicInfo = data.data;
const preCompanyInfo = this.infos.get('companyInfo');
this.infos.set('companyInfo', Object.assign(preCompanyInfo, basicInfo));
return basicInfo;
}));
}
$fetchBasicInfo (params) {
return fetch({
...API.getBasicInfo,
data: params
});
}
}
export default new CompanyStore();
Asynchronous Action
if we use Redux, we need to add redux-thunk
or redux-saga
to support asynchronous action, which requires additional configuration and template code, while Mobx does not require additional configuration.
the main codes of redux-saga are:
// src/flux/Company/saga.js
// Sagas
// ------------------------------------
const $fetchBasicInfo = (params) => {
return fetch({
...API.getBasicInfo,
data: params
});
}
export function *fetchCompanyInfoSaga (type, body) {
while (true) {
const { payload } = yield take(REQUEST_COMPANY_INFO)
console.log('payload:', payload)
const data = yield call($fetchBasicInfo, payload)
yield put(receiveCompanyInfo(data))
}
}
export const sagas = [
fetchCompanyInfoSaga
];
some ideas
whether front-end or back-end, when you encounter problems, most of the time, you may be used to recommending what has been widely used. Habit and familiarity can easily become natural. We should go further and think about it. The right one is better.
of course, for inferences such as "Redux is more standardized and reliable, we should use Redux" or "Redux templates are too many and too complex, we should choose Mobx". We should also avoid these. Relatively speaking, each framework has its own implementation, characteristics, and applicable scenarios. For example, the Redux process is more complex, but after you are familiar with the process, you will be able to grasp some of its basic / core ideas, and you may have more experience and understanding when using it. Mobx, on the other hand, simplifies and hides most things, and you can't get to its core / basic ideas without special research, which may keep developers at the usage level.
so whether it's the technology stack or the framework. Class libraries, there is no absolute comparison of what we should choose and abandon, we should pay more attention to what problems they solve, what their focus is, or how they are implemented, and their advantages and disadvantages. Which is more suitable for the current project, as well as the future development of the project.