React18中Suspense的用法以及原理

119 阅读3分钟

今天特意再次温习了下 Suspense 组件的用法,结合 chatgpt 深度学习了一遍,这里将总结记录一下,欢迎讨论。

1、Suspense是什么?原理解析

在 React18 中,Suspense 是一个用于处理异步加载状态的组件。它的核心作用是:

  • 在组件或资源未准备好渲染时,显示一个占位 UI(fallback)
  • 当异步操作完成后,自动渲染真实内容

React 捕获异步状态的方式与传统思路不同:它并不是去轮询状态或者监听 Promise,而是依赖一个约定:

当组件在渲染阶段未准备好时,可以同步抛出一个 Promise 错误,React 捕获这个 Promise 错误,然后显示 Suspense 的 fallback。Promise resolve 后,React 重新调度渲染组件。

简化的原理流程如下:

function renderWithSuspense(component) {
  try {
    return component(); // 执行组件函数
  } catch (e) {
    if (e instanceof Promise) {
      // 捕获 Promise 错误 → 挂起组件 → fallback
      scheduleRetryAfterPromise(e);
      return fallbackUI;
    } else {
      throw e; // 真正错误
    }
  }
}

也就是说,Suspense 并不会主动跟踪 Promise 状态,它只关心组件函数是否抛出了一个 Promise 错误


2、Suspense的用法与适用场景

2.1 组件异步加载(React.lazy)

Suspense 最自然、最合理的使用场景是懒加载组件。React18 内置了 React.lazy 配合 Suspense,可以实现按需加载组件:

import React, { Suspense, lazy } from 'react';

const LazyComponent = lazy(() => import('./LazyComponent'));

function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <LazyComponent />
    </Suspense>
  );
}
  • fallback:异步加载期间显示的占位 UI;
  • React.lazy 会在模块加载未完成时,同步抛出一个 Promise 错误,Suspense 捕获后显示 fallback;
  • 模块加载完成后,自动渲染组件。

2.2 嵌套 Suspense

Suspense 支持嵌套,用于控制局部加载:

<Suspense fallback={<div>加载列表中...</div>}>
  <ListComponent />
  <Suspense fallback={<div>加载详情中...</div>}>
    <DetailComponent />
  </Suspense>
</Suspense>
  • 内层 Suspense 有独立的 fallback;
  • 外层 Suspense 无法捕获内层 fallback 的状态。

3、Suspense作为异步接口请求的等待机制

3.1 是否合适?

普通的接口请求(fetch 或 Axios)不能直接和 Suspense 搭配,因为:

  • 普通 Promise 在 pending 状态下不会同步抛出;
  • React 只能捕获渲染阶段同步抛出的 Promise
  • 因此直接写 fetch + useEffect 是无法触发 Suspense fallback 的。

结论:Suspense 用作异步接口等待并不自然,如果一定要用,需要手动封装。


3.2 如何封装 Promise

可以封装一个支持 Suspense 的资源对象:

function wrapPromise<T>(promise: Promise<T>) {
  let status: 'pending' | 'success' | 'error' = 'pending';
  let result: T | any;
  const suspender = promise.then(
    r => { status = 'success'; result = r; },
    e => { status = 'error'; result = e; }
  );

  return {
    read() {
      if (status === 'pending') throw suspender; // 同步抛出 Promise 错误
      if (status === 'error') throw result;      // 同步抛出错误
      return result;                              // 返回成功数据
    }
  };
}

3.3 使用封装后的资源

const userResource = wrapPromise(fetch('/api/user/1').then(res => res.json()));

function UserProfile() {
  const user = userResource.read(); // 若未完成,抛出 Promise 错误 → Suspense 捕获
  return <div>{user.name}</div>;
}

export default function App() {
  return (
    <Suspense fallback={<div>加载用户中...</div>}>
      <UserProfile />
    </Suspense>
  );
}

运行逻辑:

  1. 组件渲染 → 调用 read()
  2. 数据未完成 → read() 同步抛出 Promise 错误 → Suspense fallback 显示 loading;
  3. 数据完成 → Promise resolve → React 自动重新渲染组件 → 显示真实内容。

4、总结

  1. Suspense 本质:捕获渲染阶段抛出的 Promise 错误,统一处理异步加载;

  2. 最自然的使用场景:组件懒加载(React.lazy);

  3. 异步数据加载:不推荐直接用 Suspense,需要手动封装 Promise 或使用支持 Suspense 的库(React Query / SWR);

  4. 普通 fetch 直接放组件里无法触发 Suspense,因为 Promise 是异步的,不会同步抛出;

  5. 一句话总结:Suspense 的设计是为了让异步渲染像同步渲染一样自然,将异步加载统一成“throw Promise 错误 → Suspense 捕获 → fallback → resolve → 重新渲染”的机制。