让我们面对它。没有人愿意在网上冲浪时看到一个破碎的、空的页面。它让你搁浅和迷惑。你不知道发生了什么,也不知道是什么原因造成的,让你对网站留下了不好的印象。
沟通错误并让用户继续使用该应用程序通常是更好的做法。用户会得到较少的坏印象,可以继续使用其功能。
在今天的文章中,我们将通过不同的方式来处理React应用程序中的错误。
React中经典的 "Try and Catch "方法
如果你使用过JavaScript,你可能不得不写一个 "try and catch "语句。为了确保我们了解它是什么,这里有一个:
try {
somethingBadMightHappen()
} catch (error) {
console.error("Something bad happened")
console.error(error)
}
它是一个很好的工具,可以捕捉行为不端的代码,确保我们的应用程序不会被炸成碎片。为了更现实,尽可能接近React世界,让我们看一个例子,看看你如何在你的应用程序中使用这个:
const fetchData = async () => {
try {
return await fetch("https://some-url-that-might-fail.com")
} catch (error) {
console.error(error) // You might send an exception to your error tracker like AppSignal
return error
}
}
在React中进行网络调用时,你通常会使用try...catch 语句。但为什么呢?不幸的是,try...catch only 在命令式代码上工作。它不适用于声明性代码,比如我们在组件中编写的JSX。所以这就是为什么你没有看到一个巨大的try...catch 来包装我们的整个应用程序。它就是行不通的。
那么,我们该怎么做?很高兴你这么问。在React 16中,引入了一个新的概念--React错误边界。让我们来探讨一下它们是什么。
React的错误边界
在我们进入错误边界之前,让我们先看看为什么它们是必要的。想象一下,你有一个这样的组件:
const CrashableComponent = (props) => {
return <span>{props.iDontExist.prop}</span>
}
export default CrashableComponent
如果你试图在某个地方渲染这个组件,你会得到一个像这样的错误。
不仅如此,整个页面将是空白的,用户将无法做或看到任何东西。但是发生了什么?我们试图访问一个属性iDontExist.prop ,而这个属性并不存在(我们没有把它传给这个组件)。这是个平庸的例子,但它表明我们不能用try...catch 语句来捕捉这些错误。
这整个实验把我们带到了错误边界。错误边界是React组件,它可以在其子组件树的任何地方捕获JavaScript错误。然后,它们会记录这些捕捉到的错误,并显示一个后备UI,而不是崩溃的组件树。错误边界在渲染过程中,在生命周期方法中,以及在它们下面的整个树的构造函数中捕捉错误。
错误边界是一个类组件,它定义了生命周期方法static getDerivedStateFromError() 或componentDidCatch() 中的一个(或两个)。static getDerivedStateFromError() 在抛出错误后渲染一个回退用户界面。componentDidCatch() 可以将错误信息记录到你的服务提供者(如AppSignal)或浏览器控制台中。
让我们看看一个典型的错误边界组件:
import { Component } from "react"
class ErrorBoundary extends Component {
constructor(props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return {
hasError: true,
error,
}
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service like AppSignal
// logErrorToMyService(error, errorInfo);
}
render() {
const { hasError, error } = this.state
if (hasError) {
// You can render any custom fallback UI
return (
<div>
<p>Something went wrong 😭</p>
{error.message && <span>Here's the error: {error.message}</span>}
</div>
)
}
return this.props.children
}
}
export default ErrorBoundary
我们可以像这样使用ErrorBoundary:
<ErrorBoundary>
<CrashableComponent />
</ErrorBoundary>
现在,当我们打开我们的应用程序时,我们将得到一个工作的应用程序,内容如下:

这正是我们想要的。我们希望我们的应用程序在发生错误时仍能保持功能。但我们也希望将错误告知用户(和我们的错误跟踪服务)。
请注意,使用错误边界并不是一个银弹。错误边界并不能捕捉到以下的错误:
- 事件处理程序
- 异步代码(如setTimeout或 requestAnimationFrame回调)。
- 服务器端的渲染
- 在错误边界本身(而不是其子代)抛出的错误
你仍然需要对这些家伙使用try...catch 语句。所以,让我们继续展示你如何做到这一点。
事件处理程序中的错误捕获
如前所述,当错误在事件处理程序中被抛出时,错误边界不能帮助我们。让我们看看我们如何处理这些。下面是一个小的按钮组件,当你点击它时抛出一个错误:
import { useState } from "react"
const CrashableButton = () => {
const [error, setError] = useState(null)
const handleClick = () => {
try {
throw Error("Oh no :(")
} catch (error) {
setError(error)
}
}
if (error) {
return <span>Caught an error.</span>
}
return <button onClick={handleClick}>Click Me To Throw Error</button>
}
export default CrashableButton
请注意,我们在handleClick 里面有一个try和catch块,确保我们的错误被捕获。如果你渲染这个组件并试图点击它,就会发生这种情况。

我们在其他情况下也要这样做,比如在setTimeout 。
setTimeout 调用中的错误捕获
想象一下,我们有一个类似的按钮组件,但这个组件在被点击时调用setTimeout 。下面是它的样子:
import { useState } from "react"
const SetTimeoutButton = () => {
const [error, setError] = useState(null)
const handleClick = () => {
setTimeout(() => {
try {
throw Error("Oh no, an error :(")
} catch (error) {
setError(error)
}
}, 1000)
}
if (error) {
return <span>Caught a delayed error.</span>
}
return (
<button onClick={handleClick}>Click Me To Throw a Delayed Error</button>
)
}
export default SetTimeoutButton
在1000毫秒之后,setTimeout 回调将抛出一个错误。幸运的是,我们把这个回调逻辑包在try...catch ,和setError 组件中。这样一来,在浏览器控制台中就不会显示堆栈跟踪。此外,我们还将错误传达给用户。下面是它在应用程序中的样子。

这一切都很好,因为尽管后台到处都是错误,我们还是让我们的应用程序的页面运行起来了。但是,有没有一种更简单的方法来处理错误,而不需要编写自定义错误边界?你肯定有,当然,它是以JavaScript包的形式出现的。让我向你介绍一下react-error-boundary 。
JavaScript的react-error-boundary 包
你可以在你的package.json ,比以前更快地弹出该库:
npm install --save react-error-boundary
现在,你已经准备好使用它了。还记得我们做的ErrorBoundary 组件吗?你可以忘了它,因为这个包导出了它自己的。下面是如何使用它:
import { ErrorBoundary } from "react-error-boundary"
import CrashableComponent from "./CrashableComponent"
const FancyDependencyErrorHandling = () => {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error) => {
// You can also log the error to an error reporting service like AppSignal
// logErrorToMyService(error, errorInfo);
console.error(error)
}}
>
<CrashableComponent />
</ErrorBoundary>
)
}
const ErrorFallback = ({ error }) => (
<div>
<p>Something went wrong 😭</p>
{error.message && <span>Here's the error: {error.message}</span>}
</div>
)
export default FancyDependencyErrorHandling
在上面的例子中,我们渲染了同样的CrashableComponent ,但这次我们使用了来自 react-error-boundary 库的ErrorBoundary 组件。它和我们的自定义组件做了同样的事情,只是它接收了FallbackComponent 道具和onError 函数处理器。其结果与我们自定义的ErrorBoundary 组件相同,只是你不必担心维护它,因为你使用的是一个外部包。
这个包的一个好处是,你可以很容易地把你的函数组件包装成一个withErrorBoundary ,使其成为一个高阶组件(HOC)。下面是它的样子:
import { withErrorBoundary } from "react-error-boundary"
const CrashableComponent = (props) => {
return <span>{props.iDontExist.prop}</span>
}
export default withErrorBoundary(CrashableComponent, {
FallbackComponent: () => <span>Oh no :(</span>,
})
很好,你现在可以去捕捉所有那些困扰你的错误了。
但是,也许你不希望在你的项目中出现另一个依赖关系。你能自己实现它吗?当然可以。让我们来看看如何做到这一点。
使用你的边界
你可以实现类似的,甚至相同的效果,你从react-error-boundary 。我们已经展示了一个自定义的ErrorBoundary 组件,但让我们改进它:
import { Component } from "react"
export default class ErrorBoundary extends Component {
constructor(props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return {
hasError: true,
error,
}
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service like AppSignal
// logErrorToMyService(error, errorInfo);
}
render() {
const { hasError, error } = this.state
if (hasError) {
// You can render any custom fallback UI
return <ErrorFallback error={error} />
}
return this.props.children
}
}
const ErrorFallback = ({ error }) => (
<div>
<p>Something went wrong 😭</p>
{error.message && <span>Here's the error: {error.message}</span>}
</div>
)
const errorBoundary = (WrappedComponent) => {
return class extends ErrorBoundary {
render() {
const { hasError, error } = this.state
if (hasError) {
// You can render any custom fallback UI
return <ErrorFallback error={error} />
}
return <WrappedComponent {...this.props} />
}
}
}
export { errorBoundary }
现在你得到了ErrorBoundary 和HOCerrorBoundary ,你可以在你的应用程序中使用。尽情地扩展和玩弄它吧。你可以让它们接收自定义的回退组件,以自定义你如何从每个错误中恢复。你也可以让它们接收一个onError 道具,然后在componentDidCatch 里面调用它。这种可能性是无穷无尽的。
但有一件事是肯定的--你毕竟不需要那个依赖性。我敢打赌,编写你自己的错误边界会带来成就感,而且你会更加了解它。另外,谁知道当你试图定制它时,你会得到什么想法。
总结一下:开始使用React错误处理
谢谢你阅读这篇关于在React中处理错误的博文。我希望你在阅读和尝试的过程中能像我写这篇文章一样开心。你可以在我创建的GitHub repo中找到所有的代码和例子。
简单介绍一下我们所经历的事情:
- React错误边界对于捕捉声明性代码中的错误非常有用(例如,在其子组件树内)。
- 对于其他情况,你需要使用
try...catch语句(例如,像setTimeout,事件处理程序,服务器端渲染,以及在错误边界本身抛出的错误)。 - 像
react-error-boundary这样的库可以帮助你写更少的代码。 - 你也可以运行你自己的错误边界,并根据你的需要定制它。
这就是全部,伙计们。谢谢大家的收听,下一讲再见