探究redux的副作用处理以及dispatch的promiseify

2,633 阅读5分钟

文章首发于语雀 探究redux的副作用处理以及dispatch的promiseify 源码分析

未经允许严禁转载

我们知道redux的作用是管理应用程序的状态,让复杂应用能够更好得处理数据,使视图与数据一一对应。那redux的运行机制是什么样的呢?大致讲一下,就是用户行为产生action,dispatch接收action,然后通过reducer处理后生成新的state,最后更新store以及视图。看👇图

image.png

副作用

redux的设计思想就是不产生副作用,数据更改的状态可回溯,所以redux中处处都是纯函数。

那怎么处理副作用呢?redux没有提供解决直接的方案。但是它提供一个中间件机制,让用户去开发副作用处理的中间件。很多优秀的中间件也随着出现,如redux-thunk、redux-promise、redux-saga等。那它们是如何处理副作用的呢?请看👇

redux-thunk

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;

redux-thunk最重要的思想,就是可以接受一个返回函数的 action creator。如果这个 action creator 返回的是一个函数,就执行它,如果不是,就按照原来的 next(action) 执行。 正因为这个 action creator 可以返回一个函数,那么就可以在这个函数中执行一些异步的操作。

redux-promise

// 判断是不是Promise函数
import isPromise from 'is-promise';
// 标准flux action
import { isFSA } from 'flux-standard-action';

export default function promiseMiddleware({ dispatch }) {
  return next => action => {
    // 首先判断action是不是flux规定的类型
    if (!isFSA(action)) {
      return isPromise(action) ? action.then(dispatch) : next(action);
    }

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

redux-promise将 promise 贯彻到底。将 promise 作为 action 传给 dispatch,让中间件处理 resolve,可以去少写 .then().catch() 之类的代码。 redux-thunk和redux-promise用法实际比较类似,都是触发一个 function/promise 让中间件自己决定处理副作用的时机。这能解决大部分的副作用场景,但对于更复杂的副作用情况,就需要写大量代码。而redux-saga正是能很好得解决这个问题。

redux-saga

用法和代码就不做探讨了。说说概念吧,redux-saga创造了一个saga层,专门用来处理副作用的。那么redux-saga是怎么处理副作用的呢? 首先,用户行为产生 action,派发 reducer 时,saga层监听到特定的 action(redux-saga提供一些辅助函数,用来监听将被派发到 reduceraction)进行处理。处理 action 的函数叫做 EffectEffect是个 Generate 函数,提供的 yield 可以用来控制代码执行,所以redux-saga适合处理异步。并且在 Effect 中也能继续发起一个普通 action,让 reducer 处理。 这就是redux-saga的大致执行过程。好了,现在进入本文第二个主题,如何使 dispatch promiseify

dispatch promiseify

这里的 dispatch promiseify 指的是在使用redux和redux-saga情况下去实现的。那么开始吧。

目标实现

function* asyncIncrease(action) {
  return action.payload
}
// 省略一些步骤
......
store.dispatch({ 
  type: 'asyncIncrease', payload: 30 
}).then(res => {
  console.log(res); // 30
})

因为 Effect 是个 Generate 函数,那先来瞧瞧 Generate 的一些概念吧。

  • 形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function 关键字与函数名之间有一个星号;二是,函数体内部使用 yield 表达式,定义不同的内部状态
  • 调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象
  • 遇到 yield 表达式,就暂停执行后面的操作,并将紧跟在 yield 后面的那个表达式的值,作为返回的对象的 value 属性值。

好了,暂时就这些概念,接下来看看怎么实现吧。 用户发起 actionreducer 后。因为 reducer是个纯函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。所以我们得区分 action,是由redux-saga处理还是reducer处理。所以得有个中间件,并且返回一个 promise,使 dispatch 能够使用then 方法。

const promiseMiddlware = () => next => action => {
  // 通过type来判断是不是由Effect处理
  // 如果是,返回promise
  if (isEffect(action.type) {
    return new Promise((resolve, reject) => {
      next({
        resolve,
        reject,
        ...action,
      })
    })
  } else {
    return next(action)
  }
}
// action其实就包含了resolve,reject和payload
function* asyncIncreate(action) {
  const { resolve, payload } = action
  resolve(payload)
}

store.dispatch({ 
  type: 'asyncIncrease', payload: 30 
}).then(res => {
  console.log(res); // 30
})

实现了 dispatchpromiseify,但不觉得每次写个 Effect,还需要写 resolve,这样不麻烦吗?那我们转变一下思路,能不能再有个 Effect 专门来包装实际的 Effect,外层 Effectresolve 呢?

// 1. 先写个外层的Effect
function* baseEffect(action) {
	const {resolve, reject,...rest} = action
  // 2. 通过yield执行实际的Effect
  const res = yield asyncIncreate(rest)
  resolve(res)
}
// asyncIncreate就返回值就ok了
function* asyncIncreate(action) {
	return action.payload
}
// 省略createStore等步骤
store.dispatch({ 
  type: 'asyncIncrease', payload: 30 
}).then(res => {
  console.log(res); // 30
})

好了,dispatchpromiseify 就实现了,是不是挺简单的,这只是个粗略版本,还可以更灵活好用,需要深入了解的,可以去dva的源码。

总结

redux是目前最流行的react的状态管理库。由于redux的设计思想,导致副作用是个问题,但也不难解决,有优秀的中间件可以去解决。在使用redux和redux-saga时,如果想使 dispatch promiseify 呢,可以去写个中间件,再搭配一个外层 Effect 就ok了。