为什么要专门来处理异步呢,因为redux的设计原则要求我们的reducer必须是纯函数,所以我们在reducer去执行一些副作用的业务是不合法的。那么怎么处理异步呢?笔者认为,对于真正理解了redux的开发者来说,处理异步并不是一件难事。如果还不够深入的理解Redux以及其的中间件机制,那么以下的内容可能引起不适,会看的云里雾里,建议翻看笔者第一篇文章从0到1重新撸一个Redux

redux-thunk

先来看看源码吧

  1. function createThunkMiddleware(extraArgument) {
  2. return ({ dispatch, getState }) => next => action => {
  3. // 判断dispatch的参数是否是函数,是函数就把dispatch getstate 参数交出
  4. // 让函数自己去玩
  5. if (typeof action === 'function') {
  6. return action(dispatch, getState, extraArgument);
  7. }
  8. return next(action);
  9. };
  10. }

原理很简单,dispatch不仅可以dispatch原有的action,也可以dispatch函数,中间件thunk发现action是函数则把所有的核心方法和参数交出。看看怎么实际应用

import fetch from 'fetch'

// 先设计一个异步请求
export function fetchNews(id) {

  // Thunk middleware 知道如何处理函数。
  // 这里把 dispatch 方法通过参数的形式传给函数,
  // 以此来让它自己也能 dispatch action。

  return function (dispatch) {

    // 首次 dispatch:更新应用的 state 来通知
    // thunk middleware 调用的函数可以有返回值,
    // 它会被当作 dispatch 方法的返回值传递。

    // 这个案例中,我们返回一个等待处理的 promise。
    // 这并不是 redux middleware 所必须的,但这对于我们而言很方便。

    return fetch(`/api/getNews`, {id})
      .then(
        response => response.json(),
         error => console.log('An error occurred.', error)
      )
      .then(json =>
        dispatch({
            name: 'add_news',
            data: json
        })
      )
  }
}

把异步的业务放进redux中去

const store = createStore(
  rootReducer,
  applyMiddleware(
    thunkMiddleware, // 允许我们 dispatch() 函数
  )
)
store.dispatch(fetchNews(1)).then(() => console.log(store.getState()))

如此一来,我们把异步的业务封装一下,把他当做一个异步action交给reudx的dispatch便可轻松解决redux本身不支持异步的问题。那么试问是否还有更好的异步解决方法呢?回到redux和异步业务本身,最为关键和直接的一点便是异步中业务可以dispatch,如果满足这个需求无论什么方法都可以解决redux异步的问题。

Promise.then与store

直接看吧

// redux module

// 直接导出store
export const store = createStore(
  rootReducer
)
// business module
import * as React from 'react';
import './index.less';
import {store} from './redux'

class Header extends React.Component {
  constructor(props: LogoProps) {
    super(props);
  }

  componentDidMount() {
    fetch(`/api/getNews`, {id: 1})
      .then(
        response => response.json(),
         error => console.log('An error occurred.', error)
      )
      .then(json =>
         store.dispatch({
             name:  'add_news',
             data: json
         })
      )
  }

  render() {
    return (<div>
      hello world
    </div>);
  }
}

export default Header;

如此一来,连中间件都不需要就可以轻松解决异步,但是这样不利于代码管理,我们可以按模块的把每一个异步业务给聚合起来。

async/awit和BaseAction

先创建一个封装了store的dispatch的基类

import {store} from './redux';
export default class ActionModeBase {
  dispatch(params) {
    store.dispatch(params);
  }
}

再创建一个处理请求新闻业务的类

import BaseAction from './ActionModeBase';
import fetch from 'fetch'
export default class HandleNewsMode {
    getNews = async (id) => {
        const news = await fetch('/api/getNews', id);
        this.dispatch({
            name: 'add_news',
            data: news
        })
    }
}

业务方引入异步业务的类

import * as React from 'react';
import './index.less';
import HandleNewsMode from './HandleNewsMode'

class Header extends React.Component {
  constructor(props: LogoProps) {
    super(props);

    // 初始化
    this.businessMode = new HandleNewsMode();
  }

  componentDidMount() {
      this.businessMode.getNews(1);
  }

  render() {
    return (<div>
      hello world
    </div>);
  }

总结

处理redux的异步并不是一件难事,redux-thunk也不是万金油,对于更为复杂的异步场景笔者认为通过内聚异步业务+async更能胜任。