React 的 Suspense 和 ErrorBoundary 这关系也能有?

Suspense 组件想必大家都用过,一般是和 React.lazy 结合用,用来加载一些异步组件。

比如这样一个组件:

// src/Aaa.jsx
export default function Aaa() {return <div>aaa</div>
}

就可以在另一个组件里用 lazy + Suspense 异步加载:

import React, { Suspense } from 'react';const LazyAaa = React.lazy(() => import('./Aaa'));export default function App() {return <div><Suspense fallback={'loading...'}><LazyAaa></LazyAaa></Suspense></div>
}

这里的 import 是 webpack 提供的用来异步加载模块的 api,它会动态下载模块所在的 chunk,然后从中解析出该模块,拿到 export 的值:

后台管理系统用这个挺多的,因为不可能一下子把所有路由的组件都下载下来,所以会用 lazy + Suspense 的方式异步加载一些组件。

大多数场景下,Suspense 就专门和 lazy 搭配使用的。

但有的时候,你会发现 Suspense 不搭配 lazy 也可以。

比如 jotai 这个状态管理库,它就号称支持了 Suspense,可以这样写:

 
import { Suspense } from 'react'
import { atom, useAtom } from 'jotai'const userAtom = atom(async (get) => {const userId = 1;const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}?_delay=2000`)return response.json()
})const UserName = () => {const [user] = useAtom(userAtom)return <div>User name: {user.name}</div>
}export default function App() {return <Suspense fallback="Loading..."><UserName /></Suspense>
}

 

现在并不是用 lazy 异步加载组件呀,怎么触发的 Suspense 呢?

回答这个问题会涉及到 ErrorBoundary。

现在 react 官网都推荐用 function 而不是 class 了:

绝大多数情况我们用 function 组件就好了,没必要用 class 组件。

但是有一个特性是只有 class 组件才有的,就是 ErrorBoundary。

这样写:

import { Component } from "react";class ErrorBoundary extends Component {constructor(props) {super(props);this.state = {hasError: false};}static getDerivedStateFromError(error) {return { hasError: true, message: error.message };}componentDidCatch(error, errorInfo) {console.log(error, errorInfo);}render() {if (this.state.hasError) {return <div>出错了: {this.state.message}</div>;}return this.props.children;}
}

 

当子组件报错的时候,会把错误传递给它的 getDerivedStateFromError 和 componentDidCatch 方法。

getDerivedStateFromError 接收 error,返回一个新的 state,会触发重新渲染来显示错误对应的 UI。

componentDidCatch 接收 error 和堆栈 info,可以用来打印错误日志。

我们试一下:

function Bbb() {const b = window.a.b;return <div>{b}</div>
}export default function App() {return <ErrorBoundary><Bbb></Bbb></ErrorBoundary>
}

window.a.b 不存在,所以正常情况下会报错,页面白屏。

但现在加上 ErrorBoundary 是这样的:

getDerivedStateFromError 修改 state 触发重新渲染,渲染出错误对应的 UI。

componentDidCatch 拿到错误信息,打印日志。

这样,就对组件抛错的情况做了兜底。

这个特性只有 class 组件有,function 组件没有:

不过一般也不用自己写这种 ErrorBoundary 组件,直接用 react-error-boundary 这个包就行:

npm install --save react-error-boundary

试一下:

import { ErrorBoundary } from "react-error-boundary";function Bbb() {const b = window.a.b;return <div>{b}</div>
}function fallbackRender({ error }) {return (<div><p>出错了:</p><div>{error.message}</div></div>);
}export default function App() {return <ErrorBoundary fallback={fallbackRender}><Bbb></Bbb></ErrorBoundary>
}

而且并不一定是 ErrorBoundary 的 children,任意层级的子组件都可以:

也就是说组件抛错的时候,会向上寻找最近的 ErrorBoundary 组件。

这也是 boundary 的含义。

话说回来,为什么讲 Suspense 要扯到 ErrorBoundary 呢?

这俩有啥关系?

其实 Suspense 也是用 throw error 的方式实现的。

比如这样:

import { Suspense } from "react";let data, promise;
function fetchData() {if (data) return data;promise = new Promise(resolve => {setTimeout(() => {data = '取到的数据'resolve()}, 2000)})throw promise;
}function Content() {const data = fetchData();return <p>{data}</p>
}export default function App() {return (<Suspense fallback={'loading data'}><Content /></Suspense>)
}

可以看到,触发了 Suspense:

也就是说,只要 throw 一个 promise,就会被最近的 Suspense 捕获。

promise 初始状态展示 fallback,promise 改变状态后展示子组件。

那 React.lazy 是不是也是基于这个实现的呢?

调试下源码,发现确实是这样:

React.lazy 包裹之后,也会 throw 一个 promise 来触发 Suspense。

当 promise 改变状态后,再返回拿到的值。

这样为什么 jotai 可以支持 Suspense 我们也就知道了:

也是这样实现的:

有的同学可能会问了:ErrorBoundary 是捕获组件 throw 的错误,而 Suspense 是捕获组件 throw 的 promise,这俩会冲突么?

试一下就知道了:

包裹一层 ErrorBoundary,你会发现没有触发:

而 throw 一个 error 的时候:

ErrorBoundary 就触发了:

也就是说,ErrorBoundary 和 Suspense 虽然都是捕获组件 throw 出的东西,但这俩互不相干,一个捕获 error,一个捕获 promise。

大概看下源码的处理:

首先会全部 catch,然后内部再区分两种情况:

如果 throw 的是 error,就是 error boundary 的处理逻辑,找最近的一个 ErrorBoundary 组件来处理。

如果 throw 的是 promise,则是 suspense boundary 的处理逻辑,找最近的 Suspense 组件来处理。

两者互不相干。

但业务代码我们不用 Suspense 来写这种 loading。

大家都这么写:

import { useEffect, useState } from "react";function fetchData() {return new Promise(resolve => {setTimeout(() => {resolve({name: 'guang'});}, 2000)})
}export default function App() {const [loading, setLoading] = useState(false);const [data, setData] = useState({});async function load() {setLoading(true);const data = await fetchData();setData(data);setLoading(false);}useEffect(() => {load();}, []);return <div>{ loading ? 'loding...' : data.name }</div>
}

 

就是加一个 state 来记录 loading 状态就行了。

要是用 Suspense,需要 throw 一个 promise 才行,那可太费劲了,而且代码也不好维护。

不过如果你用了一些支持 Suspense 的框架,比如 jotai、next.js 等,那也可以用 Suspense。

框架内部给你做了 throw promise 的事情:

import { Suspense } from 'react'
import { atom, useAtom } from 'jotai'const userAtom = atom(async (get) => {const userId = 1;const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}?_delay=2000`)return response.json()
})const UserName = () => {const [user] = useAtom(userAtom)return <div>User name: {user.name}</div>
}export default function App() {return <Suspense fallback="Loading..."><UserName /></Suspense>
}

本来 Suspense 就是用来做这个的,结果现在只有 lazy 加载异步组件的时候才能用。

react 团队也在想办法解决这个问题,所以出了一个 use 的 hook:

这样用:

它的参数是 promise。

当 promise 在 pending 的时候,展示 suspense 的 fallback。

当 promise 是 resolve 的时候,展示 Suspense 的子组件。

当 promise 是 reject 的时候,展示 ErrorBoundary 的 fallback。

这样就不用自己 throw promise 了,业务代码就可以用 Suspense 来 loading 了。

不过别高兴太早,这个 use 的 hook 还在实验阶段,还没正式发布。

我们刚才用的 jotai 就自己实现了一下 use:

就是 pending 的时候 throw promise,reject 的时候 throw error,否则 return 数据。

等 use 这个 hook 正式发布了,大家就可以告别自己写个 state 标识 loading 状态这种方式了,直接用 Suspense。

这就是文档里写的触发 Suspense 的 3 种方式:

一种是用支持 Suspense 的框架,比如 next.js 或者 jotai。

一种是 lazy 异步加载组件。

再一种就是还在实验阶段的 use 了。

这些不同的方式底层都是 throw promise。

总结

大多数人用 Suspense 都是结合 React.lazy 异步加载组件的时候用,其实它也可以独立用。

它的底层原理就是 throw 一个 promise,然后 React 会捕获这个 promise,交给最近的 Suspense 组件来处理。

类似的,ErrorBoundary 也是这种处理方式,只不过捕获的是 throw 的 error。

ErrorBoundary 只能是 class 组件的形式,通过 getDerivedStateFromError 方法来接收错误修改 state,以及 componentDidCatch 来打印错误日志。

自己写 throw promise 来触发 Suspense 还是很麻烦的,一般我们都不用这个,而是自己写个 loading 的 state 来标识。

不过当你用 next.js、jotai 等框架的时候,因为内部做了 throw promise 的封装,就可以直接用 Suspense 了。

此外,react 有一个 use 的 hook,可以接收 promise,在 pending 的时候触发 Suspense,在 reject 的时候触发 ErrorBoundary,底层原理就是 throw error 和 promise。

这个 hook 还在实验阶段,等正式发布之后,估计代码里就会有大量 Suspense 了。

就像标题说的,Suspense 和 ErrorBoundary 看似是两种不同的东西,但其实不管是用法还是实现原理,都是很类似的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/241393.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

测试开发体系介绍——测试体系介绍-L3

目录&#xff1a; 测试框架体系TDDDDTBDDATDD介绍 测试框架是什么&#xff1f;测试框架的价值&#xff1a;测试框架的收益&#xff1a;常见测试框架类型&#xff1a;TDDBDDBehaviorDrivenDevelopmentATDDAcceptanceTestDrivenDevelopmentMBTModelBasedTestingDDTDataDrivenTes…

案例147:基于微信小程序的酒店管理系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

MFC 视图窗口

目录 视图窗口概述 视图窗口的使用 视图窗口创建流程 命令消息 WM_COMMAND 处理顺序 对象关系 视图窗口概述 作用&#xff1a;提供了一个用于显示数据的窗口 关于视图窗口 视图类是用来展示用户&#xff0c;文档类是用来存储和管理数据视图窗口是覆盖掉框架窗口的客户区…

详解Vue3中的插槽(slot)

本文主要介绍Vue3中的插槽&#xff08;slot&#xff09;。 目录 一、在普通写法中使用插槽&#xff08;slot&#xff09;作用域插槽默认插槽 二、在setup写法中使用插槽&#xff1a;注意事项 在Vue3中&#xff0c;插槽&#xff08;slot&#xff09;是一种用于在父组件中向子组件…

Unity矩阵平移旋转缩放Matrix4x4

Unity矩阵平移旋转缩放Matrix4x4 Unity中的矩阵&#xff08;Matrix4x4&#xff09;创建自定义模型平移矩阵缩放矩阵旋转矩阵 Unity中的矩阵&#xff08;Matrix4x4&#xff09; 最近在研究帧同步定点数物理系统中需要自定义定点数矩阵&#xff0c;所以在这里分享下基础的矩阵案…

蓝牙物联网在智能家居中的应用前景

物联网智能家居系统是应用物联网技术&#xff0c;在传统家居环境下将各种零散无序的电器整合成统一整体&#xff0c;实现家电的全程自动控制&#xff0c;满足用户高效管理需求的一种新型家居模式。 其主要的子系统有家居感知系统、家庭网络系统、智能家居控制管理系统等&#x…

使用教程之【SkyWant.[2304]】路由器操作系统,破解移动【Netkeeper】校园网【小白篇】

许多高校目前饱受Netkeeper认证的痛苦&#xff0c;普通路由器无法使用&#xff0c; 教你利用SkyWant的Netkeeper认证软件来使你的SkyWant路由器顺利认证上网&#xff0c;全宿舍又可以合作共赢了&#xff01; 步骤一&#xff1a;正确连接网线&#xff0c;插电开机 正确连接网…

分布式Session使用步骤

目录 1. 为什么用分布式Session2. Spring-Session使用步骤2-1. 添加依赖2-2. yml配置 1. 为什么用分布式Session 将一个项目部署到多台服务器上时&#xff0c;多台服务器的Tomcat的Session不共享。那么就有可能造成Session数据不一致情况&#xff0c;所以此时就需要使用分布式…

Gateway API

Gateway API 目录 原文链接 https://onedayxyy.cn/docs/GatewayAPI 本节实战 实战名称&#x1f6a9; 实战&#xff1a;Gateway API在istio里的安装及测试-2023.12.23(测试失败) 前言 Gateway API 是由 SIG-NETWORK 社区管理的开源项目&#xff0c;项目地址&#xff1a;http…

nodejs+vue+ElementUi洗衣店订单管理系统4691l

衣服但是找订单的时间太长&#xff0c;体验非常的差。而且对于店家这也很头疼&#xff0c;麻烦的查找订单的方式&#xff0c;让他总是重复着繁琐的步骤&#xff0c;记录的时候也很容易出问题&#xff0c;容易把衣服弄错&#xff0c;再然后就是对于收来的衣服也很麻烦&#xff0…

初识QT(上篇):What Qt

初识QT&#xff08;上篇&#xff09;&#xff1a;What Qt 前言 & 说明前言说明 初识QT1.1 QT的what1. 介绍2. 发展历程3. QT架构的主要内容4.QT的常用模块 1.2 QT的 why1. QT的核心机制 下篇笔记链接 前言 & 说明 前言 前言&#xff1a; 之前说要share的qt相关知识&am…

【AI提示词人物篇】创新艺术未来,让科技改变想象空间

AI 绘画学习难度和练习技巧 学习绘画的技巧 学习能难度&#xff1a; 外貌特征&#xff1a;AI需要学习识别和理解各种外貌特征&#xff0c;如发型、肤色、眼睛颜色等。这可能需要大量的训练数据和复杂的模型架构。 镜头提示&#xff1a;AI需要学习理解不同镜头提示的含义&…

论文笔记--Learning Political Polarization on Social Media Using Neural Networks

论文笔记--Learning Political Polarization on Social Media Using Neural Networks 1. 文章简介2. 文章概括3. 相关工作4. 文章重点技术4.1 Collection of posts4.1.1 数据下载4.1.2 数据预处理4.1.3 统计显著性分析 4.2 Classification of Posts4.3 Polarization of users 5…

SpringMVC:Ajax、拦截器、文件上传、文件下载

文章目录 SpringMVC - 06一、Ajax1. 概述2. Ajax 异步加载数据1. 单个数据2. 对象 3. 实践4. 总结 二、拦截器1. 概述2. 实现3. 实践4. 总结 三、文件上传&#xff1a;Upload1. 准备工作2. 步骤3. 效果 四、文件下载&#xff1a;Download1. 步骤2. 效果3. 总结 注意&#xff1a…

mac m1芯片 pytorch安装及gpu性能测试

pytorch 使用mac的m1芯片进行模型训练。 #小结&#xff1a;在数据量小和模型参数少&#xff0c;batch_size小时&#xff0c;cpu训练更快&#xff08;原因&#xff1a;每次训练时数据需要放入GPU中&#xff0c;由于batch_size小。数据放入gpu比模型计算时间还长&#xff09; 在…

【SPI和API有什么区别】

✅什么是SPI&#xff0c;和API有什么区别 ✅典型解析&#x1f7e2;拓展知识仓&#x1f7e2;如何定义一个SPI&#x1f7e2;SPI的实现原理 ✅SPI的应用场景SpringDubbo ✅典型解析 Java 中区分 API和 SPI&#xff0c;通俗的进: API和 SPI 都是相对的概念&#xff0c;他们的差别只…

优化模型:MATLAB整数规划

一、整数规划介绍 1.1 整数规划的定义 若规划模型的所有决策变量只能取整数时&#xff0c;称为整数规划。若在线性规划模型中&#xff0c;变量限制为整数&#xff0c;则称为整数线性规划。 1.2 整数规划的分类 整数规划模型大致可分为两类&#xff1a; &#xff08;1&…

SQL进阶理论篇(二十):什么是SQL注入

文章目录 简介SQL注入的原理SQL注入的实例搭建sqli-labs注入环境实例一&#xff1a;猜测where条件判断查询语句的字段数获取当前数据库和用户信息获取MySQL中的所有数据库名称查询wucai数据库中的所有数据表查询heros数据表中的所有字段参考文献 简介 这节是纯兴趣篇了。 web…

less 查看文本时,提示may be a binary file.See it anyway?

解决办法 首先使用echo $LESSCHARSET查看less的编码 看情况设置less的编码格式(我的服务器上使用utf-8查看中文) 还要特别注意一下&#xff0c;Linux中存在的文本文件的编码一定要是utf - 8;&#xff08;这一步很关键&#xff09; 例如&#xff1a;要保证windows上传到Linux的…

Linux Shell 001-Bash简介

Linux Shell 001-Bash简介 本节关键字&#xff1a;Linux、Bash Shell、shell分类 相关指令&#xff1a;bash、sh、cat Shell的介绍 计算机只能认识&#xff08;识别&#xff09;机器语言(0和1)&#xff0c;如&#xff08;11000000 这种&#xff09;。但是&#xff0c;我们的…