[译] 实战 React 18 中的 Suspense

1a4368cdf8c1a969944ba8f04ba420cd.jpeg

> 原文:https://dev.to/darkmavis1980/a-practical-example-of-suspense-in-react-18-3lln

React 18 带来了很多变化,它不会破坏你已经编写过的代码,并且有很多改进和一些新概念。

它也让很多开发人员,包括我,意识到我们错误地使用了useEffect hook。但话说回来,我们被其名称所误导了,因为实际上useEffect并不应该被用于副作用。

在 React 18 中,虽然仍然可以使用useEffect来完成一些事情,如使用 API 接口读取的数据填充状态,但实际上不应该将其用于此类目的。如果你在应用程序中启用StrictMode,在开发模式下,你将发现使用useEffect会被调用两次,因为现在React会mount 组件、卸载它,然后再次 mount 它,以检查代码是否运行正常。

Suspense 来了

我们应该用来取而代之的,是新的Suspense组件(虽然它已经存在于 React 17 中,但现在是推荐的方法),此组件将会按照以下方式工作:

<Suspense fallback={<p>Loading...</p>}>
  <MyComponent />
</Suspense>

上面的代码将会包裹一个组件,这个组件从某些数据源中加载数据,并在完成数据获取之前显示fallback。

Suspense 是什么

简而言之,可能和你想的不同,Suspense 并不是一个新的用于获取数据的接口,因为该工作仍然由诸如“fetch”或“axios”等库委派执行,而它实际上允许你将这些库与 React 集成,并且它的真正工作只是“在加载时显示这段代码,而在完成后显示那段代码”,仅此而已。

Suspense 如何工作

首先,你需要了解 Promise 的工作原理以及它的状态。无论使用传统方式new Promise()还是新的async/await语法来使用promise,在任何情况下,promise始终具有以下这三种状态:

  • pending -> 它仍在处理请求

  • resolved -> 请求已返回某些数据,我们获得了200 OK状态

  • rejected -> 出现了错误,获得了一个错误

Suspense使用的逻辑与ErrorBoundary完全相反,因此如果代码引发异常(因为它仍处于加载状态或者由于加载失败),则显示fallback;如果成功解析,则显示子组件。

举个例子

来看一个简单的例子,我们只需创建一个组件来获取API中的某些数据,并且希望在准备好后渲染该组件。

注意
为了简化,这里不会提到如何使用“startTransition”,添加错误边界,甚至不会涉及各种策略之间的区别,例如“fetch-on-render”、“fetch-then-render”等等...

包装 fetch 逻辑

如上所述,当我们的组件正在加载数据或失败时,需要抛出异常,但是一旦成功解决了Promise,就可以简单地返回响应。

为此,我们需要使用以下函数包装我们的请求:

// wrapPromise.js
/**
 * 将promise包装,以便可以与React Suspense一起使用
 * @param {Promise} 要处理的promise
 * @returns {Object} 与Suspense兼容的响应对象
 */
function wrapPromise(promise) {
  let status = 'pending';
  let response;

  const suspender = promise.then(
    res => {
      status = 'success';
      response = res;
    },
    err => {
      status = 'error';
      response = err;
    },
  );

  const handler = {
    pending: () => {
      throw suspender;
    },
    error: () => {
      throw response;
    },
    default: () => response,
  };

  const read = () => {
    const result = handler[status] ? handler[status]() : handler.default();
    return result;
  };

  return { read };
}

export default wrapPromise;

因此,上面的代码将检查我们Promise的状态,然后返回一个名为“read”的函数,稍后我们将在组件中调用它。

现在,我们需要使用它包装接口请求库(例子中是axios),创建一个非常简单的函数:

//fetchData.js
import axios from 'axios';
import wrapPromise from './wrapPromise';

/**
* 用wrapPromise函数包装Axios请求
* @param {string} 要获取的URL
* @returns {Promise} 包装的promise
*/
function fetchData(url) {
     const promise = axios.get(url).then(({data}) => data);

     return wrapPromise(promise);
}

export default fetchData;

这只是以接口请求库表现的一种抽象,我想强调这只是一种非常简单的实现,您可以将上面的所有代码扩展到任何需要做的工作中。在这里我使用了axios,但你可以根据自己的需要使用任何东西。

在组件中读取数据

当获取方面的所有内容都准备好后,我们来在组件中使用它。假设有一个简单的组件,只需从某个接口读取名称列表并打印。不同于习惯中在组件中通过useEffect钩子调用 fetch 的做法,这一次我们要直接在组件开始时(放在任何 hooks 之外),使用我们在包装器中导出的read方法来调用请求,因此我们的Names组件大概是这个样子的:

// names.jsx
import React from 'react';
import fetchData from '../../api/fetchData.js';

const resource = fetchData('/sample.json');
const Names = () => {
  const namesList = resource.read();

  // rest of the code
}

这里所做的是,当调用组件时,read()函数将开始抛出异常,直到完全解析完成;其后,会继续执行其余代码,在此例中也就是继续 render。所以该组件的全部代码如下:

// names.jsx
import React from 'react';
import fetchData from '../../api/fetchData.js';

const resource = fetchData('/sample.json');

const Names = () => {
  const namesList = resource.read();

  return (
    <div>
      <h2>List of names</h2>
      <ul>
        {namesList.map(item => (
          <li key={item.id}>
            {item.name}
          </li>))}
      </ul>
    </div>
  );
};

export default Names;

父组件

现在 Suspense 将要发挥作用了,首先需要在父组件中导入它:

// parent.jsx
import React, { Suspense } from 'react';
import Names from './names';

const Home = () => (
  <div>
    <Suspense fallback={<p>Loading...</p>}>
      <Names />
    </Suspense>
  </div>
);

export default Home;

到底肿么了?
我们将Suspense作为React组件导入,然后使用它来包装获取数据的组件,在这些数据被 resolve 之前,它将只会渲染“fallback”组件,因此只是<p>Loading...</p>或其他什么你需要的自定义组件。

结论

长时间使用useEffect以实现相同的结果后,当我第一次看到 Suspanse 这种用法时,我对这种新方法有些怀疑。包装获取库的整个过程有点让人生疑。但是现在,我可以看到它的好处,它非常容易处理加载状态,它抽象掉了一些代码,使其易于重用,并通过消除(好吧,至少在大多数情况下)组件本身的“useEffect”钩子简化了组件的代码,这在以前可是个让人头疼的事情。

2faf6605a298a66f6dccc774716f2b21.png59479d008c64482f47556dd3f2924ee9.png

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Web 开发会话技术之 -Cookie介绍以及源码分析和图分析以及Cookie的生命周期--路径--中文乱码的分析和代码示例

目录 Web 开发会话技术之 -Cookie 会话 基本介绍 1. 什么是会话&#xff1f; 2. 会话过程中要解决的一些问题&#xff1f; cookie 技术 cookie 介绍 二说 cookie cookie 可以用来做啥 cookie 基本使用 cookie 常用方法 cookie 底层实现机制-创建和读取 Cookie Crea…

Linux-初学者系列——篇幅7_文本编辑和处理命令

文本编辑和处理命令-目录 一、系统基本编辑命令安装vim软件工具包语法格式&#xff1a; 1、vim编辑命令模式01 普通模式02 编辑模式03 命令模式 2、编辑文件技巧01 批量删除多行指定信息02 批量增加多列指定信息03 编辑常见问题错误1&#xff1a;没有指定编辑信息错误2&#xf…

Flink高手之路5-Table API SQL

文章目录 Flink 中的Table API & SQL一、Table API & SQL 介绍1. 为什么要Table API和SQL2. Table API & SQL的特点3. Table API& SQL发展历程3.1 架构升级3.2 查询处理器的选择3.3 了解-Blink planner和Flink Planner具体区别如下&#xff1a;3.4 了解-Blink …

基于GPS/北斗卫星技术的无盲区车辆调度系统

基于GPS/北斗卫星技术的无盲区车辆调度系统 现代车辆调度系统是一种集全球卫星定位技术&#xff08;GPS&#xff09;、地理信息技术&#xff08;GIS&#xff09;和现代通信技术于一体的高科技项目。它将移动目标的动态位置&#xff08;经度与纬度&#xff09;、时间和状态等信息…

uni-app入门到实战

&#x1f37f;*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#x1f37f; &#x1f35f;欢迎来到前端初见的博文&#xff0c;本文主要讲解uni-app入门到实战&#x1f35f; &#x1f468;‍&#x1f527; 个人主页 : 前端初见 &#x1f95e;喜欢的朋友可以关注一下&#xff…

javassist 字节码处理库

目录 一、快速入门 1.1 创建class文件1.2 ClassPool的相关方法1.3 CtClass的相关方法1.4 CtMethod的相关方法1.5 调用生成的类对象 1.5.1 通过反射调用1.5.2 通过接口调用1.6 修改现有的类对象二、将类冻结三、类搜索路径四、$开头的特殊字符五、ProxyFactory的使用 我们知道J…

Linux I/O复用函数的使用情况和select接口的介绍

I/O 复用使得程序能同时监听多个文件描述符&#xff0c;这对于提高程序的性能至关重要。通常&#xff0c; 网络程序在下列情况下需要使用 I/O 复用技术&#xff1a; 1.TCP服务器同时要处理监听套接字和连接套接字 2.服务器同时要处理TCP请求和UDP请求。 3.程序同时要处理多个套…

直播预告 | 时序数据处理的云端利器:TDengine Cloud 详解与演示

当下&#xff0c;我们正处在一个万物互联的时代&#xff0c;大数据、云原生、AI、5G 等数字技术极大地方便了人们的生活&#xff0c;但智能物联网产生的海量数据却成为众多企业在数据处理上的巨大痛点。从本质来看&#xff0c;这些数据大多是产生自各种设备和传感器的时序数据&…

Spring种存取Bean的5种注解

存取Bean的五种注解 存储Bean对象两种方式1.添加一行bean2.使用注解的方式(5大注解)Controller(控制器存储)Service(服务存储)Repository(仓库存储)Component(组件存储)Configuration(配置存储)方法注解 Bean 获取Bean对象(三种)1.属性注入2.setter注入3.构造方法注入三种注入的…

springboot-分页功能

1.分页功能的作用 分页功能作为各类网站和系统不可或缺的部分&#xff08;例如百度搜索结果的分页等&#xff09; &#xff0c;当一个页面数据量大的时候分页作用就体现出来的&#xff0c;其作用有以下5个。 &#xff08;1&#xff09;减少系统资源的消耗 &#xff08;2&#…

Vue 3组件传值 、组件通信

本文采用<script setup />的写法&#xff0c;比options API更自由。那么我们就来说说以下七种组件通信方式&#xff1a; props emit v-model refs provide/inject eventBus vuex/pinia 举个例子 本文将使用下面的演示&#xff0c;如下图所示&#xff1a; 上图中…

mybatis粗心使用导致内存溢出

现象 服务响应变慢&#xff0c;线程日志也出现Java heap space内存溢出的错误&#xff0c;这个服务属于基础业务服务&#xff0c;出现问题要尽快的排查 分析 因为设置了gc日志和jmap启动相关参数 所以我们进行分析&#xff0c;这里模拟线上环境将堆大小参数调整到了128m&am…

【Linux】权限管理

文章目录 &#x1f4d6; 前言1. 什么是权限2. 权限管理2.1 Linux的用户分类&#xff1a;2.2 Liunx文件的分类&#xff1a;2.3 文件的访问权限2.4 文件访问权限的相关设置方法&#xff1a;chmod对文件权限的修改chown / chgrp 2.5 以八进制修改文件权限&#xff1a;2.6 默认权限…

Springsecurity课程笔记06-13章基于数据库的方法授权

动力节点Springsecurity视频课程 6 密码处理 6.1 为什么要加密&#xff1f; csdn 密码泄露事件 泄露事件经过&#xff1a;https://www.williamlong.info/archives/2933.html 泄露数据分析&#xff1a;https://blog.csdn.net/crazyhacking/article/details/10443849 6.2加密…

IJKPLAYER源码分析-常用API

前言 本文简要介绍IJKPLAYER的几个常用API&#xff0c;以API使用的角度&#xff0c;来审视其内部运作原理。这里以iOS端直播API调用切入。 调用流程 init 创建播放器实例后&#xff0c;会先调用init方法进行初始化&#xff1a; - (IJKFFMediaPlayer *)init {self [super ini…

计算机网络复习题+答案

文章目录 导文题目一、单项选择题二、填空题三、判断改错题,判断下列命题正误,正确的在其题干后的括号内打“√”,错误的打“”,并改正。四、名词解释五、简答题六、应用题导文 计算机网络复习题 题目 一、单项选择题 在应用层协议中,主要用于IP地址自动配置的协议是: (…

文案自动修改软件-文案自动改写的免费软件下载

文章生成器ai写作机器人 随着人工智能技术的飞速发展&#xff0c;越来越多的新型产品被推向市场。其中&#xff0c;文章生成器AI写作机器人是一个备受关注的新兴行业。它使用机器学习和自然语言处理等技术&#xff0c;为用户自动生成高质量的文章和内容&#xff0c;帮助用户在…

Python——第2章 数据类型、运算符与内置函数

目录 1 赋值语句 2 数据类型 2.1 常用内置数据类型 2.1.1 整数、实数、复数 2.1.2 列表、元组、字典、集合 2.1.3 字符串 2.2 运算符与表达式 2.2.1 算术运算符 2.2.2 关系运算符 2.2.3 成员测试运算符 2.2.4 集合运算符 2.2.5 逻辑运算符 2.3 常用内置…

本地搭建属于自己的ChatGPT:基于PyTorch+ChatGLM-6b+Streamlit+QDrant+DuckDuckGo

本地部署chatglm及缓解时效性问题的思路&#xff1a; 模型使用chatglm-6b 4bit&#xff0c;推理使用hugging face&#xff0c;前端应用使用streamlit或者gradio。 微调对显存要求较高&#xff0c;还没试验。可以结合LoRA进行微调。 缓解时效性问题&#xff1a;通过本地数据库…

Mybatis高级映射及延迟加载

准备数据库表&#xff1a;一个班级对应多个学生。班级表&#xff1a;t_clazz&#xff1b;学生表&#xff1a;t_student 创建pojo&#xff1a;Student、Clazz // Student public class Student {private Integer sid;private String sname;//...... }// Clazz public class Cla…