Skip to content

Suspense 和 ErrorBoundary

原理相似,Suspense 捕获 Promise,ErrorBoundary 捕获 error。

Suspense

Suspense 允许组件在渲染前"等待"某些操作完成,如数据加载。

主要用途

1.异步加载组件:

jsx
const OtherComponent = React.lazy(() => import("./OtherComponent"));
function MyComponent() {
  return (
    <React.Suspense fallback={<Spinner />}>
      <OtherComponent />
    </React.Suspense>
  );
}

2.数据获取:

jsx
const resource = fetchProfileData();
function ProfilePage() {
  return (
    <Suspense fallback={<h1>加载中...</h1>}>
      <ProfileDetails />
      <ProfileTimeline />
    </Suspense>
  );
}

实现原理

Suspense 的核心机制是基于 "抛出 Promise" 的概念:

  1. 当子组件抛出 Promise 时,Suspense 会捕获它。
  2. Suspense 渲染 fallback 内容。
  3. Promise 解决后,Suspense 重新尝试渲染子组件。

React 内部使用 thenable 接口检测 Promise:

javascript
if (
  typeof value === "object" &&
  value !== null &&
  typeof value.then === "function"
) {
  // 这是一个 Promise/thenable
}

底层机制

  1. Fiber 架构: Suspense 利用 Fiber 的可中断渲染特性。
  2. 优先级调度: 在并发模式下,Suspense 可以根据优先级调整渲染顺序。
  3. 副作用标记: Suspense 组件在 Fiber 树中标记特殊的副作用,用于跟踪挂起状态。

ErrorBoundary

ErrorBoundary 捕获子组件树的 JavaScript 错误,记录错误并展示备用 UI。

基本用法

jsx
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  componentDidCatch(error, errorInfo) {
    console.log(error, errorInfo);
  }
  render() {
    if (this.state.hasError) {
      return <h1>出错了!</h1>;
    }
    return this.props.children;
  }
}

// 使用
<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>;

实现原理

ErrorBoundary 基于 React 的生命周期方法:

  1. getDerivedStateFromError: 静态方法,用于更新状态。
  2. componentDidCatch: 用于副作用,如日志记录。

React 内部通过 try/catch 块包裹渲染过程来实现错误捕获:

javascript
try {
  renderComponentTree();
} catch (error) {
  handleError(error);
}

底层机制

  1. 错误传播: 错误在组件树中向上冒泡,直到遇到最近的 ErrorBoundary。
  2. 降级渲染: 发生错误时,React 会尝试卸载整个发生错误的组件子树。
  3. 副作用清理: React 确保在错误发生时清理相关组件的副作用。

高级用法

Suspense 与数据获取

React 18 引入 use 钩子:

jsx
function Profile() {
  const user = use(fetchUser());
  return <h1>{user.name}</h1>;
}

use 内部实现:

  1. 检查资源是否就绪
  2. 如未就绪,抛出 Promise
  3. Promise 解决后,重新渲染组件

ErrorBoundary 与错误恢复

jsx
class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    logErrorToService(error, info);
  }

  retry = () => {
    this.setState({ hasError: false });
  };

  render() {
    if (this.state.hasError) {
      return <button onClick={this.retry}>重试</button>;
    }
    return this.props.children;
  }
}

Suspense 和 ErrorBoundary 结合

jsx
function AsyncComponent() {
  const data = use(fetchData());
  return <div>{data}</div>;
}

function App() {
  return (
    <ErrorBoundary fallback={<Error />}>
      <Suspense fallback={<Loading />}>
        <AsyncComponent />
      </Suspense>
    </ErrorBoundary>
  );
}

这种组合能优雅处理异步操作的加载和错误状态,提供更好的用户体验。

Crafted with care.