Error Boundary 是一个 React 组件,用于捕获其子组件树中的 JavaScript 错误,并显示备用界面而不是崩溃页面。在 React Native 的上下文中,它可以在非致命的渲染错误时防止应用程序完全崩溃。根据 React Documentation, 2024,error boundary 可以捕获子组件的 render 方法、生命周期钩子和构造函数中的错误,使应用程序能够继续运行。在 React Native 中,这尤为关键,因为移动应用程序不能通过 F5 重新加载——用户将丢失整个会话。
要点
Error Boundary 是 React 的一种机制,用于在渲染错误时实现优雅降级。它在 React 16(2017 年)中作为包装组件引入,实现了两个生命周期方法之一:static getDerivedStateFromError 或 componentDidCatch。Error Boundary 允许用户看到有意义的错误消息,而不是空白的白屏或应用程序完全崩溃。
在 React 16 之前,render 中任何未处理的错误都会导致整个应用程序崩溃,整个 DOM 树被卸载。在 Web 应用程序中,这意味着空白的白屏;在 React Native 中,应用程序完全崩溃并返回主屏幕。React 团队引入了 Error Boundary 作为声明式 UI 的 catch 块等效物,借鉴了 Erlang 语言中 “任其崩溃” 的方法。
在 React Native 中,缺少 Error Boundary 意味着每次渲染错误都会导致应用程序完全崩溃。用户会丢失整个当前会话,且无法恢复。Error Boundary 在 React Native 中至关重要,因为移动应用程序不会像网页那样重新加载——用户会话将永久丢失,用户必须从头开始。
Error Boundary 在 React 树级别工作。当子组件在 render 或 lifecycle 中抛出错误时,React 不会卸载整个树,而是将控制权传递给层次结构中最近的 Error Boundary。Boundary 调用 getDerivedStateFromError,设置 state.hasError = true,并渲染 fallback UI 代替损坏的组件分支。
import React, { Component, ErrorInfo, ReactNode } from "react"
interface Props {
children: ReactNode
fallback?: ReactNode
}
interface State {
hasError: boolean
error?: Error
}
class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error("被 boundary 捕获:", error)
Crashlytics.recordException(error)
}
render() {
if (this.state.hasError) {
return this.props.fallback || <FallbackUI />
}
return this.props.children
}
}
getDerivedStateFromError 设置渲染 fallback UI 的状态——这是一个静态方法,在渲染阶段提交更改之前调用。componentDidCatch 在提交阶段执行,用于副作用:日志记录、向 Crashlytics 发送崩溃报告、分析。这两种方法在 UI 状态管理和副作用之间分担责任。
要创建 Error Boundary,需要通过 getDerivedStateFromError 和/或 componentDidCatch 方法实现一个类组件。函数式组件不能成为 Error Boundary——React 仅支持类组件使用此功能,因为需要访问生命周期方法。react-error-boundary 库提供了现成的实现,带有 hooks API 以方便使用。
// 在 React Native 中使用 Error Boundary 的示例
import { ErrorBoundary } from "react-error-boundary"
const FallbackComponent = ({ error, resetError }: FallbackProps) => (
<View style={styles.container}>
<Text>出了点问题</Text>
<Text>{error.message}</Text>
<Button title="重试" onPress={resetError} />
</View>
)
const App = () => (
<SafeAreaView>
<ErrorBoundary FallbackComponent={<FallbackComponent />}>
<UserProfile userId={"123"} />
</ErrorBoundary>
<BottomNavigation />
</SafeAreaView>
)
包装级别——Error Boundary 可以放置在层次结构的不同级别。应用程序根目录的一个全局 Boundary 会在任何错误时显示 fallback UI,但导航仍然可以工作。屏幕级别的多个 Boundary 可以隔离错误:如果一个屏幕损坏,其他屏幕可以独立继续工作。react-error-boundary 通过 useErrorBoundary hook 简化了重置机制,允许在不重新加载的情况下重置状态。对于典型项目,三层 Boundary 方案被认为是 React Native 应用程序的最佳实践。
错误发生后,用户可以点击 “重试” 按钮,将 hasError 重置为 false 并重新渲染子树。重置机制对于在不重新加载的情况下恢复应用程序的功能非常重要。在 react-error-boundary 中,使用 onReset 回调,它可以清除缓存、重新请求数据或更新树中更高层的状态。
Error Boundary 不捕获异步错误——setTimeout、setInterval、Promise、async/await 中的错误。React 无法捕获渲染周期和生命周期钩子之外的错误,因为它们在其他执行上下文中运行。对于异步错误,需要在处理程序中使用单独的 try-catch 或全局的 unhandledrejection 事件处理程序。
onClick、onChange 和其他事件处理程序中的错误不会被 Error Boundary 捕获,因为它们是在 React 渲染之外执行的。事件处理程序的错误处理应该通过 try-catch 在处理程序内部进行。react-error-boundary 库提供了 useErrorHandler hook,用于将错误从事件处理程序传递到最近的 Boundary。
Error Boundary 在 Next.js 或 Gatsby 的服务器端不起作用。getDerivedStateFromError 和 componentDidCatch 方法在 SSR 时不会被调用,因为生命周期方法仅在浏览器中可用。对于服务器错误,需要单独的策略:getServerSideProps 中的 try-catch、error.js 后备页面(Next.js 13+)或全局中间件。
在 React Native 中,Error Boundary 不能防止原生模块级别的崩溃。原生崩溃(segfault、out-of-memory、原生异常)发生在 Objective-C 或 Java 级别,不会到达 JavaScript 层。对于原生崩溃,需要 Crashlytics NDK(Android)或 KSCrash(iOS)。Error Boundary 仅保护 React Native 应用程序的 JavaScript 层。
将 Error Boundary 放置在有意义的模块边界上:每个屏幕一个 Boundary,每个第三方开发者的 widget 一个 Boundary,每个复杂表单一个 Boundary。这可以隔离错误,并允许用户在应用程序的其他部分继续工作。根 Boundary 必须始终存在——用于公共导航组件或提供程序中的关键错误。每个 Boundary 负责自己的界面片段,并且在错误发生时不会影响相邻的组件。
始终通过 componentDidCatch 将错误传递到 Crashlytics 或 Sentry。添加上下文:屏幕名称、userId、应用程序版本、导航参数。Sentry 中提供了 breadcrumbs——用户错误前的操作序列。对于非致命错误频率分析,使用按问题分组的 Crashlytics 仪表板。
不要使用 “空的” fallback——创建有意义的界面。推荐套件适用于 React Native:错误消息(用户友好,非技术性)、“重试” 按钮、支持或聊天链接。避免空白 View——用户会认为应用程序完全崩溃并关闭它。Fallback 应该融入应用程序的整体设计中。
使用 React Testing Library 或 React Native Testing Library 测试每个 Error Boundary。创建一个在渲染时抛出错误的触发器组件,并检查是否显示 fallback UI。对于集成测试,使用不同 Error Boundary 状态的 storybook:正常、错误、重置后的状态。自动化的 Boundary 测试确保在组件更改时,fallback UI 在生产环境中继续正常工作。每个 Boundary 的测试覆盖应该是 React Native 项目的 code review 强制要求。
常见问题
React 仅通过类组件实现 Error Boundary,因为需要访问 componentDidCatch 和 getDerivedStateFromError 生命周期方法。函数式组件没有这些方法。react-error-boundary 库提供现成的类包装器,带有 hooks API 以方便使用。
影响很小——Error Boundary 在每次渲染子树时添加一个状态检查。比较 state.hasError 是一个 O(1) 操作,具有恒定的复杂度。在没有错误时,没有开销。只有在发生错误时,Boundary 才会执行额外的 fallback UI 渲染。
不需要,2-3 层就够了:整个应用程序的根 Boundary、每个导航分支的屏幕 Boundary 以及关键 widget 的本地 Boundary(支付表单、地图、聊天)。过多的 Boundary 会使架构复杂化而没有显著的好处。
Error Boundary 和 Suspense 是独立的:Suspense 捕获加载(React 18+ 中的 pending Promise),Error Boundary 捕获渲染错误。它们可以组合使用:<ErrorBoundary><Suspense><Component /></Suspense></ErrorBoundary>。加载时 Suspense 首先执行,Error Boundary 在加载的组件出错时执行。
try-catch 捕获同步命令式代码中的错误,但无法捕获 JSX 渲染错误。Error Boundary 专为声明式 UI 而设计:它捕获 render、生命周期钩子和子组件构造函数中的错误,由于 React 渲染的特性,try-catch 无法做到这一点。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。