1、错误边界
部分 UI 的 JavaScript 错误不应该导致整个应用崩溃,为了解决这个问题,React 16 引入了一个新的概念 —— 错误边界。
错误边界是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。
注意 错误边界无法捕获以下场景中产生的错误:
- 事件处理(了解更多)
- 异步代码(例如
**setTimeout**
或**requestAnimationFrame**
回调函数)- 服务端渲染
- 它自身抛出来的错误(并非它的子组件)
A.错误处理组件的定义
一个 class 组件中定义了 [static getDerivedStateFromError()](https://zh-hans.reactjs.org/docs/react-component.html#static-getderivedstatefromerror)
或 [componentDidCatch()](https://zh-hans.reactjs.org/docs/react-component.html#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;
}
}
B.像常规组件引入使用
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
错误边界的工作方式类似于 JavaScript 的 catch {}
,不同的地方在于错误边界只针对 React 组件。只有 class 组件才可以成为错误边界组件。大多数情况下, 只需要声明一次错误边界组件, 并在整个应用中使用它。
注意:错误边界仅可以捕获其子组件的错误,它无法捕获其自身的错误。如果一个错误边界无法渲染错误信息,则错误会冒泡至最近的上层错误边界,这也类似于 JavaScript 中 catch {} 的工作机制。
C.官网示例
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { error: null, errorInfo: null };
}
componentDidCatch(error, errorInfo) {
// Catch errors in any components below and re-render with error message
this.setState({
error: error,
errorInfo: errorInfo
})
// You can also log error messages to an error reporting service here
}
render() {
if (this.state.errorInfo) {
// Error path
return (
<div>
<h2>Something went wrong.</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
// Normally, just render children
return this.props.children;
}
}
class BuggyCounter extends React.Component {
constructor(props) {
super(props);
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(({counter}) => ({
counter: counter + 1
}));
}
render() {
if (this.state.counter === 5) {
// Simulate a JS error
throw new Error('I crashed!');
}
return <h1 onClick={this.handleClick}>{this.state.counter}</h1>;
}
}
function App() {
return (
<div>
<p>
<b>
This is an example of error boundaries in React 16.
<br /><br />
Click on the numbers to increase the counters.
<br />
The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component.
</b>
</p>
<hr />
<ErrorBoundary>
<p>These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.</p>
<BuggyCounter />
<BuggyCounter />
</ErrorBoundary>
<hr />
<p>These two counters are each inside of their own error boundary. So if one crashes, the other is not affected.</p>
<ErrorBoundary><BuggyCounter /></ErrorBoundary>
<ErrorBoundary><BuggyCounter /></ErrorBoundary>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
2、未捕获错误(Uncaught Errors)的新行为
自 React 16 起,任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载。
把一个错误的 UI 留在那比完全移除它要更糟糕。例如,在类似 Messenger 的产品中,把一个异常的 UI 展示给用户可能会导致用户将信息错发给别人。同样,对于支付类应用而言,显示错误的金额也比不呈现任何内容更糟糕。
此变化意味着当你迁移到 React 16 时,你可能会发现一些已存在你应用中但未曾注意到的崩溃。增加错误边界能够让你在应用发生异常时提供更好的用户体验。
例如,Facebook Messenger 将侧边栏、信息面板、聊天记录以及信息输入框包装在单独的错误边界中。如果其中的某些 UI 组件崩溃,其余部分仍然能够交互。
3、组件栈追踪
React 16 会把渲染期间发生的所有错误打印到控制台,即使该应用意外的将这些错误掩盖。除了错误信息和 JavaScript 栈外,React 16 还提供了组件栈追踪。可以准确地查看发生在组件树内的错误信息:
可以在组件栈追踪中查看文件名和行号,这一功能在 Create React App 项目中默认开启
没有使用 Create React App创建React应用,可以手动将该插件添加到你的 Babel 配置中。注意它仅用于开发环境,在生产环境必须将其禁用 。
注意 组件名称在栈追踪中的显示依赖于
[Function.name](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name)
属性。如果想要支持尚未提供该功能的旧版浏览器和设备(例如 IE 11),考虑在打包(bundled)应用程序中包含一个Function.name
的 polyfill,如[function.name-polyfill](https://github.com/JamesMGreene/Function.name)
。或者,可以在所有组件上显式设置[displayName](https://zh-hans.reactjs.org/docs/react-component.html#displayname)
属性。
4、关于 try/catch
try
/ catch
用于命令式代码(imperative code)
try {
showButton();
} catch (error) {
// ...
}
React 组件需要声明式的并且具体指出 什么 需要被渲染
<Button />
错误边界保留了 React 的声明性质。例如,即使一个错误发生在 componentDidUpdate
方法中,并且由某一个深层组件树的 setState
引起,其仍然能够冒泡到最近的错误边界。
5、关于事件处理器
错误边界无法捕获事件处理器内部的错误。
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>
}
}
6、 ErrorBoundary
处理 Reac.lazy
懒加载组件的使用
如果模块加载失败(如网络问题),它会触发一个错误。可以通过异常捕获边界(Error boundaries)技术来处理这些情况,以显示良好的用户体验并管理恢复事宜。
import MyErrorBoundary from './MyErrorBoundary';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
const MyComponent = () => (
<div>
<MyErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</MyErrorBoundary>
</div>
);
7、React16对异常处理方法名的更改
React 15 中有一个支持有限的错误边界方法 unstable_handleError
。此方法不再起作用,同时自 React 16 beta 发布起需要在代码中将其修改为 componentDidCatch
。