Redux with its middleware: redux-thunk,redux-actions,redux-promise,redux-sage

Source: Internet
Author: User
Tags exception handling generator redux middleware
Preface

What is to be said here is a redux in the react of the application of the problem, talk about Redux,react-redux,redux-thunk,redux-actions,redux-promise, Redux-sage the effects of these packages and the problems they solve.
Because do not want to take the space too long, so there is not too much source code analysis and grammar explanation, can be how simple simple.

Redux

First look at the Baidu encyclopedia above redux a picture:

This is Redux's introduction on GitHub: Redux is a predictable state container for JS programs.

The first thing we need to understand here is what is predictable? What is a state container?

What do you mean state? is actually the variable, the dialog box shows or hides the variable, a cup of tea how much money the variable.

The state container, then, is actually a variable that holds these variables.

You create a global variable called store, and then store the variables in the code that control each state in it, and now the store is called the state container.

What do you mean predictable?

When you operate the store, you always set the value in a Store.price way, which is primitive, and for complex systems you never know what's going on in the process of running the program.

So now we're all going to make a change by sending an action, and the store will use reducer to handle the data passed by the action when it receives the action, and finally apply it to the store.

This approach is undoubtedly more troublesome than the store.price approach, but the advantage of this approach is that every action can be written in a log that can record changes in various states, which is predictable.

So if your program is simple, you have absolutely no need to use redux.

Look at the sample code for redux:

Actiontypes.js:

export const CHANGE_BTN_TEXT = 'CHANGE_BTN_TEXT';

Actions.js:

import * as T from './actionTypes';export const changeBtnText = (text) => {  return {    type: T.CHANGE_BTN_TEXT,    payload: text  };};

Reducers.js:

import * as T from './actionTypes';const initialState = {  btnText: '我是按钮',};const pageMainReducer = (state = initialState, action) => {  switch (action.type) {    case T.CHANGE_BTN_TEXT:      return {        ...state,        btnText: action.payload      };    default:      return state;  }};export default pageMainReducer;

Index.js

import { createStore } from 'redux';import reducer from './reducers';import { changeBtnText } from './actions';const store = createStore(reducer);// 开始监听,每次state更新,那么就会打印出当前状态const unsubscribe = store.subscribe(() => {  console.info(store.getState());});// 发送消息store.dispatch(changeBtnText('点击了按钮'));// 停止监听state的更新unsubscribe();

This does not explain what grammatical function, such as the Internet too much information.

The combination of Redux and react: React-redux

Redux is a predictable state container, and the library that builds the UI with react is two separate things from one another.

Redux to apply to react, it is clear action,reducer,dispatch these phases do not need to change, the only thing to consider is how the state in Redux needs to be passed to the react component.

It's simple to use store.getstate to get to the current state every time you want to update the data, and pass that data to the component.

So the question is, how do you get each component to the store?

Of course, the store is passed as a value to the root component, and the store goes down one level at a level, allowing each component to get the value of the store.

But is it too tedious for each component to write a logic to pass the store? To solve this problem, you have to use the context of react, by placing the store in the context of the root component on the root component, and then getting to the store through the context in the subassembly.

This is also true of React-redux's main train of thought, by placing the store in context through nested components provider, and by using the high-level component of connect to hide the store operation, So we don't have to go into the context and write a lot of code.

And then we'll come back. Based on the previous Redux sample code, we give the demo code for React-redux, where the action and reduce sections are unchanged, first adding a component pagemain:

const PageMain = (props) => {  return (    <div>      <button onClick={() => {        props.changeText('按钮被点击了');      }}      >        {props.btnText}      </button>    </div>  );};// 映射store.getState()的数据到PageMainconst mapStateToProps = (state) => {  return {    btnText: state.pageMain.btnText,  };};// 映射使用了store.dispatch的函数到PageMainconst mapDispatchToProps = (dispatch) => {  return {    changeText: (text) => {      dispatch(changeBtnText(text));    }  };};// 这个地方也可以简写,react-redux会自动做处理const mapDispatchToProps = {  changeText: changeBtnText};export default connect(mapStateToProps, mapDispatchToProps)(PageMain);

Notice the above state.pageMain.btnText, this pagemain is I use Redux combinereducers to merge multiple reducer after the original reducer a name.

Its code is as follows:

import { combineReducers } from 'redux';import pageMain from './components/pageMain/reducers';const reducer = combineReducers({  pageMain});export default reducer;

Then modify the Index.js:

import React from 'react';import { createStore } from 'redux';import { Provider } from 'react-redux';import ReactDOM from 'react-dom';import reducer from './reducers';import PageMain from './components/pageMain';const store = createStore(reducer);const App = () => (  <Provider store={store}>    <PageMain />  </Provider>);ReactDOM.render(<App />, document.getElementById('app'));
Middleware for Redux

Before we talked about the redux is a predictable state container, which can be predicted that each modification of the data can be processed and recorded accordingly.

If we now need to record the modified content every time we modify the data, we can add a console.info record of the modified content in front of each dispatch.

But this is too cumbersome, so we can directly modify the Store.dispatch:

let next = store.dispatchstore.dispatch = (action)=> {  console.info('修改内容为:', action)  next(action)}

Redux also has the same function, that is applymiddleware. Literal translation is "Application Middleware", its role is to transform the dispatch function, and the above play basically similar.

To a demo code:

import { createStore, applyMiddleware } from 'redux';import reducer from './reducers';const store = createStore(reducer, applyMiddleware(curStore => next => action => {  console.info(curStore.getState(), action);  return next(action);}));

It seems strange to play, but it is not difficult to understand. This method of returning functions allows the store and action to be handled internally as well as when we use it, and the next application here is applymiddleware to use multiple middleware.

And usually we do not need to write the middleware themselves, such as the log records have a mature middleware: Redux-logger, here is a simple example:

import { applyMiddleware, createStore } from 'redux';import createLogger from 'redux-logger';import reducer from './reducers';const logger = createLogger();const store = createStore(  reducer,  applyMiddleware(logger));

This allows you to record all the actions and the logs of the state before and after they are sent, and we can see what happens when the code actually runs.

Redux-thunk: Handling Asynchronous action

In the above code, when we click on the button, we modify the text of the button directly, which is a fixed value.

Actions.js:

import * as T from './actionTypes';export const changeBtnText = (text) => {  return {    type: T.CHANGE_BTN_TEXT,    payload: text  };};

However, in our actual production process, a lot of situations need to ask the server to get the data to be modified, this process is an asynchronous process. or need settimeout to do something.

We can go to modify this section as follows:

const mapDispatchToProps = (dispatch) => {  return {    changeText: (text) => {      dispatch(changeBtnText('正在加载中'));      axios.get('http://test.com').then(() => {        dispatch(changeBtnText('加载完毕'));      }).catch(() => {        dispatch(changeBtnText('加载有误'));      });    }  };};

In fact, we don't know how much of this code to process every day.

But the problem is that asynchronous operations are a lot more deterministic than synchronous operations, such as when we show that we are loading, we may want to do an asynchronous operation A, and the process of requesting the background is very fast, causing the loading to occur first, and then the operation A is done, and then the load is displayed.

So the above play does not meet this situation.

This time we need to go through store.getstate to get the current state, so as to determine whether the display is loading or the display is finished loading.

This process cannot be placed in the mapdispatchtoprops, but needs to be placed in the middleware, because the middleware can get the store.

The first time you create a store, you need to apply React-thunk,

import { createStore, applyMiddleware } from 'redux';import thunk from 'redux-thunk';import reducer from './reducers';const store = createStore(  reducer,  applyMiddleware(thunk));

Its source code is super simple:

function createThunkMiddleware(extraArgument) {  return ({ dispatch, getState }) => next => action => {    if (typeof action === 'function') {      return action(dispatch, getState, extraArgument);    }    return next(action);  };}const thunk = createThunkMiddleware();thunk.withExtraArgument = createThunkMiddleware;export default thunk;

As can be seen from this, it is to strengthen the function of the dispatch, before dispatch an action, to determine whether the action is a function, if it is a function, then execute this function.

Then we can use it very simply, when we modify the Actions.js

import axios from 'axios';import * as T from './actionTypes';export const changeBtnText = (text) => {  return {    type: T.CHANGE_BTN_TEXT,    payload: text  };};export const changeBtnTextAsync = (text) => {  return (dispatch, getState) => {    if (!getState().isLoading) {      dispatch(changeBtnText('正在加载中'));    }    axios.get(`http://test.com/${text}`).then(() => {      if (getState().isLoading) {        dispatch(changeBtnText('加载完毕'));      }    }).catch(() => {      dispatch(changeBtnText('加载有误'));    });  };};

And the original mapdispatchtoprops in the play and synchronous action is the same play:

const mapDispatchToProps = (dispatch) => {  return {    changeText: (text) => {      dispatch(changeBtnTextAsync(text));    }  };};

By Redux-thunk we can simply perform asynchronous operations, and we can get values for the state of each asynchronous operation.

Redux-actions: Simplifying the use of redux

Redux is good, but there are some duplicate code inside, so there are redux-actions to simplify those repetitive code.

This part of the simplification is mainly focused on the construction of action and the handling of reducers.

Let's take a look at the original actions.

import axios from 'axios';import * as T from './actionTypes';export const changeBtnText = (text) => {  return {    type: T.CHANGE_BTN_TEXT,    payload: text  };};export const changeBtnTextAsync = () => {  return (dispatch, getState) => {    if (!getState().isLoading) {      dispatch(changeBtnText('正在加载中'));    }    axios.get('http://test.com').then(() => {      if (getState().isLoading) {        dispatch(changeBtnText('加载完毕'));      }    }).catch(() => {      dispatch(changeBtnText('加载有误'));    });  };};

Then take a look at the modified:

import axios from 'axios';import * as T from './actionTypes';import { createAction } from 'redux-actions';export const changeBtnText = createAction(T.CHANGE_BTN_TEXT, text => text);export const changeBtnTextAsync = () => {  return (dispatch, getState) => {    if (!getState().isLoading) {      dispatch(changeBtnText('正在加载中'));    }    axios.get('http://test.com').then(() => {      if (getState().isLoading) {        dispatch(changeBtnText('加载完毕'));      }    }).catch(() => {      dispatch(changeBtnText('加载有误'));    });  };};

After this piece of code replaces the above part of the code, the program's running results remain the same, meaning that createaction simply encapsulates the above code.

Notice here that the asynchronous action does not use CreateAction, because the CreateAction returns an object, not a function, which causes the Redux-thunk code to not function.

You can also use the Createactions function to create multiple actions at the same time, but to be reasonable, this syntax is very strange and good with createaction.

The same redux-actions also dealt with parts of reducer, such as Handleaction and handelactions.

Let's take a look at the original reducers.

import * as T from './actionTypes';const initialState = {  btnText: '我是按钮',};const pageMainReducer = (state = initialState, action) => {  switch (action.type) {    case T.CHANGE_BTN_TEXT:      return {        ...state,        btnText: action.payload      };    default:      return state;  }};export default pageMainReducer;

Then use Handleactions to handle

import { handleActions } from 'redux-actions';import * as T from './actionTypes';const initialState = {  btnText: '我是按钮',};const pageMainReducer = handleActions({  [T.CHANGE_BTN_TEXT]: {    next(state, action) {      return {        ...state,        btnText: action.payload,      };    },    throw(state) {      return state;    },  },}, initialState);export default pageMainReducer;

Here handleactions can join exception handling and help handle the initial value.

Note that both CreateAction and handleaction are just a little bit of a simple encapsulation of the code, and both can be used alone, not to say that using the createaction must be handleaction.

Redux-promise:redux-actions, easy to create and handle asynchronous action

Remember that when you use Redux-actions's createaction above, we can't handle the asynchronous action.

Since we use CreateAction to return an object instead of a function, it will cause the Redux-thunk code to not work.

And now we're going to use redux-promise to deal with this kind of situation.

You can look at the examples we used createaction before:

export const changeBtnText = createAction(T.CHANGE_BTN_TEXT, text => text);

Now let's join the Redux-promise middleware:

import thunk from 'redux-thunk';import createLogger from 'redux-logger';import promiseMiddleware from 'redux-promise';import reducer from './reducers';const store = createStore(reducer, applyMiddleware(thunk, createLogger, promiseMiddleware));

Then process the asynchronous action:

export const changeBtnTextAsync = createAction(T.CHANGE_BTN_TEXT_ASYNC, (text) => {  return axios.get(`http://test.com/${text}`);});

You can see that we are returning a promise object here. (The Axios Get method result is the Promise object)

We also remember the Redux-thunk middleware, which will determine if the action is a function, and if so, execute it.

And we have here the Redux-promise middleware, he will be in dispatch when judging if the action is not similar

{  type:'',  payload: ''}

Such a structure, that is, the FSA, then to determine whether it is a promise object, if it is to perform action.then play.

Obviously, the result of our createaction is the FSA, so we will go to the following branch, it will determine whether the Action.payload is Promise object, if it is

action.payload  .then(result => dispatch({ ...action, payload: result }))  .catch(error => {    dispatch({ ...action, payload: error, error: true });    return Promise.reject(error);  })

This means that our code will eventually change to:

axios.get(`http://test.com/${text}`)  .then(result => dispatch({ ...action, payload: result }))  .catch(error => {    dispatch({ ...action, payload: error, error: true });    return Promise.reject(error);  })

This middleware code is also very simple, a total of 19 lines, you can see directly on GitHub.

Redux-sage: Controller with more elegant asynchronous processing

Our asynchronous processing with Redux-thunk + Redux-actions + redux-promise, in fact, it is very good use.

But with the advent of generator in ES6, it has been found that using generator to handle asynchrony can be simpler.

and Redux-sage is using generator to handle asynchrony.

The following knowledge is based on generator, if you do not understand this, you can simply understand the relevant knowledge, it takes about 2 minutes, it is not difficult.

The Redux-sage document does not say that it is a tool for handling asynchrony, but rather that it is used to handle marginal effects (side effects), where the marginal effect can be understood as program-to-external operations, such as the request backend, such as manipulating files.

Redux-sage is also a redux middleware, which is positioned to play a similar role in MVC as a controller by centrally controlling the action.

At the same time, its syntax makes complex asynchronous operations less likely to happen in the same way as promise, making it easier to perform various tests.

This thing has its advantages, also has its bad place, that is more complex, there is a certain cost of learning.

And personally I am not accustomed to the use of generator, think promise or await better use.

Here is still a record of usage, after all, there are many frameworks to use this.

There is no difference between applying this middleware and our other middleware:

import React from 'react';import { createStore, applyMiddleware } from 'redux';import promiseMiddleware from 'redux-promise';import createSagaMiddleware from 'redux-saga';import {watchDelayChangeBtnText} from './sagas';import reducer from './reducers';const sagaMiddleware = createSagaMiddleware();const store = createStore(reducer, applyMiddleware(promiseMiddleware, sagaMiddleware));sagaMiddleware.run(watchDelayChangeBtnText);

After the Sage middleware is created, and then its middleware is plugged into the store, the middleware is used to run the Sages.js returned generator to monitor each action.

Now we give the Sages.js code:

import { delay } from 'redux-saga';import { put, call, takeEvery } from 'redux-saga/effects';import * as T from './components/pageMain/actionTypes';import { changeBtnText } from './components/pageMain/actions';const consoleMsg = (msg) => {  console.info(msg);};/** * 处理编辑效应的函数 */export function* delayChangeBtnText() {  yield delay(1000);  yield put(changeBtnText('123'));  yield call(consoleMsg, '完成改变');}/** * 监控Action的函数 */export function* watchDelayChangeBtnText() {  yield takeEvery(T.WATCH_CHANGE_BTN_TEXT, delayChangeBtnText);}

In Redux-sage, there is a class of functions for dealing with marginal effects such as put, call, which are intended to simplify operations.

For example, put is equivalent to Redux's dispatch, and call is the equivalent of calling a function. (Refer to the example in the code above)

Another type of function is similar to takeevery, and its function is to intercept the action as normal Redux middleware and make corresponding processing.

The above code, for example, intercepts an action of type T.watch_change_btn_text and then calls Delaychangebtntext.

You can then look back at our previous code with one line of code:

Sagamiddleware.run (Watchdelaychangebtntext);

This is actually the introduction of the monitoring generator, and then run the monitoring generator.

This allows us to intercept and handle the action when the dispatch type is t.watch_change_btn_text in the code.

Of course there are people here who might ask, is it true that every async is going to be written like this, wouldn't it have to run many times?

This is certainly not the case, as we can write in Sage:

export default function* rootSaga() {  yield [    watchDelayChangeBtnText(),    watchOtherAction()  ]}

All we have to do is write in this format, put the generator that watchdelaychangebtntext so that monitors the action in an array of the above code, and then return as a generator.

Now just refer to this Rootsaga and run the Rootsaga.

If you want to monitor more actions later, simply add the new monitoring generator to the sages.js.

With this processing, we've made Sages.js a controller in MVC that can handle a wide variety of actions, dealing with complex asynchronous operations and marginal effects.

Note, however, that it is important to differentiate between the actions used for monitoring in sages.js and the actions used for real functions, such as adding a watch keyword to avoid complex code confusion after the business.

Summarize

Overall:

    • The redux is a predictable state container,
    • React-redux is the combination of the store and the react, making data presentation and modification easier for react projects.
    • Redux Middleware is to do some processing of action before dispatch action
    • Redux-thunk is used to do asynchronous operations
    • Redux-actions for simplifying redux operations
    • Redux-promise can be used with redux-actions to handle promise objects, making asynchronous operations easier
    • Redux-sage can play a role as a controller, concentrating on marginal utility, and making asynchronous operations more elegant.

OK, although said do not want to write so much, the result still wrote a lot.

If you find it helpful, please feel good.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.