React 16 的異常/錯誤處理
本文翻譯自 React 官方博客:Error Handling in React 16
隨著 React 16 即將發布,我們打算介紹一些組件內部 React 如何處理 JavaScript 錯誤。這些改變會包含在 React 16 的 beta 版中,並將成為 React 16 的一部分。
順便一提,你可以嘗試我們剛發布了 React 16 的第一個 beta 版本!
React 15 及之前的行為
過去,組件內部的 JavaScript 異常經常阻斷 React 內部狀態,並導致其在下一次渲染時觸發了未知的隱藏錯誤。這些錯誤往往是由應用程序代碼中之前的錯誤引起的,但 React 並未提供一種在組件內部優雅處理的方式,也不會從異常中恢復。
相關鏈接:
- TypeError: Cannot read property "_currentElement" of null · Issue #4026 · facebook/react
- Error: performUpdateIfNecessary: Unexpected batch number ... · Issue #6895 · facebook/react
- Cannot read property "getHostNode" of null · Issue #8579 · facebook/react
錯誤邊界介紹
在 UI 部分發生的 JavaScript 異常不應該阻斷整個應用。為了解決這一問題,React 16 引入了「錯誤邊界(error boundary)」這一新概念。
錯誤邊界作為 React 組件,用以捕獲在子組件樹中任何地方的 JavaScript 異常,列印這些錯誤,並展示備用 UI 而非讓組件樹崩潰。錯誤邊界會捕獲渲染期間,在生命周期方法中以及在其整個樹的構造函數中的異常。
定義一個名稱為 componentDidCatch(error, info) 的新生命周期方法,則類組件會成為錯誤邊界:
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } componentDidCatch(error, info) { // Display fallback UI this.setState({ hasError: true }); // You can also log the error to an error reporting service logErrorToMyService(error, info); } render() { if (this.state.hasError) { // You can render any custom fallback UI return <h1>Something went wrong.</h1>; } return this.props.children; }}
而後可作為一個正常組件進行使用:
<ErrorBoundary> <MyWidget /></ErrorBoundary>
componentDidCatch() 方法的作用類似於 JavaScript 的 catch {},但僅針對組件。僅有類組件可以成為錯誤邊界。實際上,大多數時間你會想僅聲明一次錯誤邊界組件並在整個應用中使用。
注意,錯誤邊界僅可以捕獲組件樹中的後代組件錯誤。一個錯誤邊界無法捕獲其自身的錯誤。若錯誤邊界在渲染錯誤信息時失敗,則該錯誤會傳遞至上一層最接近的錯誤邊界。而這也類似與 JavaScript 中的 catch {} 塊的工作方式。
Live Demo
查看 React 16 beta 版 中 關於如何聲明和使用錯誤邊界的例子。
在哪裡放置錯誤邊界
錯誤邊界的粒度完全取決於你。你可能將其包裝在頂層路由組件中並為用戶展示「內部異常(Something went wrong)」的信息,類似於服務端框架處理崩潰的方式。你可能也會在錯誤邊界包裝一些內部組件用以保護你的應用,不會讓應用的餘下部分崩潰。
未捕獲錯誤的新行為
這一改變有一個重要的意義。在 React 16 中不是由錯誤邊界引起的錯誤將會使得整個 React 組件樹被卸載。
我們曾爭論過這一決定,但在我們的經驗中,將異常的 UI 留在那裡比完全移除它還要糟糕得多。例如,在類似 Messenger 這樣的產品中留下可見的異常的 UI 可能會導致一些人將信息發送給其它人。類似地,對於支付應用來說顯示錯誤的金額要比什麼都不顯示糟糕得多。
這一改變意味著遷移至 React 16,你會發現之前未留意過的應用程序存在的崩潰。添加錯誤邊界可以讓您在出現問題時提供更好的用戶體驗。
例如,Facebook Messenger 將邊欄,信息面板,會話日誌和消息輸入的內容包裝到單獨的錯誤邊界中。如果其中一個 UI 區域中的某些組件崩潰,其餘組件將保持互動。
我們也鼓勵你使用 JS 錯誤報告服務(或自己構建),以讓你能夠了解在生產環境中發生的未處理的異常,並修復它們。
組件棧追蹤
即使應用程序意外吞噬了這些異常,React 16 也會將渲染期間發生的所有錯誤都列印到控制台。除了錯誤消息和 JavaScript 堆棧之外,它還提供組件堆棧跟蹤。現在,您可以在組件樹中看到發生異常的位置:
你還可以在組件堆棧跟蹤中查看文件名和行號。默認情況下,在 創建反應」應用程序Create React App 項目中默認開啟:
若你不使用 Create React App,你可以手動添加 該插件(babel-plugin-transform-react-jsx-source) 到你的 Babel 配置中。注意這個插件僅能在開發環境中使用,在生產環境中要禁用這個插件。
為何不使用 try / catch?
try / catch 很棒,但它僅適用於命令式的代碼:
try { showButton();} catch (error) { // ...}
然而,React 組件是聲明式的,並指定哪些應該渲染:
<Button />
錯誤邊界保留了 React 聲明式的特性,同時其行為和你期望的一致。例如,即使在 componentDidUpdate 周期由組件樹內部底層的 setState 導致的錯誤,它仍能正確地傳遞到最近的錯誤邊界。
自 React 15 開始的命名變更
React 15 在不同的方法名下包含對錯誤邊界非常有限的支持: unstable_handleError。此方法不再適用,同時自 React 16beta 發布開始,你需要將其修改為 componentDidCatch。
對於此更改,我們提供了一份 codemod 來自動遷移您的代碼。
廣告時間,React 交流群:450586076
推薦閱讀:
※基於 Webpack 的應用包體尺寸優化
※React 實現一個漂亮的 Table
※React Conf 2017 不能錯過的大起底——Day 1!
※解析 Redux 源碼
※我對Flexbox布局模式的理解