React中如何获取远程数据?

目录
文章目录隐藏
  1. React 服务器组件用于数据获取
  2. React Query 用于数据获取
  3. 服务器组件+React Query
  4. React 的 use() API
  5. 数据获取钩子
  6. TRPC:用于类型化数据获取的库
  7. 总结

在 React 中如何获取数据?

在 React 中,从远程 API 获取数据有多种方式。本博客,将带大家探讨多年来出现且在现在仍被使用的所有 React 数据获取选项。虽然其中一些是较新的且被推荐使用的方式,但其他一些则不太被推荐,而且在大多数情况下应避免使用。

接下来我将从一个简单的组件示例开始,在这个示例中,我们从一个远程 API 获取帖子列表,并将其显示为一系列项目。

首先,我们定义一个代表帖子的 Post 类型:

export type Post = {
  id: string;
  title: string;
};

其次,定义一个包含一些帖子的虚拟数据库,这些帖子存储在内存中:

import { Post } from "./types";

export const POSTS: Post[] = [
  {
    id: "1",
    title: "Post 1",
  },
  {
    id: "2",
    title: "Post 2",
  },
];

然后,通过 getPosts 函数获取数据,它返回一个包含帖子的 Promise。根据接下来讨论的情况,这个函数的实现细节可能会有所不同。稍后再详细讨论:

import { POSTS } from "../db";

export const getPosts = async () => {
  // artificial delay
  await new Promise((resolve) => setTimeout(resolve, 2000));

  return POSTS;
};

最后,我们通过一个组件,用于获取帖子:

import { Post } from "@/features/post/types";

const PostsPage = () => {
  const posts: Post[] = [];
  // TODO: fetch posts from the API

  return (
    <div>
      <h1>Posts</h1>

      <ul>
        {posts.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default PostsPage;

从这里开始,我们将探索在 React 中获取数据的不同方法。

React 服务器组件用于数据获取

如果你在使用基于 React 的框架(如 Next.js),该框架实现了 React 服务器组件(RSC),那么你可以执行服务器端数据获取,因为服务器组件在将 HTML 返回给客户端之前会在服务器上执行:

import { getPosts } from "@/features/post/queries/get-posts";

const PostsPage = async () => {
  const posts = await getPosts();

  return (
    <div>
      <h1>React Server Component</h1>

      <ul>
        {posts?.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default PostsPage;

异步组件会暂停执行,直到异步操作完成。一旦等待的 Promise 被解决,组件将继续使用获取到的数据进行渲染。在 RSC(React 服务器组件)的情况下,只有 HTML 会被返回给客户端。

由于服务器组件的特性,数据获取是在服务器端完成的,这意味着 getPosts 可以直接从数据库中读取数据,而无需通过 API。你只需要使用你的 ORM(对象关系映射)或数据库客户端来检索数据:

export const getPosts = async () => {
  return await db.query("SELECT * FROM posts");
};

如果你使用的是支持 React 服务器组件的框架(例如 Next.js),我建议你在服务器上获取数据,因为这样可以避免客户端和服务器之间的往返通信,并且你可以直接访问服务器端的数据源。

从这里开始,你可以通过向组件添加错误处理或加载状态来增强用户体验。后者可以通过使用 React 的 Suspense 组件来实现。

React Query 用于数据获取

当涉及到客户端渲染(CSR)的 React 应用程序(即单页应用程序 SPA)时,最推荐的数据获取方式是使用像 React Query 这样的库。它是一个功能强大的库,为你的 React 应用程序提供了用于获取、缓存和更新数据的钩子:

"use client";

import { getPosts } from "@/features/post/queries/get-posts";
import { useQuery } from "@tanstack/react-query";

const PostsPage = () => {
  const { data: posts } = useQuery({
    queryKey: ["posts"],
    queryFn: getPosts,
  });

  return (
    <div>
      <h1>React Query</h1>

      <ul>
        {posts?.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default PostsPage;

在这个示例中,我们使用 React Query 的 useQuery 钩子来通过客户端数据获取方式获取帖子。useQuery 钩子接受一个包含 queryKey 和 queryFn 的对象。queryKey 是一个数组,用于标识查询(即用于缓存管理),而 queryFn 是获取数据的函数。

在客户端数据获取的情况下,getPosts 函数无法访问后端代码(例如 ORM、数据库),因此需要通过 HTTP(例如 REST)与远程 API 进行通信。这通常是通过原生的 fetch API 或像 axios 这样的库来完成的。你还需要决定是使用async/await还是 Promise API(即 then)。虽然所有组合都是可能的,但使用 fetch 的 async/await 是最常用的:

export const getPosts = async () => {
  const response = await fetch("/api/posts");
  return response.json();
};

与使用服务器组件进行服务器端数据获取不同,从 fetch 或 axios 获得的结果不会自动进行类型定义。你需要通过类似 OpenAPI 的工具来引入类型化模式生成。

此外,由于客户端数据获取的特性,你必须自己处理网络错误、加载状态和缓存。好在,React Query 在这些方面都能提供帮助,所以你无需重复造轮子。

每当你需要执行客户端数据获取时,React Query 都是首选。例如,它默认处理缓存、竞态条件和陈旧数据。如果你希望向组件添加错误处理或加载状态,可以从 useQuery 钩子的结果中解构出 isLoading 和 error 属性。

React Query 的替代方案是 SWR。如果你使用 GraphQL 作为 API 层而不是 REST,还有 Relay 和 Apollo Client 可供选择,尽管 React Query 也可以与 GraphQL 一起使用。

服务器组件+React Query

你已经了解了使用 React 服务器组件(内置)进行服务器端数据获取和使用 React Query(库)进行客户端数据获取的方法。但如果你想将它们结合起来使用呢?

例如,你可能希望在服务器端使用 React 服务器组件(如果你的 React 框架支持)获取初始数据,然后使用 React Query 在客户端继续进行数据获取(例如实现无限滚动)。

对于这个高级数据获取示例,你需要一个服务器组件,它在服务器端获取初始数据,然后将其传递给一个客户端组件,该客户端组件使用 React Query 在客户端继续进行数据获取:

import { getPosts } from "@/features/post/queries/get-posts";
import { PostList } from "./_components/post-list";

const PostsPage = async () => {
  const posts = await getPosts();

  return (
    <div>
      <h1>React Server Component + React Query</h1>

      <PostList initialPosts={posts} />
    </div>
  );
};

export default PostsPage;

基本上,服务器组件的实现细节与我们之前示例中的相同,但是,我们不是直接渲染帖子列表,而是将它们传递给一个客户端组件,该组件将其作为初始数据进行进一步处理:

"use client";

import { getPosts } from "@/features/post/queries/get-posts";
import { Post } from "@/features/post/types";
import { useQuery } from "@tanstack/react-query";

type PostListProps = {
  initialPosts: Post[];
};

const PostList = ({ initialPosts }: PostListProps) => {
  const { data: posts } = useQuery({
    queryKey: ["posts"],
    queryFn: getPosts,
    initialData: initialPosts,
  });

  return (
    <ul>
      {posts?.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
};

export { PostList };

客户端组件使用来自服务器组件的 props 作为 useQuery 钩子中的 initialData。从此处开始,React Query 接管数据的缓存、重新获取和更新。这样,你就可以将两者的优点结合起来:使用 React 服务器组件进行服务器端数据获取,以及使用 React Query 进行客户端数据获取。

getPosts 函数的实现细节存在争议:当在服务器组件中执行该函数时(请参阅用于数据获取的 React 服务器组件),你可以直接访问数据源(即服务器)。但是,当在客户端组件中执行该函数时(请参阅用于数据获取的 React Query),你必须使用远程 API。

在这里你需要实现两个 getPosts 函数。这里有一个小小的解决方法(即用于数据获取的服务器操作),你可以使用相同的 getPosts 函数来服务于服务器组件和客户端组件。

React 的 use() API

React 的 use API 仍处于实验阶段。它允许你从服务器组件传递一个 Promise 到客户端组件,并在客户端组件中解析它。这样,你就可以避免使用 await 来阻塞服务器组件的渲染:

import { Suspense } from "react";
import { getPosts } from "@/features/post/queries/get-posts";
import { PostList } from "./_components/post-list";

const PostsPage = () => {
  const postsPromise = getPosts();

  return (
    <div>
      <h1>use(Promise)</h1>

      <Suspense>
        <PostList promisedPosts={postsPromise} />
      </Suspense>
    </div>
  );
};

export default PostsPage;

这种方法与服务器组件示例相似,但使用 React 的 use API 将 promise 传递给客户端组件,而不是直接在 RSC(React 服务器组件)中解析它。

"use client";

import { use } from "react";
import { Post } from "@/features/post/types";

type PostListProps = {
  promisedPosts: Promise<Post[]>;
};

const PostList = ({ promisedPosts }: PostListProps) => {
  const posts = use(promisedPosts);

  return (
    <ul>
      {posts?.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
};

export { PostList };

就我个人而言,感觉就像是朝着尚未实现的异步客户端组件迈出的一步。在写本博客时,只有服务器组件可以在其函数签名中使用 async 关键字。一旦有了异步客户端组件,我们就可以省略 use API,直接在客户端组件中等待 getPosts 的结果。

数据获取钩子

除了使用像 React Query 这样的专用客户端数据获取库之外,还可以使用钩子来实现自己的数据获取逻辑。虽然这种方法不推荐用于生产环境,但它是学习 React 中数据获取基础知识的好方法:

"use client";

import { getPosts } from "@/features/post/queries/get-posts";
import { Post } from "@/features/post/types";
import { useEffect, useState } from "react";

const PostsPage = () => {
  const [posts, setPosts] = useState<Post[]>([]);

  useEffect(() => {
    const fetchPosts = async () => {
      const posts = await getPosts();
      setPosts(posts);
    };

    fetchPosts();
  }, []);

  return (
    <div>
      <h1>Hooks</h1>

      <ul>
        {posts?.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default PostsPage;

在 React Query 这样的数据获取库出现之前,开发者会使用 useEffect 和 useState 来在 React 中获取数据。但那种实现方式只是冰山一角,因为你需要自己处理从加载状态到缓存以及竞态条件的一切事务。

但无论如何,React 初学者都会在《React 进阶之路》中了解到这种方法,因为它是一个很好的途径,可以帮助你理解在像 React Query 这样复杂的库中,数据获取的内部工作机制是如何运作的。

TRPC:用于类型化数据获取的库

通常,数据获取是在客户端-服务器架构中通过 REST 进行的。在客户端,如前所述,这可以通过 React Query 来实现。但这种解决方案在网络中缺乏类型安全性,因为你需要使用像 OpenAPI 这样的第三方来生成类型化架构。

这时,像 tRPC 这样的远程过程调用(RPC)库就派上了用场。它是一个为你的 React 应用程序提供类型安全 API 层的库。以下是如何使用 tRPC 获取帖子的方法:

"use client";

import { trpc } from '~/trpc/client';

const PostsPage = () => {
  const posts = trpc.posts.getPosts.useQuery();

  return (
    <div>
      <h1>tRPC</h1>

      <ul>
        {posts?.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default PostsPage;

tRPC 的优势在于,从数据获取函数到数据本身,一切都是类型化的。这样,你就可以避免运行时错误,并获得更好的开发体验。但记住,tRPC 是一个全栈解决方案,因此你需要一个带有 TypeScript 后端的 Node.js 才能使用它。

总结

写到这里大家可能会问,在 React 中推荐的数据获取方式是什么呢?这取决于你的技术栈。如果你正在使用支持 React 服务器组件的框架,我强烈建议你在服务器端使用 RSC 来获取数据。如果你正在构建客户端渲染的 React 应用程序,则应该使用 React Query 来获取数据。

如果你在单页应用程序(SPA)中采用客户端数据获取的方法,那么现在 React Query 是首选。即使在高级服务器端 React 应用程序中,也无法绕过它,因为它为你处理了许多事情(如竞态条件、缓存、重新获取、无限滚动等)。

如果你已经启用了 RSC,并且想支持更高级的数据获取模式,如无限滚动,你可以将 React 服务器组件与 React Query 结合起来。这样,你就可以在服务器端获取初始数据,然后在客户端继续使用 React Query 进行持续的数据获取。

像 React 的 use API 这样的接口目前仍处于实验阶段,不建议用于生产环境(至少目前还没有)。在我看来,它们可能更像是朝着异步客户端组件迈进的一块垫脚石,而这些组件在本文还仅处于讨论阶段。

作为初学者,如果你只是想了解数据获取以及像 React Query 这样复杂的库内部的工作原理,你可以使用钩子(useEffect + useState)来实现自己的数据获取逻辑。

但在实际的应用程序中,你应该使用像 React Query 这样的库来进行客户端数据获取,或者使用 React 服务器组件来进行服务器端数据获取。

如果你不能使用 React 服务器组件,但又想实现类型安全的数据获取,你可以使用 tRPC。它是一个为你的 React 应用程序提供类型安全 API 层的库。但这只在你拥有带有 TypeScript 后端的 Node.js 时才有效,因为 tRPC 是一个全栈解决方案。

「点点赞赏,手留余香」

1

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系maynote@foxmail.com处理
码云笔记 » React中如何获取远程数据?

发表回复