In the early stage of my work development project, I used React Native + Flux to conduct cross-platform development of mobile applications. In my last blog post, I took notes on data flow architecture (I)-Flux and summarized Flux. This paper is the second data flow architecture learning note (II)-Redux, which is a learning record of reconstructing state management and data flow using Redux in the project during my work.

The origin of the story

In 2014, Facebook proposed the concept of Flux architecture and the idea of one-way data flow management, and presented the basic data flow for managing state. However, with the exponential increase of front-end application complexity, front-end pages need to manage more and more states. Therefore, many realization methods of Flux basic data flow concept and unidirectional data flow idea appeared. In 2015, Redux came along, combining Flux with functional programming, and in a short time became the hottest front-end architecture.

In a real project, you should have encountered the following situations:

  • In the debug project, when complex data is updated on the page, some non-standard listening and observer mechanisms or too much use of this. SetState in React is used to render the page. As the data is rendered asynchronously, it is often impossible to determine the reason for the page rendering caused by the change of data state. What’s worse is that you can’t know what the actual data state of your App is in its current state, that is, you can’t know how much data your App currently has, and you can’t quickly predict how your App will change next. Redux is a good solution to this problem.
  • Similarly, if you are still working on modular, component-based development in your project, Redux allows you to quickly incorporate your modular components into the project and disassemble them.

How Redux works

Redux bills itself as a “predictable state container” that takes advantage of the functional nature to make the whole implementation more elegant, pure and simple to use.

Redux(oldState) => newStateCopy the code

Redux can be seen as an evolution of Flux. Redux follows three basic principles:

  • There is only one trusted data source for the entire application, that is, there is only one Store
  • State can only be changed by triggering an Action
  • State changes must be written as a pure function, that is, each change always returns a new State. This function is called Reducer in Redux


View triggers data update - > Actions to pass data to Store - > Store updates state - > Update View.Copy the code

In Redux, the state of the entire application is stored in an object tree, corresponding to a unique Store, and state is read-only. Using the pure reducer function to update state generates a new state instead of directly modifying the original state.

Redux uses these constraints to make state changes predictable.

If you don’t understand these concepts, it is recommended that you study the official documentation of Redux first, and then check out other people’s blogs and usage methods to get faster use.

Redux instance encapsulation

This section uses the React Native project login section to show how to apply Redux to React Native development for data management. In actual architecture projects, everyone has their own coding habits. Therefore, although it is the same Redux, the code writing in each part is always different. The writing method used in the actual project is also different, but the overall idea is the same, do not stick to the code writing method, the code is only for reference and summary, should understand the purpose of writing and how to reflect the integration and use of Redux.

The View layer View

The login page:

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import LoginAction from '.. /.. /actions/loginAction'; . class Login extends Component { ... _doLogin =(a)= > {
        const param = {
          uid: this.state.uid,
          pwd: this.state.pwd
        };

        this.props.actions.doLogin(param)
          .then((a)= > {
            const { navigation, login } = this.props;
            if (login.status === 'done' && navigation) {
              navigation.resetRouteTo('TabBar', { title: 'home'.selectedTab: 'home' });
            } else {
              Alert.alert(
                'tip', login.message ); }}); }; . }... const mapStateToProps =(state) = > {
  return {
    login: state.loginReducer
  };
};
const mapDispatchToProps = dispatch= > {
  return ({
    actions: bindActionCreators({ ... LoginAction }, dispatch) }); };export default connect(mapStateToProps, mapDispatchToProps)(Login);Copy the code

Simply View layer primary role is to respond to user operation, and practical in the code our main role is to trigger the Action, such as code calls this. Props. Actions. The doLogin () function, The actions attribute in this.props is available because, at the end, I use the bindActionCreators method to bind the corresponding LoginAction to the page component Login, which causes me to only call the action in the View layer. Dispatch is not used directly for message distribution. This completes the View -> Actions process.

Behavior of the Action


const _loginSuccess = (data) = > {//eslint-disable-line
  return {
    type: ActionTypes.LOGIN_SUCCESS,
    payload: {
      user: data.uid
    }
  };
};

const _loginFailed = (error) = > {
  return {
    type: ActionTypes.FAIL,
    payload: {
      message: error.message
    }
  };
};

const _doLogin = (url, param) = > dispatch => {
  dispatch(CommonAction.showLoading());
  return Fetcher.postQsBodyFetch(url, param)
      .then((response) = > {
        dispatch(CommonAction.dismissLoading());
        dispatch(_loginSuccess(param, response));
      }).catch((error) = > {
        dispatch(CommonAction.dismissLoading());
        dispatch(_loginFailed(error));
      });
};

const LoginAction = {
  doLogin: (param) = > _doLogin(NetLink.login, param),
  loginSuccess: (data) = > _loginSuccess(data),
  loginFailed: (error) = > _loginFailed(error),
};Copy the code

The Action usually does the network layer call, request data, and distribute data. Since the bindActionCreators method and component bindings are used in the View layer, the View layer component Dispatch property method is directly available. Causes dispatch() to be called in the pure function of the Action for data distribution after the data is returned. This completes the Actions -> Reducer process.

Reducer

import ActionType from '.. /constants/actionType';

const initialState = {
  status: 'init'.user: ' '.message: null
};

const loginReducer = (state = initialState, action) = > {
  switch (action.type) {
    case ActionType.LOGIN_SUCCESS:
      return Object.assign({}, state, {
        status: 'done'.user: action.payload.user,
      });
    case ActionType.FAIL:
      return Object.assign({}, state, {
        status: 'fail'.message: action.payload.message,
      });
    default:
      returnstate; }};Copy the code

Reducer is similar to the store of the original Flux and serves as the source of the data warehouse. The Reducer will receive the message obtained after calling Dipatch (), process and save it, and automatically feedback to the page component for data update and asynchronous rendering after timely updating data through component binding of Redux. In this case, you should return a new object so that Redux can know that you have updated the reducer associated with the current component. At this stage, you should have questions about how the data state is fed back to the View. How do your ordinary actions and Reducer js files relate to your components and applications?

Binding and importing

Entry component root.js:

import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import Fetcher from './network/fetcher';
import Main from './containers/mainContainer';
import rootReducer from './reducers/rootReducer';

const middlewares = [thunk];
constcreateStoreWithMiddleware = applyMiddleware(... middlewares)(createStore);const createLogger = require('redux-logger');

if (process.env.NODE_ENV === 'development') {
  const logger = createLogger();
  middlewares.push(logger);
}

function configureStore(initialState) {
  const store = createStoreWithMiddleware(rootReducer, initialState);
  return store;
}

const store = configureStore();

export default class Root extends Component {
  constructor(props) {
    super(props);
    Fetcher.initNetworkState();
  }

  componentWillUnmount() {
    Fetcher.removeNetworkStateListener();
  }

  render() {
    return (
      <Provider store={store}>
        <Main {. this.props} / >
      </Provider>); }}Copy the code

The rootReducer. Js:

import { combineReducers } from 'redux';
import LoginReducer from './loginReducer'; . const rootReducer = combineReducers({loginReducer: LoginReducer,
  ...
});

export default rootReducer;Copy the code

CombineReducer was first used to merge all reducer into a rootReducer, and then rootReducer was used to create a store object through createStore method. You are done binding the Store object directly to the actual View component through the Provider component provided by Redux

View triggers data update - > Actions to pass data to Store - > Store updates state - > Update View.Copy the code

For applyMiddleware, the bindActionCreators and other methods are advanced knowledge of asynchronous Actions, asynchronous data flows, and Middleware. You are advised to refer to the official Redux documentation for details. The following Redux progression will also outline their use and why.

Redux advanced

Use the Redux-Thunk and Redux-Logger frameworks, and use applyMiddleware and Middleware to enhance redux to make Redux more powerful, regulated, and reasonable. The redux-Logger framework is simply understood as adding a Redux log printing and processing framework. Developers do not need to know how to regulate the printing of Redux dispatch logs, but it is a very useful tool for debugging and so on during development. Redux-thunk belongs to the framework for handling asynchronous Actions and asynchronous data flows.

Asynchronous Actions and asynchronous data flows

What are asynchronous Actions and asynchronous data flows? Simply put, Actions controlled by network requests. If you click a button in the App and immediately send a dispatch(), this is the dispatch() you make to the App control, which is called synchronous Actions, while asynchronous Actions are not controlled by you. For example, a dispatch() will be sent after the network request succeeds or fails. These are asynchronous Actions, and you don’t know when the dispatch() was done, or whether you sent a successful dispatch() or a failed dispatch().

If I have the loginAction method, I should now have a good understanding of how this method is written:

const _doLogin = (url, param) = > dispatch => {
  dispatch(CommonAction.showLoading());
  return Fetcher.postQsBodyFetch(url, param)
      .then((response) = > {
        dispatch(CommonAction.dismissLoading());
        dispatch(_loginSuccess(param, response));
      }).catch((error) = > {
        dispatch(CommonAction.dismissLoading());
        dispatch(_loginFailed(error));
      });
};Copy the code

Middleware

Middleware is aptly translated as middleware, and is easy to understand. If you want to use middleware like Redux-Thunk or Redux-Promise, you’ll need to use applyMiddleware to add frameworks to your projects. Make projects using Redux more powerful and standardized.

Asynchronous Middleware such as Redux-thunk or Redux-Promise wraps a store dispatch() method that lets you dispatch something other than an action, such as: Function or Promise. Any middleware you use can resolve whatever content you dispatch in its own way and continue to pass actions to the next middleware. For example, Promise-enabled Middleware can intercept promises and asynchronously dispatch a pair of Begin /end actions for each Promise.

When the last middleware in the chain starts a dispatch action, the action must be a normal object. This is where synchronous Redux data flows start (you can use as much asynchronous middleware as you want to do, but you need to use normal objects as the last action to be dispatched to bring the flow back to synchronous).

It’s important to understand how Middleware has evolved to do all sorts of things, including asynchronous API calls. How they evolve and enhance your application is not specified here.

conclusion

Redux is powerful and relatively complex. Simple projects may not need it, but if your project gets bigger and more complex, Redux will make your project more disciplined and robust. It’s very important to use a formal framework architecture in your projects, and it’s interesting that you can use architecture for your projects.

The article is very long and Redux is very complex. Please let me know if there are any mistakes in the article and I will correct them in time. Progress and learn together. Thank you very much!

reference

Redux Chinese document

Redux Tutorial – Nguyen Yifeng