渲染错误
React在render过程中发生错误且未被捕获,会导致js树构建失败,React会将UI从屏幕中移除,如果不处理会出现白屏。
ErrorBoundary
1.什么是ErrorBoundary
React官方文档中定义了一个名为ErrorBoundary的概念,它是一种特殊的组件,用于显示一个渲染失败的UI来代替页面崩溃(白屏)。
ErrorBoundary的颗粒度是可以控制的,可以整个页面用一个ErrorBoundary包裹,出现崩溃时直接显示一个错误页面;也可以针对部分组件,当这部分组件的渲染崩溃时渲染降级的UI部分,而不影响页面整体的渲染。
2.如何编写一个ErrorBoundary
React官方文档中的例子已经是一个可用、能复用的ErrorBoundary组件了,根据自己的业务场景稍加修改即可:
import React from 'react';
export default class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, errorMsg: '' };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true, errorMsg: error.message };
}
componentDidCatch(error, info) {
// Example "componentStack":
// in ComponentThatThrows (created by App)
// in ErrorBoundary (created by App)
// in div (created by App)
// in App
console.log(error, info.componentStack);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return this.props.fallback || <div>{this.state.errorMsg}</div>;
}
return this.props.children;
}
}
值得一提的是:编写一个ErrorBoundary必须使用class组件,这是class组件和function组件在能力边界上的少数区别之一。
3.如何使用ErrorBoundary
上报错误
捕获到错误后不仅可以降级渲染ui, 还可以通过componentDidCatch上报错误,作为前端页面监控的一部分。
componentDidCatch(error, info) {
console.log(error, info.componentStack);
// 上报错误
window.reporter.report(error ,info)
}
页面级使用
全局只使用一个ErrorBoundary,当出现错误时,直接展示降级后的错误页面(FailedMain)
function App() {
return (
<ErrorBoundary fallback={<FailedMain/>}>
<Main />
</ErrorBoundary>
)
}
这里的fallback也可以改成传入一个渲染函数,这样在ErrorBoundary内部调用时可以接收错误信息作为参数。
function App() {
return (
<ErrorBoundary fallback={props => <FailedMain {...props} />}>
<Main />
</ErrorBoundary>
)
}
组件级使用
想象一个场景,我们有一个内容非常多的页面,页面可以分为n个子模块且互相独立,我们不希望一个子模块的崩溃导致整个页面都降级渲染,这会导致其他的正常模块也不可用。
// 这是一个用户订单页面的结构,Flight,Hotel, Ticket都是包含很多内容和功能的子模块。
// 这时候我们不希望Flight组件的崩溃导致其他的子模块不可用。
function App() {
return (
<>
<ErrorBoundary>
<Header />
</ErrorBoundary>
<ErrorBoundary>
<Flight />
</ErrorBoundary>
<ErrorBoundary>
<Hotel />
</ErrorBoundary>
<ErrorBoundary>
<Ticket />
</ErrorBoundary>
</>
)
}
上述的例子可以使用高阶组件封装,这样ui结构上会更加简单清晰。