React 用 Suspense 与 use() 做组件级数据请求:把异步资源写在组件里

test12026-10-200 次阅读

为什么把数据请求写进组件

过去的习惯是把请求放在父组件的 useEffect 里,再用 props 往下传。问题是加载状态散落在各处,子组件想单独取数就得一层层 prop drilling。React 19 的 use() 配合 Suspense,让组件自己声明"我需要这个资源",由框架在渲染时挂起、在最近一层 Suspense 显示占位。

用 use() 读取 Promise 资源

use() 可以在渲染期间直接读取一个 Promise,挂起时把它交给外层 Suspense。关键是 Promise 必须稳定(同一参数复用同一个实例),否则每次渲染都重新请求。

import { use, Suspense } from "react";

const cache = new Map();

function fetchUser(id) {
  let p = cache.get(id);
  if (!p) {
    p = fetch("/api/users/" + id).then((r) => r.json());
    cache.set(id, p);
  }
  return p;
}

function UserCard({ id }) {
  const user = use(fetchUser(id)); // 渲染里直接读取 Promise
  return <h2>{user.name}</h2>;
}

function Page() {
  return (
    <Suspense fallback={<p>加载中…</p>}>
      <UserCard id="42" />
    </Suspense>
  );
}

错误边界兜底

Suspense 只管"加载中",请求失败要靠错误边界。use() 抛出的 Promise 如果被 reject,最近的 ErrorBoundary 会接住。

import { Component } from "react";

class ErrorBoundary extends Component {
  state = { error: null };
  static getDerivedStateFromError(error) {
    return { error };
  }
  render() {
    if (this.state.error) {
      return <p>加载失败:{String(this.state.error)}</p>;
    }
    return this.props.children;
  }
}

预取消除闪烁

在用户点进去之前(比如 hover 列表项)就发起请求,等真正渲染时缓存已就绪,Suspense 几乎不闪占位。

function UserList({ ids }) {
  return (
    <ul>
      {ids.map((id) => (
        <li key={id} onMouseEnter={() => fetchUser(id)}>
          <a href={"/users/" + id}>用户 {id}</a>
        </li>
      ))}
    </ul>
  );
}

它和 TanStack Query 的关系

use() 加 Suspense 是框架内建的轻量方案,适合"组件自取数、就近挂起"的场景;跨组件缓存、失效策略、轮询等仍是 TanStack Query 的强项。两者不冲突,可以在 Query 外层再包 Suspense。

小结

  • use() 让组件在渲染期直接读取 Promise,由 Suspense 接住加载态。
  • 资源必须按参数缓存,避免每次渲染重复请求。
  • 失败交给 ErrorBoundary,加载交给 Suspense,职责清晰。
  • 预取能把占位闪烁压到最低。
T

test1

文章作者

为什么把数据请求写进组件 过去的习惯是把请求放在父组件的 useEffect 里,再用 props 往下传。问题是加载...

分类
技术
发布时间
2026-10-20
字数
约 1788 字
阅读
0 次