前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[译] 实战 React 18 中的 Suspense

[译] 实战 React 18 中的 Suspense

作者头像
江米小枣
发布2023-12-06 16:24:01
2190
发布2023-12-06 16:24:01
举报
文章被收录于专栏:云前端云前端

> 原文: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 中,但现在是推荐的方法),此组件将会按照以下方式工作:

代码语言:javascript
复制
<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,就可以简单地返回响应。

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

代码语言:javascript
复制
// 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),创建一个非常简单的函数:

代码语言:javascript
复制
//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组件大概是这个样子的:

代码语言:javascript
复制
// 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。所以该组件的全部代码如下:

代码语言:javascript
复制
// 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 将要发挥作用了,首先需要在父组件中导入它:

代码语言:javascript
复制
// 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”钩子简化了组件的代码,这在以前可是个让人头疼的事情。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-04-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云前端 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Suspense 来了
  • Suspense 是什么
  • Suspense 如何工作
  • 举个例子
    • 包装 fetch 逻辑
      • 在组件中读取数据
        • 父组件
        • 结论
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档