錯誤邊界介紹
部分 UI 中的 JavaScript 錯誤不應該破壞整個應用程序涤浇。 為了解決 React 用戶的這個問題燃观,React 16引入了一個 “錯誤邊界(Error Boundaries)” 的新概念铅协。
錯誤邊界是 React 組件,它可以在子組件樹的任何位置捕獲 JavaScript 錯誤,記錄這些錯誤,并顯示一個備用 UI 割坠,而不是使整個組件樹崩潰。 錯誤邊界(Error Boundaries) 在渲染妒牙,生命周期方法以及整個組件樹下的構造函數(shù)中捕獲錯誤彼哼。
使用方法
如果一個類組件定義了生命周期方法中的任何一個(或兩個)static getDerivedStateFromError()
或 componentDidCatch()
,那么它就成了一個錯誤邊界湘今。 使用static getDerivedStateFromError()
在拋出錯誤后渲染回退UI敢朱。 使用 componentDidCatch()
來記錄錯誤信息。
捕獲范圍
組件內(nèi)異常,也就是異常邊界組件能夠捕獲的異常拴签,主要包括:
- 渲染過程中異常孝常;
- 生命周期方法中的異常;
- 子組件樹中各組件的constructor構造函數(shù)中異常蚓哩。
不能捕獲的異常构灸,主要是異步及服務端觸發(fā)異常:
- 事件處理器中的異常;
處理方法: 使用try/catch代碼進行捕獲
- 異步任務異常杖剪,如setTiemout冻押,ajax請求異常等;
處理方法:使用全局事件window.addEventListener捕獲
- 服務端渲染異常盛嘿;
- 異常邊界組件自身內(nèi)的異常洛巢;
處理方法:將邊界組件和業(yè)務組件分離,各司其職次兆,不能在邊界組件中處理邏輯代碼稿茉,也不能在業(yè)務組件中使用didcatch
錯誤邊界盡可以捕獲其子組件的錯誤,無法捕獲其自身的錯誤芥炭;如果一個錯誤邊界無法渲染錯誤信息漓库,則錯誤會向上冒泡至最接近的錯誤邊界。這也類似于 JavaScript 中 catch {} 的工作機制
如何放置錯誤邊界
錯誤邊界的粒度完全取決于你的應用园蝠。你可以將其包裝在最頂層的路由組件并為用戶展示一個 “發(fā)生異常(Something went wrong)“的錯誤信息渺蒿,就像服務端框架通常處理崩潰一樣。你也可以將單獨的插件包裝在錯誤邊界內(nèi)部以保護應用不受該組件崩潰的影響彪薛。
借鑒Facebook的message項目茂装,他們應用錯誤邊界的方式是將大的模塊應用錯誤邊界包裹,這樣當一個主要模塊因為意外的錯誤崩潰后善延,其它組件仍然能夠正常交互
錯誤邊界實戰(zhàn)
首先我定義了一個高階組件
import React from 'react'
const ErrorBoundary = errorInfo => WrapComponent => {
return class ErrorBoundary extends React.Component{
constructor(props) {
super(props);
this.state = { hasError: false };
}
// 這個靜態(tài)方法和componentDidCatch方法定義一個即可
static getDerivedStateFromError(error) {
// 當發(fā)生錯誤時少态,設置hasError為true,然后展示自己的錯誤提示組件
return { hasError: true };
}
componentDidCatch(error, info) {
// 這里可以將報錯信息上報給自己的服務
// logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
return <h1>{errorInfo}</h1>;
}
return <WrapComponent/>;
}
}
}
export default ErrorBoundary
接下來可以使用邊界組件包裹業(yè)務組件易遣,這里列舉我認為react項目中可以處理的錯誤方式彼妻,例如事件處理器的錯誤,異步錯誤豆茫,promise錯誤侨歉,渲染錯誤等
import React from 'react'
import ErrorBoundary from '../../utils/ErrorBoundary'
@ErrorBoundary('i am not ok')
export default class Error extends React.Component{
constructor() {
super()
}
componentWillMount() {
window.addEventListener('error', event => {
console.log(event)
}, true)
window.addEventListener('unhandledrejection', event => {
console.log(event)
})
}
// 這個異步錯誤 ErrorBoundary組件不會捕獲到 但是在入口寫的全局window.onerror事件捕獲到了
componentDidMount() {
setTimeout(() => {
// console.log(b)
}, 100)
}
// 事件處理器中的錯誤 onerror也可以捕獲到
// 這里如果想要hold住錯誤 需要使用try catch
handleEventError = () => {
console.log(error)
}
// promise 如果reject 但是沒有寫catch語句的話 會報錯
// 但是onerror和try-catch和ErrorBoundary組件都無法捕獲
// 需要寫一個全局unhandledrejection 事件捕獲
handlePromiseError = () => {
const promise = new Promise((resolve, reject) => {
reject()
})
promise.then()
}
render() {
return <div>
<div>hi i am fine</div>
<button onClick={this.handleEventError}>handle event error</button>
<button onClick={this.handlePromiseError}>handle promise error</button>
</div>
}
}