[React]Redux中间件与异步操作

来源:Redux入门教程-阮一峰
文档:Redux中文文档

中间件

同步操作:在Action发出以后,Reducer立即返回新的State;
异步操作:在Action发出以后,过一段时间才执行Reducer;
中间件是使Reducer在异步操作结束后自动执行的工具

中间件的概念

中间件的功能是在异步操作结束之后能够自动执行Reducer更新state,这个功能应该被添加在发送Action的功能中,即store.dispatch()。
例如,在下面的例子中,将会对store.dispatch()进行改造,使其能够将Action和State打印。

let next=store.dispatch;
store.dispatch=function dispatchAndLog(action){
    console.log('dispatching',action);
    next(action);
    console.log('next state',store.getState());
}

以上的dispatch()将会在发送Action的前后进行打印。

中间件的用法

一般常见的中间件都已经被编写好了,只要通过引用即可获得,引用的方法为:

import {applyMiddleware,createStore} from 'redux';
import createLogger from 'redux-logger';
const logger=createLogger();

const store=createStore(
    reducer,
    applyMiddleware(logger)
)

在上面的例子中,添加了一个日志中间件,将其放入applyMiddleware()方法中作为参数传入createStore()。
注意:

  • createStore()方法可以接收整个应用的初始状态作为参数,在接受初始状态时,applyMiddleware是第三个参数;
const store = createStore(
  reducer,
  initial_state,
  applyMiddleware(logger)
);
  • 中间件的次序需要注意
    中间件的次序在有些时候有些要求,使用前要检查一下文档,比如logger一定要放在最后。

异步操作的基本思路

同步操作只要发出一种Action,但是异步操作要发出三种Action:

  • 操作发起时的Action
  • 操作成功时的Action
  • 操作失败时的Action

在向服务器获得数据时,三种Action可以有两种不同的写法:

//写法1:名称相同,参数不同
{type:'FETCH_POSTS'}
{type:'FETCH_POSTS',status:'error',error:'0ops'}
{type:'FETCH_POSTS',status:"success",response:{...}}

//写法2:名称不同
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }

Action种类不同,且异步操作的State也要进行改造,反映不同的操作状态。

let state = {
  // ... 
  isFetching: true,
  didInvalidate: true,
  lastUpdated: 'xxxxxxx'
};

在上面的例子中,定义了State的属性isFetching是否在获得数据,didInvalidate表示数据是否过时,lastUpdated表示上一次更新时间。
在异步操作过程中,需要:

  • 在操作开始时,送出一个Action,触发State更新为"正在操作"状态,View重新渲染;
  • 操作结束后,再送出一个Action,触发State更新为"操作结束"状态,View再一次渲染。

redux-thunk中间件

异步操作需要送出两个Action:用户在操作开始时发送的第一个Action,在结束时送出第二个Action;
这需要在Action Creator中进行操作:

class AsyncApp extends Component {
  componentDidMount() {
    const { dispatch, selectedPost } = this.props
    dispatch(fetchPosts(selectedPost))
  }

// ...

在加载结束后生命周期函数内送出Action,向服务器请求数据(fetchPosts(selectedSubreddit)),这里的fetchPosts是Action Creator。

fetchPosts

fetchPosts的代码为:

const fetchPosts = postTitle => (dispatch, getState) => {
  //第一个Action,表示异步操作开始
  dispatch(requestPosts(postTitle));
  return fetch(`/some/API/${postTitle}.json`)//获取数据
    .then(response => response.json())//将数据转化为JSON
    .then(json => dispatch(receivePosts(postTitle, json)));//发送第二个Action,表示异步操作结束
  };
};

fetchPosts是一个Action Creator,返回一个函数,函数执行之后将会发出一个Action,进行异步操作得到结果后将会在对结果进行处理之后再发一个Action。
在上面的例子中,

  • fetchPosts返回一个函数,普通的Action Creator默认返回一个对象;
  • 返回函数的参数是dispatchgetState这两个Redux方法,普通的Action Creator的参数是Action的内容。

存在问题:在通常情况下,store.dispatch()的参数为Action对象,不能是函数
为了解决整个问题,使用中间件redux-thunk;

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';

// Note: this API requires redux@>=3.1.0
const store = createStore(
  reducer,
  applyMiddleware(thunk)
);

使用中间件改造后的store.dispatch()将可以接受函数作为参数。
这是异步操作的第一个解决方案。

redux-promise

另一个异步操作的解决方案时让Action Creator返回一个Promise对象,这需要redux-promise中间件的支持。

import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise';
import reducer from './reducers';

const store = createStore(
  reducer,
  applyMiddleware(promiseMiddleware)
); 

这个中间件使得store.dispatch()可以接收Promise对象作为参数,这时有两种写法,一种是将Action Creator的返回值设置为一个Promise对象;
另一种是将Action的payload属性设置为Promise对象,这需要从redux-actions引入createAction方法。
两种写法如下:

//第一种
const fetchPosts = 
  (dispatch, postTitle) => new Promise(function (resolve, reject) {
     dispatch(requestPosts(postTitle));
     return fetch(`/some/API/${postTitle}.json`)
       .then(response => {
         type: 'FETCH_POSTS',
         payload: response.json()
       });
});
//第二种
import { createAction } from 'redux-actions';

class AsyncApp extends Component {
  componentDidMount() {
    const { dispatch, selectedPost } = this.props
    // 发出同步 Action
    dispatch(requestPosts(selectedPost));
    // 发出异步 Action
    dispatch(createAction(
      'FETCH_POSTS', 
      fetch(`/some/API/${postTitle}.json`)
        .then(response => response.json())
    ));
  }

createAction的第二个参数必须是一个Promise对象。
对于redux-promise的源码进行分析:

export default function promiseMiddleware({ dispatch }) {
  return next => action => {
    
    if (!isFSA(action)) {
      return isPromise(action)//Action是一个Promise
        ? action.then(dispatch)//resolve的值将会被送出,reject无动作
        : next(action);
    }

    return isPromise(action.payload)//Action的payload属性是一个Promise
      ? action.payload.then(
          result => dispatch({ ...action, payload: result }),//resolve发出Action
          error => {//reject发出Action
            dispatch({ ...action, payload: error, error: true });
            return Promise.reject(error);
          }
        )
      : next(action);
  };
}
原文地址:https://www.cnblogs.com/liuju/p/12639530.html