获取异步数据

在 React 18 及以前版本中,异步获取数据的操作一般都是通过 Axios 或者 fetch 功能来完成的。但是在 Canary 和 Experimental 发布渠道中,React 新提供了一个use函数来从 Promise 和 Context 中获取内容。

使用 fetch 搭配自定义 Hook 完成异步

fetch 和 Axios 都是用来完成对服务端进行数据异步访问的,它们之间仅有一些使用上的不同。由于 React 里很多 Hook 都要求同步操作,尤其是useEffect中,所以在使用 fetch 或者 Axios 访问服务端数据时,一般都需要自定义一个 Hook 来简化组件中对于异步数据的访问。

以下是一个基于 fetch 使用 GET 方式访问 RESTful API 服务的自定义 Hook 示例。

import { useEffect, useState } from "react";

export function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error("Network response was not ok");
        }
        const result = await response.json();
        setData(result);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

在这个示例中,需要注意的是,useEffect Hook 中只能使用同步函数,所以即便是使用async/await定义了异步函数,也需要使用其同步调用方式。

使用use获取异步数据 🧪

Tip

本章节的内容在当前React 18版本中是不可用的,如果需要尝试本章节介绍的内容需要使用Canary或者Experimental发布渠道。预计在未来React 19版本中,本章的内容将被发布至正式版。

use不是一个 Hook,而是一个普通的函数,所以她可以在循环语句和条件判断语句中调用,但是调用use的函数依旧需要是一个组件或者 Hook。

使用use读取 Context 中的内容时十分简单,基本上与useContext一样。参见以下示例。

import { use } from "react";

function ThemedPage() {
  const theme = use(ThemeContext);
  // ...
}

但是当use与 Promise 结合使用的时候,效果就不一样了。use可以在调用异步过程的时候暂停组建的渲染过程,这也就是说use可以与<Suspense>组件一起搭配使用。当调用use的组件被<Suspense>包裹的时候,组件被use挂起时,将展示fallback指定的内容,直到use参数中的 Promise 被解决。如果use中的 Promise 出现错误,那么最近的错误边界中的后备 UI 将被展示。

以下示例展示的是利用 Promise 从服务端获取数据的用法。

import { Suspense, use } from "react";

function Message({ messagePromise }) {
  const messageContent = use(messagePromise);

  return <div>Message: {messageContent}</div>;
}

function App() {
  const messagePromise = async () => {
    const response = await fetch("/message");
    return response.text();
  };

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Message messagePromise={messagePromise} />
    </Suspense>
  );
}

现在对比一下上面传统使用自定义 Hook 实现的异步操作,是不是已经简单了很多。

在有async/await加持的条件下,Promise 被 reject 时,往往可以使用try/catch来处理,但是在使用use捕获 Promise 返回的值时,是不可以使用try/catch的。作为替代方案,需要使用错误便边界。

错误边界的定义不在 React 中提供,函数式组件中目前也没有提供处理错误的错误边界功能。所以错误边界功能目前还是由传统的使用类编写组件的方式实现的,一般情况下,可以使用react-error-boundary库来支持错误边界,其中提供的<ErrorBoundary>组件的使用与 React 中提供的<Suspense>组件的使用基本上是一致的。