原文连接:reactjs.org/docs/error-…html
过去,组件内出现JavaScript异常时会致使React内部的state被破坏而且在下一次渲染时抛出 可能没法跟踪的 异常。这些错误基本上都是有早期代码(非React组件代码)形成的,可是React并无提供可以优雅地在组件中处理和回复这些异常的方法。react
在部分UI中出现的JavaScript异常是不该该致使整个应用的崩溃的。为了解决这个问题,React16引进了一个新的概念“异常捕获边界(Error Boundaries)
“。git
异常捕获边界是一种React组件,它可以捕获在它子组件树中出现的任何JavaScript异常,将它们打印出来并展现一个备用UI,这样就不会致使组件树的崩溃。异常捕获边界可以捕获它的子组件数中在渲染,生命周期方法和构造函数中出现的任何异常。github
注意: 异常捕获边界不会捕获下列异常:npm
- 事件处理(了解更多)
- 异步代码(如
setTimeout
或requestAnimationFrame
回调函数)- 服务端渲染
- 异常捕获边界自身抛出的异常(不是它的子元素抛出的异常)
只要在组件中定义其中一个及以上的生命周期方法(static getDerivedStateFromError()或 componentDidCatch()),那么这个组件就变成了异常捕获边界。当异常被抛出时使用static getDerivedStateFromError()
来渲染一个备用UI,使用componentDidCatch()
来打印异常信息。浏览器
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
//更新state后再下次渲染时会显示备用UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
//你也能够在异常报告设备中打印异常信息
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
//你能够渲染自定义的任意备用UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
复制代码
如今你就能够像正常组件同样使用它了:bash
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
复制代码
异常捕获边界的工做机制相似于JavaScript的catch{}
模块,可是它是捕获组件抛出的异常的。只有class组件可以成为异常捕获边界。在实践中,绝大部分实践咱们只想要声明一个异常捕获边界,而后再整个应用中使用它。babel
注意异常捕获边界只会捕获那些做为它的子组件的组件抛出的异常。但它不会捕获自身抛出的异常。若是一个异常捕获边界未能成功渲染异常信息,那么它会把这个异常信息传递给离他最近的祖先异常捕获边界组件。这也是相似于JavaScript catch{}
模块的工做机制。app
异常捕获边界的粒度由你来决定。你能够包裹顶层组件来向用户展现“有什么地方出错了”,就像服务端框架处理崩溃同样。你也能够将小部件包裹再异常处理边界中来防止它崩溃时影响其余部分。框架
这一改变有着重要的意义。在React16中,任何没有被异常捕获边界捕获的异常将会致使整个React组件树的卸载。
对这个决定咱们也存在着争议,可是在咱们以往的经验中,不处理崩溃的UI比将它们彻底移除更加糟糕。好比在社交软件这类产品中,崩溃的UI留在界面上可能会致使用户将信息发送给错误的对象。一样的,在支付软件中显示错误的金额比什么也不展现形成的影响更大。
这一改变意味着当你迁移到React16时,你可能会发现以前没有发现的错误。使用异常捕获边界可以让你的应用在出现异常时提供一个更好的用户体验。
好比,Facebook Messager将侧边栏、信息面板、聊天记录以及信息输入框包裹在单独的异常捕获边界中,这样若是其中的某一部分崩溃了,也不影响其余部分的正常运行。
同时咱们也推荐使用JS错误报告服务(或者自行构建),这样就能够了解在生产环境中未捕获的异常信息而且修复它们。
在开发环境中,React16会将渲染过程当中出现的全部异常都打印到控制台上,即便应用之外地将它们掩盖了。除了异常信息和JavaScript栈,React16同时还提供了组件栈追踪功能。如今你能够看到异常发生在组件树中的具体位置了。
你也能够在组件栈追踪中看到异常所在的文件名和行数。这在经过Create React App建立的项目中默认执行。
若是你没有使用Create React App,你能够在Babel配置中手动添加这个插件。注意这只是在开发环境中使用的,在生产环境中必定要关闭这个功能。
注意: 在栈追踪中展现的组件名称取决于Function.name属性。若是你想要支持还没有提供该功能的浏览器或设备(好比IE11),考虑在你的打包应用程序中加入一个包含
Function.name
的polyfill
,好比function.name-polyfill。做为替代的,你也能够在你的组件中显式地设置displayName属性。
try/catch很棒但它只能用于命令式代码(imperative code):
try {
showButton();
} catch (error) {
// ...
}
复制代码
可是React组件是声明式的而且明确指出了什么是要被渲染的:
<Button />
复制代码
异常捕获边界保留了React声明式的特性而且可以像你预期的同样工做。即便在组件树的深层层级中的componentDidUpdate
方法中调用setState
方法发生了异常,它也会将这个异常传递给最近的那个异常捕获边界。
异常捕获边界不会捕获发生在事件处理器中的异常。
React不须要经过异常捕获边界来修复发生在事件处理器中的异常。不想render方法和生命周期方法,事件处理器不会再渲染期间被调用。因此即便事件处理器抛出了异常,React任然知道须要展现什么。
若是你须要在事件处理器中捕获异常,请使用常规的JavaScript try/catch语句:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { error: null };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
try {
// 会抛出异常的操做
} catch (error) {
this.setState({ error });
}
}
render() {
if (this.state.error) {
return <h1>Caught an error.</h1>
}
return <div onClick={this.handleClick}>Click Me</div>
}
}
复制代码
注意上面的例子只是展现了常规的JavaScript行为,并无使用异常捕获边界。
React15有一个对异常捕获支持有限的方法:unstable_handleError
。这个方法如今已经再也不起做用了,自使用React16开始,你须要把unstable_handleError
方法改成componentDidCatch
。
为了这个改变,咱们提供了codemod来自动迁移你的代码。