在react+redux的技术栈中优雅的使用异步

简介: 在使用react和redux进行前端开发时,一定会遇到异步的action处理,这个使用redux-thunk可以很好地处理,相信你已经知道了。但对于多个异步请求级联触发的情况,怎么处理才好呢? 本文使用一个实际的例子就这个问题进行一些探讨。相关代码都在这个代码库中:https://github.com/cui-liqiang/react-redux-async-chain-example

在使用react和redux进行前端开发时,一定会遇到异步的action处理,这个使用redux-thunk可以很好地处理,相信你已经知道了。但对于多个异步请求级联触发的情况,怎么处理才好呢?

本文使用一个实际的例子就这个问题进行一些探讨。相关代码都在这个代码库中:https://github.com/cui-liqiang/react-redux-async-chain-example

阅读本文,假设你已经理解了以下知识:

  1. react和redux的基本使用
  2. redux store的中间件机制及redux-thunk中间件
  3. co和generator
  4. async/await
  5. promise

基础代码

下载示例代码,切换到base分支。并在根目录中启动web server之后。访问http://localhost:8000/index.html ,便可看到下面的图片:

其中左边是模拟的后台数据。右边是可以进行的操作。在这个分支上可以看到两个操作:

  1. 按照名字加载用户
  2. 按照用户ID加载Post

这两个接口背后是通过setTimeout模拟出来的(fakeRemote.jsx)

fetchUser(name)
fetchPostsByUser(userId)

模拟的异步接口是回调风格的,并使用redux-chunk中间件来处理异步action。见action.jsx:

function fetchUserAction(name) {
  return dispatch => {
    return fetchUser({
      name
    }, (result) => {
      return dispatch(setUser(result));
    })
  }
}

要处理的问题

现在已经有了两个操作,我们要实现第三个操作:”按用户名加载用户信息及Post“。也就是上面两个操作的组合。但由于这两个操作都是异步的,因此最简单的组合方式就是新写一个action,叫做“fetchUserAndRelatedPost”,使用回调套回调的方式实现。这种方式有两个问题:

  1. 回调套回调太恶心(曾经忍着恶心写了4层的异步回调,写到最后,各种括号都对不上了)
  2. 多个action之间也出现了重复

因此期望的方式是能够在react组件中复用已有的这两个action,进行优雅的组合。本代码库使用了两种方式进行了实现。

使用async/await

切换到async分支看最终代码。

async/await是一种化异步为同步的编码方式,具体原理不在这里展开。简单讲一下代码。

首先,能够await的东西需要是一个promise。什么是promise呢,就是能够响应then和catch这两函数的一个对象。所以需要把之前异步action中的回调风格改成promise风格。

//这里加了1的后缀,是因为没有使用后端预处理,所有函数都在顶层作用域,和后面要将的co中的一个方法重名了,所以要区分下。
function toPromise1(f) {
  return (option) => {
    return new Promise((resolve, reject) => {
      f(option, resolve)
    })
  }
}

然后把前面的回调风格的action改成promise风格:

function fetchUserAction(name) {
  return dispatch => {
    return toPromise1(fetchUser)({
      name
    }).then((result) => {
      return dispatch(setUser(result));
    })
  }
}

这里需要强调的一点,这些Action结尾的函数,其实并不是Action,而是“ActionCreator”,调用它返回的那个值才是action,因为太长了,所以都懒得写全。经过react-redux的connect之后,在react组件中调用this.props.fetchUserAction(userName),就相当于调用store.dispatch(fetchUserAction(userName))。由于这个action是个函数,且我们使用了redux-thunk这个中间件,所以这个调用最终的返回值其实是下面这一坨东西。

return toPromise1(fetchUser)({
  name
}).then((result) => {
  return dispatch(setUser(result));
})

也就是一个promise,上面提到了只有promise才能被await,所以才能在react组件中使用这样的代码:await this.props.fetchUserAction(this.state.name)

然后还需要把action中返回的函数使用async进行修饰,这样才能在react组件中使用await进行等待:

// action定义
function fetchUserAction(name) {
  return async dispatch => {
    return toPromise1(fetchUser)({
      name
    }).then((result) => {
      return dispatch(setUser(result));
    })
  }
}

// 组件中dispatch的调用(container.jsx)
// 由于await只能在async标记的函数中使用,所以这里也加上了async修饰
async fetchUserAndPost() {
  await this.props.fetchUserAction(this.state.name)
  await this.props.fetchPostsByUserAction(this.props.user.id)
}

这样就可以在不增加新的action的前提下,在组件中通过组合来完成自己特性的业务诉求。

使用co+generator

切换到co分支看代码。

co+generator也是化异步为同步的神器。效果上基本和async/await类似,写法略有不同。

相同之处是,都需要action(不是action creator哦)返回的结果是一个promise。不同之处是,返回promise的那个函数不需要使用async修饰。

await只能在async的函数中使用,本方案中与await对应的是yield,它只能在generator中使用。并且需要使用co这个函数来驱动generator的执行。因此写出来是这样的:

// action定义,不需要async修饰
function fetchUserAction(name) {
  return dispatch => {
    return toPromise1(fetchUser)({
      name
    }).then((result) => {
      return dispatch(setUser(result));
    })
  }
}

// 使用co驱动一个generator,在generator内部使用yield表示等待(container.jsx)
fetchUserAndPost() {
  const that = this;
  co(function* () {
    yield that.props.fetchUserAction(that.state.name)
    that.props.fetchPostsByUserAction(that.props.user.id)
  })
}

可以看出,async方案和co+generator都能达到不错的效果。且原理非常的相似,简单的总结下:

  1. 串行化代码的上下文,async/await使用关键字驱动;co+generator还需要使用co这个函数包一层来驱动,这点上比async/await差一点。
  2. 等待方式,一个用await,一个用yield,都是在等待一个promise的完成。
  3. 都需要作用于promise。

最后强调一下,上面的写法本质都是语法糖,实际的执行还都是异步的,只是编写起来更加符合直觉,编写出来的代码更加易于理解和维护。

其他方案

上面的两个方案其实已经足够优雅了,但如果你对无状态有非常高的追求,还可以尝试使用redux-saga来进一步隔离无副作用和有副作用的代码。它也是使用generator来实现的,可以理解为它把上述方案中的co部分的工作做掉了,并且让react组件中的代码“看起来”完全无副作用。相信理解上上述的做法,理解redux-saga就很容易了。

目录
相关文章
|
1月前
|
JavaScript 前端开发 算法
React技术栈-虚拟DOM和DOM diff算法
这篇文章介绍了React技术栈中的虚拟DOM和DOM diff算法,并通过一个实际案例展示了如何使用React组件和状态管理来实现动态更新UI。
34 2
|
2月前
|
存储 JavaScript 前端开发
React中使用redux
React中使用redux
128 56
|
1月前
|
消息中间件 前端开发
React技术栈-组件间通信的2种方式
本文介绍了React技术栈中组件间通信的两种方式:通过props传递数据和使用消息发布(publish)-订阅(subscribe)机制,并通过实例代码展示了如何使用PubSubJS库实现跨组件通信。
46 11
React技术栈-组件间通信的2种方式
|
1月前
|
前端开发
React技术栈-react使用的Ajax请求库实战案例
这篇文章介绍了在React应用中使用Axios和Fetch库进行Ajax请求的实战案例,展示了如何通过这些库发送GET和POST请求,并处理响应和错误。
34 10
React技术栈-react使用的Ajax请求库实战案例
|
1月前
|
前端开发
React技术栈-react使用的Ajax请求库用户搜索案例
这篇文章展示了一个React技术栈中使用Ajax请求库(如axios)进行用户搜索的实战案例,包括React组件的结构、状态管理以及如何通过Ajax请求获取并展示GitHub用户数据。
24 7
React技术栈-react使用的Ajax请求库用户搜索案例
|
1月前
|
前端开发 NoSQL MongoDB
React技术栈-基于react脚手架编写评论管理案例
这篇文章介绍了在MongoDB中使用sort和投影来对查询结果进行排序和限制返回的字段,通过具体的命令示例展示了如何实现这些操作。
37 6
React技术栈-基于react脚手架编写评论管理案例
|
1月前
|
前端开发 Python
React技术栈-React路由插件之自定义组件标签
关于React技术栈中React路由插件自定义组件标签的教程。
45 4
React技术栈-React路由插件之自定义组件标签
|
1月前
|
前端开发 JavaScript
React技术栈-React UI之ant-design使用入门
关于React技术栈中使用ant-design库的入门教程,包括了创建React应用、搭建开发环境、配置按需加载、编写和运行代码的步骤,以及遇到错误的解决方法。
24 2
React技术栈-React UI之ant-design使用入门
|
1月前
|
前端开发 程序员 API
React技术栈-React路由插件之react-router的基本使用
这篇博客介绍了React路由插件react-router的基本使用,包括其概念、API、以及如何通过实战案例在React应用中实现SPA(单页Web应用)的路由管理。
41 9
|
1月前
|
前端开发 JavaScript
React技术栈-react的脚手架创建应用案例
本文介绍了如何使用React的官方脚手架工具create-react-app快速创建React项目,并展示了项目的目录结构和基本的代码文件,以及如何启动和运行React应用。
24 2

热门文章

最新文章

下一篇
无影云桌面