大家好,我卡頌。
本文會講解React
中Error Boundaries
的完整實現邏輯。
一張圖概括:
這裡簡單講解下React
工作流程,後文有用。分為三步:
- 觸發
更新
- render階段:計算
更新
會造成的副作用
- commit階段:在宿主環境執行
副作用
副作用
有很多,比如:
- 插入
DOM
節點 - 執行
useEffect
回撥
好了,讓我們進入主題。
歡迎加入人類高質量前端框架群,帶飛
什麼是Error Boundaries
React
提供了兩個與錯誤處理相關的API
:
getDerivedStateFromError
:靜態方法,當錯誤發生後提供一個機會渲染fallback UI
componentDidCatch
:元件例項方法,當錯誤發生後提供一個機會記錄錯誤資訊
使用了這兩個API
的ClassComponent
通常被稱為Error Boundaries
(錯誤邊界)。
在Error Boundaries
的子孫元件中發生的所有React工作流程內的錯誤都會被Error Boundaries
捕獲。
通過開篇的介紹可以知道,React工作流程指:
- render階段
- commit階段
考慮如下程式碼:
class ErrorBoundary extends Component {
componentDidCatch(e) {
console.warn(“發生錯誤”, e);
}
render() {
return <div>{this.props.children}</div>;
}
}
const App = () => (
<ErrorBoundary>
<A><B/></A>
<C/>
<ErrorBoundary>
)
A
、B
、C
作為ErrorBoundary
的子孫元件,當發生React工作流程內的錯誤,都會被ErrorBoundary
中的componentDidCatch
方法捕獲。
步驟1:捕獲錯誤
首先來看工作流程中的錯誤都是何時被捕獲的。
render
階段的核心程式碼如下,發生的錯誤會被handleError
處理:
do {
try {
// 對於併發更新則是workLoopConcurrent
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
commit
階段包含很多工作,比如:
componentDidMount/Update
執行- 繫結/解綁
ref
useEffect/useLayoutEffect
callback
與destroy
執行
這些工作會以如下形式執行,發生的錯誤被captureCommitPhaseError
處理:
try {
// …執行某項工作
} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}
步驟2:構造callback
可以發現,即使沒有Error Boundaries
,工作流程中的錯誤已經被React
捕獲了。而正確的邏輯應該是:
- 如果存在
Error Boundaries
,執行對應API
- 丟擲
React
的提示資訊 - 如果不存在
Error Boundaries
,丟擲未捕獲的錯誤
所以,不管是handleError
還是captureCommitPhaseError
,都會從發生錯誤的節點的父節點開始,逐層向上遍歷,尋找最近的Error Boundaries
。
一旦找到,就會構造:
- 用於執行Error Boundaries API的
callback
- 用於丟擲React提示資訊的
callback
// ...為了可讀性,邏輯有刪減
function createClassErrorUpdate() {
if (typeof getDerivedStateFromError === 'function') {
// 用於執行getDerivedStateFromError的callback
update.payload = () => {
return getDerivedStateFromError(error);
};
// 用於丟擲React提示資訊的callback
update.callback = () => {
logCapturedError(fiber, errorInfo);
};
}
if (inst !== null && typeof inst.componentDidCatch === 'function') {
// 用於執行componentDidCatch的callback
update.callback = function callback() {
this.componentDidCatch(error);
};
}
return update;
}
如果沒有找到Error Boundaries
,繼續向上遍歷直到根節點。
此時會構造:
- 用於丟擲未捕獲錯誤的
callback
- 用於丟擲React提示資訊的
callback
// ...為了可讀性,邏輯有刪減
funffction createRootErrorUpdate() {
// 用於丟擲“未捕獲的錯誤”及“React的提示資訊”的callback
update.callback = () => {
onUncaughtError(error);
logCapturedError(fiber, errorInfo);
};
return update;
}
執行callback
構造好的callback
在什麼時候執行呢?
在React
中有兩個執行使用者自定義callback的API
:
- 對於
ClassComponent
,this.setState(newState, callback)
中newState
和callback
引數都能傳遞Function
作為callback
所以,對於Error Boundaries
,相當於主動觸發了一次更新:
this.setState(() => {
// 用於執行getDerivedStateFromError的callback
}, () => {
// 用於執行componentDidCatch的callback
// 以及 用於丟擲React提示資訊的callback
})
- 對於根節點,執行
ReactDOM.render(element, container, callback)
中callback
引數能傳遞Function
作為callback
所以,對於沒有Error Boundaries的情況,相當於主動執行了如下函式:
ReactDOM.render(element, container, () => {
// 用於丟擲“未捕獲的錯誤”及“React的提示資訊”的callback
})
所以,Error Boundaries
的實現可以看作是:React
利用已有API
實現的新功能。
總結
經常有人問:為什麼Hooks
沒有Error Boundaries
?
可以看到,Error Boundaries
的實現藉助了this.setState
可以傳遞callback
的特性,useState
暫時無法完全對標。
最後,給你留個作業,在官方文件介紹了4種情況的錯誤不會被Error Boundaries
捕獲。
利用本文知識,你能分析下他們為什麼不會被捕獲麼?