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" 的概念:
- 当子组件抛出 Promise 时,Suspense 会捕获它。
- Suspense 渲染 fallback 内容。
- Promise 解决后,Suspense 重新尝试渲染子组件。
React 内部使用 thenable
接口检测 Promise:
javascript
if (
typeof value === "object" &&
value !== null &&
typeof value.then === "function"
) {
// 这是一个 Promise/thenable
}
底层机制
- Fiber 架构: Suspense 利用 Fiber 的可中断渲染特性。
- 优先级调度: 在并发模式下,Suspense 可以根据优先级调整渲染顺序。
- 副作用标记: 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 的生命周期方法:
getDerivedStateFromError
: 静态方法,用于更新状态。componentDidCatch
: 用于副作用,如日志记录。
React 内部通过 try/catch
块包裹渲染过程来实现错误捕获:
javascript
try {
renderComponentTree();
} catch (error) {
handleError(error);
}
底层机制
- 错误传播: 错误在组件树中向上冒泡,直到遇到最近的 ErrorBoundary。
- 降级渲染: 发生错误时,React 会尝试卸载整个发生错误的组件子树。
- 副作用清理: React 确保在错误发生时清理相关组件的副作用。
高级用法
Suspense 与数据获取
React 18 引入 use
钩子:
jsx
function Profile() {
const user = use(fetchUser());
return <h1>{user.name}</h1>;
}
use
内部实现:
- 检查资源是否就绪
- 如未就绪,抛出 Promise
- 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>
);
}
这种组合能优雅处理异步操作的加载和错误状态,提供更好的用户体验。