我的编程空间,编程开发者的网络收藏夹
学习永远不晚

React之Suspense提出的背景及使用方法是什么

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

React之Suspense提出的背景及使用方法是什么

本篇内容主要讲解“React之Suspense提出的背景及使用方法是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“React之Suspense提出的背景及使用方法是什么”吧!

    Suspense 提出的背景

    假设我们现在有如下一个应用:

    const Articles = () => {  const [articles, setArticles] = useState(null)  useEffect(() => {    getArticles().then((a) => setArticles(a))  }, [])  if (articles === null) {    return <p>Loading articles...</p>  }  return (    <ul>      {articles.map((article) => (        <li key={article.id}>          <h5>{article.title}</h5>          <p>{article.abstract}</p>        </li>      ))}    </ul>  )}export default function Profile() {  const [user, setUser] = useState(null)  useEffect(() => {    getUser().then((u) => setUser(u))  }, [])  if (user === null) {    return <p>Loading user...</p>  }  return (    <>      <h4>{user.name}</h4>      <Articles articles={articles} />    </>  )}

    该应用是一个用户的个人主页,包含用户的基本信息(例子中只有名字)以及用户的文章列表,并且规定了必须等待用户获取成功后才能渲染其基本信息以及文章列表。 该应用看似简单,但却存在着以下几个问题:

    • "Waterfalls",意思是文章列表必须要等到用户请求成功以后才能开始渲染,从而对于文章列表的请求也会被用户阻塞,但其实对于文章的请求是可以同用户并行的。

    • "fetch-on-render",无论是 Profile 还是 Articles 组件,都是需要等到渲染一次后才能发出请求。

    对于第一个问题,我们可以通过修改代码来优化:

    const Articles = ({articles}) => {  if (articles === null) {    return <p>Loading articles...</p>  }  return (    <ul>      {articles.map((article) => (        <li key={article.id}>          <h5>{article.title}</h5>          <p>{article.abstract}</p>        </li>      ))}    </ul>  )}export default function Profile() {  const [user, setUser] = useState(null)  const [articles, setArticles] = useState(null)  useEffect(() => {    getUser().then((u) => setUser(u))    getArticles().then((a) => setArticles(a))  }, [])  if (user === null) {    return <p>Loading user...</p>  }  return (    <>      <h4>{user.name}</h4>      <Articles articles={articles} />    </>  )}

    现在获取用户和获取文章列表的逻辑已经可以并行了,但是这样又导致 Articles 组件同其数据获取相关的逻辑分离,随着应用变得复杂后,这种方式可能会难以维护。同时第二个问题 "fetch-on-render" 还是没有解决。而 Suspense 的出现可以很好的解决这些问题,接下来就来看看是如何解决的。

    Suspense 的使用

    Suspense 用于数据获取

    还是上面的例子,我们使用 Suspense 来改造一下:

    // Profile.jsimport React, {Suspense} from 'react'import User from './User'import Articles from './Articles'export default function Profile() {  return (    <Suspense fallback={<p>Loading user...</p>}>      <User />      <Suspense fallback={<p>Loading articles...</p>}>        <Articles />      </Suspense>    </Suspense>  )}// Articles.jsimport React from 'react'import {getArticlesResource} from './resource'const articlesResource = getArticlesResource()const Articles = () => {  debugger  const articles = articlesResource.read()  return (    <ul>      {articles.map((article) => (        <li key={article.id}>          <h5>{article.title}</h5>          <p>{article.abstract}</p>        </li>      ))}    </ul>  )}// User.jsimport React from 'react'import {getUserResource} from './resource'const userResource = getUserResource()const User = () => {  const user = userResource.read()  return <h4>{user.name}</h4>}// resource.jsexport function wrapPromise(promise) {  let status = 'pending'  let result  let suspender = promise.then(    (r) => {      debugger      status = 'success'      result = r    },    (e) => {      status = 'error'      result = e    }  )  return {    read() {      if (status === 'pending') {        throw suspender      } else if (status === 'error') {        throw result      } else if (status === 'success') {        return result      }    },  }}export function getArticles() {  return new Promise((resolve, reject) => {    const list = [...new Array(10)].map((_, index) => ({      id: index,      title: `Title${index + 1}`,      abstract: `Abstract${index + 1}`,    }))    setTimeout(() => {      resolve(list)    }, 2000)  })}export function getUser() {  return new Promise((resolve, reject) => {    setTimeout(() => {      resolve({        name: 'Ayou',        age: 18,        vocation: 'Program Ape',      })    }, 3000)  })}export const getUserResource = () => {  return wrapPromise(getUser())}export const getArticlesResource = () => {  return wrapPromise(getArticles())}

    首先,在 Profile.js 中开始引入 UserArticles 的时候就已经开始请求数据了,即 "Render-as-You-Fetch"(渲染的时候请求),且两者是并行的。当渲染到 User 组件的时候,由于此时接口请求还未返回,const user = userResource.read() 会抛出异常:

    ...  read() {    if (status === 'pending') {      throw suspender    } else if (status === 'error') {      throw result    } else if (status === 'success') {      return result    }  },...

    Suspense 组件的作用是,当发现其包裹的组件抛出异常且异常为 Promise 对象时,会渲染 fallback 中的内容,即 <p>Loading user...</p>。等到 Promise 对象 resolve 的时候会再次触发重新渲染,显示其包裹的内容,又因为获取文章列表的时间比用户短,所以这里会同时显示用户信息及其文章列表(具体过程后续会再进行分析)。这样,通过 Suspense 组件,我们就解决了前面的两个问题。

    同时,使用 Suspense 还会有另外一个好处,假设我们现在改变我们的需求,允许用户信息和文章列表独立渲染,则使用 Suspense 重构起来会比较简单:

    React之Suspense提出的背景及使用方法是什么

    而如果使用原来的方式,则需要修改的地方比较多:

    React之Suspense提出的背景及使用方法是什么

    可见,使用 Suspense 会带来很多好处。当然,上文为了方便说明,写得非常简单,实际开发时会结合 Relay 这样的库来使用,由于这一款目前还处于试验阶段,所以暂时先不做过多的讨论。

    Suspense 除了可以用于上面的数据获取这种场景外,还可以用来实现 Lazy Component

    Lazy Component

    import React, {Suspense} from 'react'const MyComp = React.lazy(() => import('./MyComp'))export default App() {  return (    <Suspense fallback={<p>Loading Component...</p>}>      <MyComp />    </Suspense>  )}

    我们知道 import('./MyComp') 返回的是一个 Promise 对象,其 resolve 的是一个模块,既然如此那这样也是可以的:

    import React, {Suspense} from 'react'const MyComp = React.lazy(  () =>    new Promise((resolve) =>      setTimeout(        () =>          resolve({            default: function MyComp() {              return <div>My Comp</div>            },          }),        1000      )    ))export default function App() {  return (    <Suspense fallback={<p>Loading Component...</p>}>      <MyComp />    </Suspense>  )}

    甚至,我们可以通过请求来获取 Lazy Component 的代码:

    import React, {Suspense} from 'react'const MyComp = React.lazy(  () =>    new Promise(async (resolve) => {      const code = await fetch('http://xxxx')      const module = {exports: {}}      Function('export, module', code)(module.exports, module)      resolve({default: module.exports})    }))export default function App() {  return (    <Suspense fallback={<p>Loading Component...</p>}>      <MyComp />    </Suspense>  )}

    这也是我们实现远程组件的基本原理。

    原理

    介绍了这么多关于 Suspense 的内容后,你一定很好奇它到底是如何实现的吧,我们先不研究 React 源码,先尝试自己实现一个 Suspense

    import React, {Component} from 'react'export default class Suspense extends Component {  state = {    isLoading: false,  }  componentDidCatch(error, info) {    if (this._mounted) {      if (typeof error.then === 'function') {        this.setState({isLoading: true})        error.then(() => {          if (this._mounted) {            this.setState({isLoading: false})          }        })      }    }  }  componentDidMount() {    this._mounted = true  }  componentWillUnmount() {    this._mounted = false  }  render() {    const {children, fallback} = this.props    const {isLoading} = this.state    return isLoading ? fallback : children  }}

    其核心原理就是利用了 “Error Boundary” 来捕获子组件中的抛出的异常,且如果抛出的异常为 Promise 对象,则在传入其 then 方法的回调中改变 state 触发重新渲染。

    接下来,我们还是用上面的例子来分析一下整个过程:

    export default function Profile() {  return (    <Suspense fallback={<p>Loading user...</p>}>      <User />      <Suspense fallback={<p>Loading articles...</p>}>        <Articles />      </Suspense>    </Suspense>  )}

    我们知道 React 在渲染时会构建 Fiber Tree,当处理到 User 组件时,React 代码中会捕获到异常:

    do {  try {    workLoopConcurrent()    break  } catch (thrownValue) {    handleError(root, thrownValue)  }} while (true)

    React之Suspense提出的背景及使用方法是什么

    其中,异常处理函数 handleError 主要做两件事:

    throwException(  root,  erroredWork.return,  erroredWork,  thrownValue,  workInProgressRootRenderLanes)completeUnitOfWork(erroredWork)

    其中,throwException 主要是往上找到最近的 Suspense 类型的 Fiber,并更新其 updateQueue

    const wakeables: Set&lt;Wakeable&gt; = (workInProgress.updateQueue: any)if (wakeables === null) {  const updateQueue = (new Set(): any)  updateQueue.add(wakeable) // wakeable 是 handleError(root, thrownValue) 中的 thrownValue,是一个 Promise 对象  workInProgress.updateQueue = updateQueue} else {  wakeables.add(wakeable)}

    React之Suspense提出的背景及使用方法是什么

    completeUnitOfWork(erroredWork) 在React 源码解读之首次渲染流程中已经介绍过了,此处就不再赘述了。

    render 阶段后,会形成如下所示的 Fiber 结构:

    React之Suspense提出的背景及使用方法是什么

    之后会进入 commit 阶段,将 Fiber 对应的 DOM 插入到容器之中:

    React之Suspense提出的背景及使用方法是什么

    注意到 Loading articles... 虽然也被插入了,但确是不可见的。

    前面提到过 SuspenseupdateQueue 中保存了 Promise 请求对象,我们需要在其 resolve 以后触发应用的重新渲染,这一步骤仍然是在 commit 阶段实现的:

    function commitWork(current: Fiber | null, finishedWork: Fiber): void {  ...  case SuspenseComponent: {    commitSuspenseComponent(finishedWork);    attachSuspenseRetryListeners(finishedWork);    return;  }  ...}
    function attachSuspenseRetryListeners(finishedWork: Fiber) {  // If this boundary just timed out, then it will have a set of wakeables.  // For each wakeable, attach a listener so that when it resolves, React  // attempts to re-render the boundary in the primary (pre-timeout) state.  const wakeables: Set<Wakeable> | null = (finishedWork.updateQueue: any)  if (wakeables !== null) {    finishedWork.updateQueue = null    let retryCache = finishedWork.stateNode    if (retryCache === null) {      retryCache = finishedWork.stateNode = new PossiblyWeakSet()    }    wakeables.forEach((wakeable) => {      // Memoize using the boundary fiber to prevent redundant listeners.      let retry = resolveRetryWakeable.bind(null, finishedWork, wakeable)      if (!retryCache.has(wakeable)) {        if (enableSchedulerTracing) {          if (wakeable.__reactDoNotTraceInteractions !== true) {            retry = Schedule_tracing_wrap(retry)          }        }        retryCache.add(wakeable)        // promise resolve 了以后触发 react 的重新渲染        wakeable.then(retry, retry)      }    })  }}

    到此,相信大家对“React之Suspense提出的背景及使用方法是什么”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    React之Suspense提出的背景及使用方法是什么

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档

    猜你喜欢

    React之Suspense提出的背景及使用方法是什么

    本篇内容主要讲解“React之Suspense提出的背景及使用方法是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“React之Suspense提出的背景及使用方法是什么”吧!Suspense
    2023-07-05

    React中style的使用方法及注意事项是什么

    这篇“React中style的使用方法及注意事项是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“React中style的
    2023-07-05

    Java之System.getProperty()的作用及使用方法是什么

    今天小编给大家分享一下Java之System.getProperty()的作用及使用方法是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一
    2023-07-05

    vue3自定义插件的作用场景及使用方法是什么

    本篇内容主要讲解“vue3自定义插件的作用场景及使用方法是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“vue3自定义插件的作用场景及使用方法是什么”吧!插件的作用场景在vue2的插件那篇文
    2023-07-05

    React各种状态管理器的原理及使用方法是什么

    今天就跟大家聊聊有关React各种状态管理器的原理及使用方法是什么,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。首先我们要先知道什么是状态管理器,这玩意是干啥的?当我们在多个页面中使
    2023-06-25

    jsp之scriptlet的使用方法是什么

    在JSP中,scriptlet是一种用于在JSP页面中嵌入Java代码的特殊标记。它的使用方法是在JSP页面中使用标记将Java代码包裹起来,如下所示:<%// 在这里插入Java代码int num1 = 10;int n
    jsp之scriptlet的使用方法是什么
    2024-03-08

    MySQL之mysqldump的使用方法是什么

    mysqldump是MySQL数据库备份工具,可用于将数据库中的数据和结构导出到一个文件中。其基本使用方法如下:在命令行中使用以下语法来备份数据库:mysqldump -u [用户名] -p [数据库名] > [备份文件名].sql如果需要
    MySQL之mysqldump的使用方法是什么
    2024-03-08

    C语言之素数的判断及输出方法是什么

    这篇文章主要讲解了“C语言之素数的判断及输出方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C语言之素数的判断及输出方法是什么”吧!C素数(质数)的判断及输出一:质数的判断输入一个大
    2023-07-05

    React中使用react-player播放视频或直播的方法是什么

    React中使用react-player播放视频或直播的方法是什么,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。业务中需要播放视频,寻来寻去,找到了react-
    2023-06-28

    intouch安装及使用的方法是什么

    要安装和使用InTouch,您需要按照以下步骤进行操作:1. 下载InTouch软件:您可以从Wonderware官方网站或其他可信来源下载InTouch软件的安装程序。确保下载与您的操作系统兼容的版本。2. 运行安装程序:双击下载的安装程
    2023-09-21

    ​Couchbase安装及使用的方法是什么

    Couchbase安装与使用指南下载并安装CouchbaseServer,满足先决条件(处理器、内存、磁盘空间)。启动CouchbaseServer服务。连接到Server,创建数据库(桶)。创建文档并将其存储在数据库中。通过索引管理和查询来优化数据检索。最佳实践:优化数据模型,使用索引,监控性能。利用复制功能提高数据冗余和可用性。定期备份数据以确保数据安全。参考来源:Couchbase文档Couchbase教程Couchbase社区论坛
    ​Couchbase安装及使用的方法是什么
    2024-04-11

    synaptics设置及使用的方法是什么

    要设置和使用Synaptics触摸板驱动程序,您可以按照以下步骤进行操作:1. 打开控制面板:右键单击开始菜单并选择“控制面板”选项。2. 在控制面板中,找到并打开“鼠标”或“硬件和声音”选项。3. 在“鼠标”或“硬件和声音”窗口中,找到并
    2023-09-21

    kafka安装及使用的方法是什么

    Kafka是一个分布式流处理平台,用于发布和订阅流数据,它具有高吞吐量、可扩展性和容错性等特点。下面是Kafka的安装和使用方法:安装Kafka:1. 下载Kafka二进制文件:可以从Kafka官方网站(https://kafka.apac
    2023-10-08

    ​Cassandra安装及使用的方法是什么

    Cassandra安装前提条件:Linux系统,Java8+,SSH权限步骤:创建cassandra用户下载和解压Cassandra设置环境变量配置cassandra.yaml和jvm.options启动和使用:启动Cassandra创建密钥空间和表插入和查询数据停止Cassandra监控Cassandra(nodetool)
    ​Cassandra安装及使用的方法是什么
    2024-04-13

    SVN安装及使用的方法是什么

    SVN(Subversion)是一种版本控制系统,用于管理文件和目录的更改。下面是安装和使用SVN的一般步骤:安装SVN:1. 下载SVN安装包:可以从官方网站(https://subversion.apache.org/packages.
    2023-08-15

    ​MongoDB安装及使用的方法是什么

    MongoDB安装和使用指南本文提供详细步骤来安装MongoDB,包括Linux/macOS和Windows系统的说明。还涵盖了MongoDB基本用法,包括启动、连接、创建数据库和集合,以及执行查询和更新操作。本文还提供了有关高级查询、聚合、索引和备份/恢复的信息,帮助用户充分利用MongoDB。
    ​MongoDB安装及使用的方法是什么
    2024-04-13

    mysqlcluster搭建及使用的方法是什么

    MySQL Cluster是一种高可用性、高性能的数据库集群解决方案,可以实现数据的分布式存储和处理。以下是MySQL Cluster搭建及使用的方法:1. 安装MySQL Cluster软件包:下载MySQL Cluster软件包并安装到
    2023-06-10

    zencart安装及使用的方法是什么

    以下是Zencart安装及使用的方法:1. 下载Zencart软件包并解压缩。2. 将解压缩后的文件上传到您的网站根目录或子目录中。3. 创建一个MySQL数据库并为其分配一个用户。4. 在浏览器中输入您的网站地址,将会看到Zencart的
    2023-06-03

    react-navigation6.x路由库的基本使用方法是什么

    这篇文章主要讲解了“react-navigation6.x路由库的基本使用方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“react-navigation6.x路由库的基本使用方法是
    2023-06-25

    编程热搜

    • Python 学习之路 - Python
      一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
      Python 学习之路 - Python
    • chatgpt的中文全称是什么
      chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
      chatgpt的中文全称是什么
    • C/C++中extern函数使用详解
    • C/C++可变参数的使用
      可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
      C/C++可变参数的使用
    • css样式文件该放在哪里
    • php中数组下标必须是连续的吗
    • Python 3 教程
      Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
      Python 3 教程
    • Python pip包管理
      一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
      Python pip包管理
    • ubuntu如何重新编译内核
    • 改善Java代码之慎用java动态编译

    目录