写在前面
在实际的业务场景中,react应用一定会涉及到错误处理的情况。默认情况下,react应用出现fetal error时,浏览器上显示的是一个空白页面,这对于用户来说很不友好---------他们并不知道发生了什么以及为什么发生。对于前端开发者而言,我们应该如何去处理这种错误处理,避免影响用户体验呢?最简单最有效的方法,就是在我们的react应用中添加一个错误处理层(Error Handling Layer)。
错误处理层能够将所有错误处理逻辑集中在一起,允许我们捕获错误并向用户显示清晰而有用的错误消息。这可以帮助react应用避免因为崩溃而导致的浏览器白屏显示,从而改善整体用户体验。
接下来就让我们详细了解错误处理层是什么,理解为什么React应用需要有一个错误处理层,以及如何在React中实现错误处理层。
react应用中什么是错误处理层?
错误处理层是react应用中的一部分,它将错误处理逻辑的代码封装并集中在一个地方,负责检测和处理react应用中发送的错误。
在React应用中,错误处理层通常负责以下功能:
- 错误检测:拦截react应用程序在执行期间发生的错误和异常。
- 错误处理:在检测到错误后,它将优雅的处理掉这些错误,特别是防止react应用程序因为一些错误导致崩溃而在浏览器端显示白屏,并且能够在浏览器端向用户显示自定义的错误页面。在背后,错误处理层还可以记录错误并将错误日志报告给错误监控服务。
react应用中为什么需要错误处理层?
如前文所述,react应用中默认对异常错误的处理方式就是卸载整个应用,浏览器显示白屏,它没有内置的机制来处理这些运行过程中发生的错误。可以想象,用户遇到应用白屏的情况,绝对会是一种懵逼状态,他们完全不知道发生了什么。因此,react默认的错误处理方式不是一个理想的解决方案,我们才需要在react应用中建立一个错误处理层。
详细地来说,建立错误处理层有以下好处:
- 避免崩溃:如果有错误发生了,错误处理层会拦截它,并按照我们的意愿来处理错误,防止应用程序因未处理的错误而崩溃。
- 改善用户体验:错误处理层无论何时检测到错误,最终都会让应用向用户显示一个漂亮的错误页面,并展示错误信息,这对用户来说十分友好。
- 集中错误处理逻辑:能够将react应用中所有的错误处理逻辑集中在一起,并在react应用中集成错误日志记录和监控,开发者们可以方便的收集这些错误信息,然后使用这些信息来识别错误的根源、调试和修复错误。
下面我们就来看看如何在react应用中添加错误处理层。
使用错误边界(Error Boundary)来实现错误处理层
从react 16开始,官方提供了Error Boundary来进行错误处理。Error Boundary是一个特殊的react组件,它可以捕获子组件树中任何地方发生的JS错误,并且在错误发生时显示备用UI组件,从而优雅地处理错误并避免应用出现致命的崩溃。
需要注意的是,标准Error Boundary只能捕获在组件渲染时、生命周期方法中和组件构造函数中的错误,其它时候的错误它将捕获不到。
接下来让我们一步一步在React应用中使用Error Boundary来创建错误处理层!
1. 创建一个Error Boundary组件
要在React中创建一个Error Boundary,我们需要定义一个类组件,它包括以下内容:
- 状态变量hasError:用于确定错误边界是否拦截了错误。
- 静态方法getDerivedStateFromError:一个React生命周期方法,在后代组件抛出错误后调用。该方法返回的值将更新Error Boundary组件的状态。
- 方法componentDidCatch:一个特殊的React生命周期方法,当Error Boundary捕获错误时调用。我们可以用它来记录错误日志并上报。
- 一个带有if-else逻辑的render()方法:在出现错误的情况下,它返回备用UI组件。否则,它将显示由Error Boundary包装的子组件。
注意,React只在类组件上支持关键的生命周期方法getDerivedStateFromError()和componentDidCatch()。这意味着无法将错误边界编写为函数式组件。
定义Error Boundary组件:
js
import React from "react";
import ErrorPage from "./ErrorPage";
export default class StandardErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: undefined
};
}
// 当出现错误时更新组件状态
static getDerivedStateFromError(error) {
// 指定Error Boundary捕获错误
return {
hasError: true,
error: error
};
}
// 定义捕获错误时要做什么
componentDidCatch(error, errorInfo) {
// 记录错误日志
console.log("Error caught!");
console.error(error);
console.error(errorInfo);
}
render() {
// 错误出现时ErrorPage
if (this.state.hasError) {
return <ErrorPage />;
} else {
// 正常显示
return this.props.children;
}
}
}
当在它的任何子组件中发生错误时,StandardErrorBoundary会拦截、记录它,并将hasError设置为true,然后呈现备用UI组件。
定义ErrorPage 组件:
js
export default function ErrorPage(props) {
return (
<div className={"error-page"}>
<div className={"oops"}>Oops!</div>
<div className={"message"}>Something went wrong...</div>
</div>
);
}
然后我们可以根据自己的喜好来定制备用UI组件ErrorPage样式就行了。值得注意的是,一个有效的ErrorPage组件告诉用户发生了什么就行了,不需要过多的细节来让用户担心。
2. 如何使用Error Boundary组件
我们在react应用中用上面定义的StandardErrorBoundary组件包裹住顶级组件就行了:
js
<StandardErrorBoundary>
<App />
</StandardErrorBoundary>
现在,react应用中发生的任何与react相关的错误都将被Error Boundary拦截和处理。
注意:react应用中可以有多个Error Boundary,每一个Error Boundary都负责处理不同组件的子树中的错误。但是为了将错误逻辑集中在一个地方,还是建议只使用一个顶级错误边界比较好。
3. Error Boundary的局限性
Error Boundary是一个很好的工具,但是它也有一些局限性,react官方文档中提到,Error Boundary不能捕获到下列的异常错误:
- 异步代码
- 事件处理函数
- 服务器组件
- Error Boundary自身
考虑到JavaScript严重依赖事件回调和异步代码,Error Boundary的这些限制有可能带来一些问题。
使用react-error-boundary来实现错误处理层
在react官方文档中,也推荐使用react-error-boundary来实现react应用中的错误处理。react-error-boundary是一个npm react库,它提供了一种简单可靠的方法来使用错误边界来处理错误。它简化了创建错误边界的过程,并提供了有效的解决方案来克服标准错误边界的局限性。
我们通过npm install 将react-error-boundary添加到我们的项目中来
js
npm install react-error-boundary
接下来我详细讲讲如何使用它。
1. 创建Error Boundary组件
react-error-boundary库提供了一个ErrorBoundary组件,它暴露了几个prop属性来构建错误边界组件。下面是用react-error-boundary创建的错误边界:
js
import ErrorPage from "./ErrorPage";
import { ErrorBoundary } from "react-error-boundary";
export default function ReactErrorBoundary(props) {
return (
<ErrorBoundary
FallbackComponent={ErrorPage}
onError={(error, errorInfo) => {
// log the error
console.log("Error caught!");
console.error(error);
console.error(errorInfo);
// record the error in an APM tool...
}}
>
{props.children}
</ErrorBoundary>
);
}
FallbackComponent属性,提供了一个在错误时渲染的备用UI组件。当错误发生时,也会触发onError事件,执行我们定义的事件处理函数。如果没有错误发生,将显示它所包裹的子组件。相比前面用官方的标准的Error Boundary组件,通过第三方库来创建错误边界,代码更简洁明了。
和前文一样,我们用我们的错误边界组件包裹我们顶级组件就行了:
js
<ReactErrorBoundary>
<App />
</ReactErrorBoundary>
用react-error-boundary构建的错误边界组件既可以是函数组件,也可以是类组件。我们不再局限于类组件,可以根据自己的目标和偏好做出最佳选择。
2. 发生错误后重置应用
react-error-boundary 提供了一个onReset的prop属性,它接受一个回调函数,ErrorBoundary组件会自动将这个方法传递给备用UI组件。
现在我们来更新我们前文定义的ReactErrorBoundary组件:
js
import ErrorPage from "./ErrorPage";
import { ErrorBoundary } from "react-error-boundary";
export default function ReactErrorBoundary(props) {
return (
<ErrorBoundary
FallbackComponent={ErrorPage}
onError={(error, errorInfo) => {
// log the error
console.log("Error caught!");
console.error(error);
console.error(errorInfo);
}}
onReset={() => {
// reloading the page to restore the initial state
// of the current page
console.log("reloading the page...");
window.location.reload();
// other reset logic...
}}
>
{props.children}
</ErrorBoundary>
);
}
onReset回调函数所做的只是通过js重新加载当前页面,这是最基本的错误恢复逻辑。在更复杂的应用程序中,您可能需要将应用的状态恢复到前一个点或更新Redux状态。
默认情况下,ErrorBoundary会向备用UI组件传递以下两个prop:
- Error:存储由错误边界捕获的错误对象。
- resetErrorBoundary:包含传递给onReset prop属性的回调函数(如果定义的话)。
现在,我们可以试试添加一个重试按钮到ErrorPage,如下所示:
js
import "./ErrorPage.css";
export default function ErrorPage(props) {
return (
<div className={"error-page"}>
<div className={"oops"}>Oops!</div>
<div className={"message"}>Something went wrong...</div>
{props.resetErrorBoundary && (
<div>
<button className={"retry-button"} onClick={props.resetErrorBoundary}>
Try Again!
</button>
</div>
)}
</div>
);
}
现在,出现错误时显示的回退页面上将显示"Tra Again"按钮。当用户单击它时,将执行onReset函数并重新加载当前页面。如果应用是在用户操作过程中出现的错误,那么刷新页面,应用将恢复正常。
3. 处理异步代码的错误
react-error-boundary提供了useErrorHandler() hook,这个hook能够允许我们捕获到到传统的React错误边界无法捕获到的error,因此在API请求、事件处理程函数中,我们可以使用useErrorHandler()来捕获错误。
下面就是一个在异步请求代码中使用useErrorHandler() hook来捕获异常的示例:
js
import { useEffect } from "react";
import { useErrorHandler } from "react-error-boundary";
export default function MyPageComponent(props) {
const handleError = useErrorHandler();
useEffect(() => {
// API call
fetchSomeData().then(() => {
// ...
}).catch((e) => {
// 将错误传递给ErrorBoundary
handleError(e);
})
}, [])
// return ...
}
类似地,我们可以在标准错误边界捕获不到错误的任何情况下采用useErrorhandler()。另外,请记住,不管我们是用react-error-boundary实现的错误边界还是标准实现的错误边界,useErrorhandler()可以将错误传递过去。
结语
在本文中,我们了解了错误处理层是什么,错误处理层的好处,以及如何将其集成到React应用中来。错误处理层其实就是一个错误边界组件,它能够捕获错误然后优雅地处理错误。这样我们就能够将错误处理相关的逻辑全部整合到react应用中一个地方,从而让我们调试、回溯问题更加方便容易。得益于第三方库react-error-boundary,我们能够弥补官方提供的标准错误边界组件的不足,让我们能够更加游刃有余的去实现react应用中错误处理层。
本文如有错误,敬请指正!