React组件如何优雅地处理异步数据详解

目录
  • 前言
  • API介绍
    • Suspense
    • Error Boundaries
  • 完整方案
    • 处理异步请求的子组件
    • 外层组件
  • 总结

前言

我们在编写React应用的时候,常常需要在组件里面异步获取数据。因为异步请求是需要一定时间才能结束的,通常我们为了更好的用户体验会在请求还没有结束前给用户展示一个loading的状态,然后如果发生了错误还要在页面上面展示错误的原因,只有当请求结束并且没有错误的情况下,我们才渲染出最终的数据。这个需求十分常见,如果你的代码封装得比较好的话,你的处理逻辑大概是这样的:

const AsyncComponent = () => {
    const [data, isLoading, error] = fetchData('./someapi')
    if (isLoading) {
      return <Loading />
    }
    if (error) {
      return <Error error={error} />
    }
    return <DisplayData
        data={data}
    />
}

在上面的代码中我展示了大多数项目里面常用的做法,那就是:封装一个自定义的hook(fetchData) 来处理异步请求的不同状态 - pending, errorsuccess。这种做法一般情况下是没有什么问题的,至少比没有封装要好很多,可是当我们的项目规模变大了以后,你会发现我们还是需要写很多模板代码,因为每次调用完fetchData都需要判断isLoadingerror的值然后展示相对应的内容。那么有没有一种办法可以让我们在某些地方统一处理pendingerror的情况,从而我们在组件里面只需要处理success的情况呢?答案是肯定的,本篇文章将会提供一种基于SuspenseErrorBoundary的思路来解决这个问题。

API介绍

在介绍具体方案之前,我们先来看看会用到的两个组件 - SuspenseErrorBoundary的具体用法。

Suspense

React 16.6引入了Suspense组件,这个组件会在其子组件还处于pending状态时展示一个fallback的效果,例如:

import { Suspense } from 'react'
<Suspense fallback={<Loading />}>
  <SomeComponent />
</Suspense>

在上面的代码中当SomeComponent处于pending状态时,Suspense会展示Loading组件。看到这里你可能会问Suspense组件是怎么知道SomeComponent处于pending状态的呢?它的原理简单来说就是这个组件会捕获子组件抛出来的异常,如果这个异常是一个promise,而且这个promisepending状态的它就显示fallback的内容否则就渲染其子组件。

其实如果你有做过Code Spliting的优化,你大概率已经用过这个组件了,一般它会用来懒加载某个组件,例如下面的代码:

import { lazy, Suspense } from 'react'
const LazyComponent = lazy(() => import('./component'))
<Suspense fallback={<Loading />}>
  <LazyComponent />
<Suspense>

Error Boundaries

Error Boundaries也是React 16引入进来的概念。它的引入是为了解决某个组件发生错误的时候整个页面crash的情况(白屏)。有了Error Boundaries这个功能后,你可以实现一个ErrorBoundary组件,这个组件可以捕获到从子组件抛出来的错误,然后你就可以对这个错误进行自定义的处理从而防止这个错误直接传递到应用的最外层导致整个应用的奔溃。以下是一个常见的ErrorBoundary组件的实现:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props)
    // 使用state来保存当前组件的错误信息
    this.state = {error: null}
  }
  // 就是这个函数实现了error boundary的功能,用来返回错误出现后的state
  static getDerivedStateFromError(error) {
    return { error }
  }
  render() {
    // 如果组件发生了错误那么就展示错误的信息否则渲染子组件的内容
    if (this.state.error) {
      return <div>error occur</div>
    }
    return this.props.children
  }
}

完整方案

在介绍完我们需要用到的两个组件SuspenseErrorBoundary后,我们终于可以来看一下实际的方案了。我们的方案很简单,总的来说就是:在需要处理异步请求的组件外面包裹一层Suspense组件和ErrorBoundary组件,其中Suspense组件处理异步请求的pending状态,而ErrorBoundary处理请求的error状态。Talk is cheap, show me the code。我们来看一下具体的代码实现:

处理异步请求的子组件

假如我们需要实现一个组件,这个组件会调用一个返回随机单词的接口,当结果返回后我们需要显示返回的单词。我们这里要调用的接口是一个公共的接口,地址是https://api.api-ninjas.com/v1/randomword,调用这个接口的一个示例返回值是:

{
  "word": "Stokesia"
}

接着我们来实现子组件的相关代码:

// utils/fetchData.js
// 这个函数式是对fetch函数的封装,它在请求pending和error的状态下都会抛出异常
export const fetchData = (url) => {
  // 记录当前请求的状态
  let status = 'pending'
  // 记录请求的结果
  let response
  const promise = fetch(url)
    .then(res => res.json())
    .then(res => {
      // 请求成功,转变状态
      status = 'success'
      // 保存请求的结果
      response = res
    })
    .catch(error => {
      // 请求失败,转变状态
      status = 'error'
      // 保存接口的错误信息
      response = error
    })
  return () => {
    switch(status) {
      // 如果请求还在进行中就抛出promise的异常,这个promise会被外层的Suspense处理
      case 'pending':
        throw promise
      // 如果请求出现错误就抛出错误信息,这个错误信息会被外层的ErrorBoundary处理
      case 'error':
        throw response
      // 如果请求已经完成,就直接返回数据
      case 'success':
        return response
      default:
        throw new Error('unexpected status')
    }
  }
}
// RandomWord.jsx
import { fetchData } from './utils/fetchData'
// 调用上面的fetchData函数来获取一个包装完毕的fetch函数
const randomWordFetch = fetchData('https://api.api-ninjas.com/v1/randomword')
const RandomWord = () => {
  const response = randomWordFetch()
  // 如果代码能执行到这里就表示接口已经调用成功并且返回了
  const word = response.word
  return <div>
    {word}
  </div>
}
export default RandomWord

外层组件

编写完子组件的代码后,我们再来看看外层组件(App)的代码:

// App.jsx
import ErrorBoundary from "./ErrorBoundary"
import RandomWord from "./RandomWord"
import {Suspense} from 'react'
function App() {
  return (
   <ErrorBoundary>
    <Suspense fallback={<div>loading...</div>}>
      <RandomWord />
    </Suspense>
   </ErrorBoundary>
  )
}
export default App

看到这里你可能会说每次都需要在子组件最外层使用SuspenseErrorBoundary组件的话感觉跟文章开始前介绍的方案没有很大的区别。其实不是的,这种做法和开头的思路的最大区别就是:这种做法可以统一在最外层处理所有子组件的状态。举个例子,你可以在路由的最外层处理所有子路由的异步请求状态:

<ErrorBoundary>
  <Suspense fallback={<Loading />}>
    <Switch>
      <Route path='/a' component={ComponentA} />
      <Route path='/b' component={ComponentB} />
      ...
    </Switch>
  </Suspense>
</ErrorBoundary>

你看当项目规模变大后,这种写法一下子就简单很多了,因为你只需要处理一次异步请求的逻辑即可!

总结

上面的代码只是给大家说了一个使用SuspenseErrorBoundary组件来优雅地处理异步请求的大概思路,单纯从实现上看还有很多不完善的地方,例如子组件对fetchData的调用放在了组件定义之外,这个做法是不够完善的,更好的做法是在组件内部使用useMemo来缓存对某个请求的调用,由于文章篇幅的限制我在这里就不再论述了,感兴趣的同学可以在项目里面自己实践一下。

以上就是React组件如何优雅地处理异步数据的详细内容,更多关于React处理异步数据的资料请关注我们其它相关文章!

(0)

相关推荐

  • React组件化的一些额外知识点补充

    目录 React的额外补充 Portals的使用 Fragment的使用 严格模式StrictMode 总结 React的额外补充 Portals的使用 某些情况下,我们希望渲染的内容独立于父组件,甚至是独立于当前挂载到的DOM元素中(默认都是挂载到id为root的DOM 元素上的). Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案: 第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment; 第二个参数(conta

  • react组件的创建与更新实现流程详解

    目录 React源码执行流程图 legacyRenderSubtreeIntoContainer legacyCreateRootFromDOMContainer createLegacyRoot ReactDOMBlockingRoot createRootImpl createContainer createFiberRoot createHostRootFiber createFiber updateContainer 总结 这一章节就来讲讲ReactDOM.render()方法的内部实现

  • React HOC高阶组件深入讲解

    目录 1. 概念 2. 属性代理 2.1 代理props 2.2 条件渲染 2.3 添加状态 3. 反向继承 3.1 拦截渲染 3.2 劫持生命周期 3.3 操作state 3.4 修改react树 3.5 记录渲染性能 4. 使用装饰器 4.1 安装和配置 4.2 使用 5.总结 1. 概念 高阶组件和高阶函数的类似,使用函数接收一个组件,并返回一个组件. function withList(WrapComponent) { return class extends Component { r

  • react 组件实现无缝轮播示例详解

    目录 正文 无缝轮播 实现思路 构思使用时代码结构 Carousel组件 CarouselItem组件 完善组件 完成小圆点 正文 需求是做一个无缝轮播图,我说这不是有很多现成的轮子吗?后来了解到他有一个特殊的需求,他要求小圆点需要在轮播图外面,因为现在大部分插件都是将小圆点写在轮播图内部的,这对于不了解插件内部结构的小伙伴确实不知道如何修改. 很久没有写插件的我准备写一个插件(react) 无缝轮播 无缝轮播从最后一张到第一张的过程中不会原路返回,它就像轮子似的,从结束到开始是无缝连接的,非常

  • React组件的应用介绍

    目录 1. 介绍 2. 组件的创建方式 2.1 函数创建组件 2.2 类组件 3. 父子组件传值 3.1 函数组件 3.2 类组件 1. 介绍 组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思.从概念上类似于 JavaScript 类或函数.它接受任意的形参( props ),并返回用于描述页面展示内容的 React元素( jsx ). 定义好的组件一定要有返回值. react中定义组件的2种方式 函数组件(无状态组件/UI组件) 类组件(状态组件/容器组件) 2. 组件

  • React组件如何优雅地处理异步数据详解

    目录 前言 API介绍 Suspense Error Boundaries 完整方案 处理异步请求的子组件 外层组件 总结 前言 我们在编写React应用的时候,常常需要在组件里面异步获取数据.因为异步请求是需要一定时间才能结束的,通常我们为了更好的用户体验会在请求还没有结束前给用户展示一个loading的状态,然后如果发生了错误还要在页面上面展示错误的原因,只有当请求结束并且没有错误的情况下,我们才渲染出最终的数据.这个需求十分常见,如果你的代码封装得比较好的话,你的处理逻辑大概是这样的: c

  • Angularjs的$http异步删除数据详解及实例

    Angularjs的$http异步删除数据详解及实例 有人会说删除这东西有什么可讲的,写个删除的service,controller调用一下不就完了. 嗯...看起来是这样,但是具体实现起来真的有这么简单吗?首先有以下几个坑 怎么确定数据是否删除成功? 怎么同步视图的数据库的内容? 1.思路 1.实现方式一 删除数据库中对应的内容,然后将$scope中的对应的内容splice 2.实现方式二 删除数据库中对应的内容,然后再reload一下数据(也就是再调用一次查询方法,这种消耗可想而知,并且还要

  • react装饰器与高阶组件及简单样式修改的操作详解

    使用装饰器调用 装饰器 用来装饰类的,可以增强类,在不修改类的内部的源码的同时,增强它的能力(属性或方法) 装饰器使用@函数名写法,对类进行装饰,目前在js中还是提案,使用需要配置相关兼容代码库. react脚手架创建的项目默认是不支持装饰器,需要手动安装相关模块和添加配置文件 安装相关模块 yarn add -D customize-cra react-app-rewired  @babel/plugin-proposal-decorators 修改package.json文件中scripts

  • react电商商品列表的实现流程详解

    目录 整体页面效果 项目技术点 拦截器的配置 主页面 添加商品 分页与搜索 修改商品 删除商品 完整代码 整体页面效果 项目技术点 antd组件库,@ant-design/icons antd的图标库 axios 接口请求,拦截器配置 node-sass sass-loader css样式的一个嵌套 react-router-dom react路由使用 react-redux redux hooks:大多数我们用的是函数组件,函数组件没有state属性,所以我们使用hooks来初始化数据,并且函

  • 无UI 组件Headless框架逻辑原理用法示例详解

    目录 概述 精读 总结 概述 Headless 组件即无 UI 组件,框架仅提供逻辑,UI 交给业务实现.这样带来的好处是业务有极大的 UI 自定义空间,而对框架来说,只考虑逻辑可以让自己更轻松的覆盖更多场景,满足更多开发者不同的诉求. 我们以 headlessui-tabs 为例看看它的用法,并读一读 源码. headless tabs 最简单的用法如下: import { Tab } from "@headlessui/react"; function MyTabs() { ret

  • react echarts tree树图搜索展开功能示例详解

    目录 前言 最终效果 版本信息 核心功能: 关键思路: 附上代码 数据data.js 功能: TreeUtils 总结: 前言 umi+antd-admin 框架中使用类组件+antd结合echarts完成树图数据展示和搜索展开功能 最终效果 版本信息 "antd": "3.24.2", "umi": "^2.7.7", "echarts": "^4.4.0", "echart

  • React Fiber 树思想解决业务实际场景详解

    目录 熟悉 Fiber 树结构 业务场景 熟悉 Fiber 树结构 我们知道,React 从 V16 版本开始采用 Fiber 树架构来实现渲染和更新机制. Fiber 在 React 源码中可以看作是一个任务执行单元,每个 React Element 都会有一个与之对应的 Fiber 节点. Fiber 节点的核心数据结构如下: type Fiber = { type: any, //类型 return: Fiber, //父节点 child: Fiber, // 指向第一个子节点 sibli

  • log4j2异步Logger(详解)

    1 异步Logger的意义 之前的日志框架基本都实现了AsyncAppender,被证明对性能的提升作用非常明显. 在log4j2日志框架中,增加了对Logger的异步实现.那么这一步的解耦,意义何在呢? 如图,按我目前的理解:异步Logger是让业务逻辑把日志信息放入Disruptor队列后可以直接返回(无需等待"挂载的各个Appender"都取走数据) 优点:更高吞吐.调用log方法更低的延迟. 缺点:异常处理麻烦. 可变日志消息问题.更大的CPU开销.需要等待"最慢的A

  • java异步编程详解

    很多时候我们都希望能够最大的利用资源,比如在进行IO操作的时候尽可能的避免同步阻塞的等待,因为这会浪费CPU的资源.如果在有可读的数据的时候能够通知程序执行读操作甚至由操作系统内核帮助我们完成数据的拷贝,这再好不过了.从NIO到CompletableFuture.Lambda.Fork/Join,java一直在努力让程序尽可能变的异步甚至拥有更高的并行度,这一点一些函数式语言做的比较好,因此java也或多或少的借鉴了某些特性.下面介绍一种非常常用的实现异步操作的方式. 考虑有一个耗时的操作,操作

随机推荐