在 React 16 版本後新增錯誤邊界 (Error Boundary) 的概念,新加入 componentDidCatch 組件生命週期方法來捕捉「子組件」錯誤,當邊界內層子組件發生不預期錯誤時,不會影響到邊界外層父組件;當然也會有一些限制存在,本篇文章來針對 Error Boundary 進行說明。
前言
以往對於組件生成的錯誤處理多為 try catch 方式,但在組件生成的過程會經歷了許多生命週期方法,若想要全面性處理異常錯誤,往往帶來的負面效果就是代碼充斥著錯誤捕捉的結構,沒有一個比較恰當的方式來處理;但在 React 16 發布後新增了 componentDidCatch 組件生命週期方法,讓開發人員可以於此統一處理子組件的異常錯誤。以下介紹。
錯誤邊界 (Error Boundary)
想要成為一個 Error Boundary 的方式相當簡單,只要在組件中加入 componentDidCatch 方法就成為 Error Boundary 組件,當「子組件」發生錯誤時就會被這個 Error Boundary 捕捉,不會讓錯誤影響到錯誤邊界外的任何組件;另外,開發人員可透過 error 與 info 參數記錄錯誤的相關資訊,對於錯誤釐清有很大的幫助。
class ErrorBoundary extends React.Component {
// ...
componentDidCatch (error, info) {
// 子組件發生錯誤!!
// 可透過 state 控制錯誤發生的畫面呈現樣貌
this.setState({ hasError: true })
// 可利用 error 與 info 記錄下錯誤資訊
console.error(error, info)
}
// ...
}
限制項目
請特別注意 Error Boundary 並不是所有錯誤都會被捕捉,相關限制及特性如下:
- 只能捕捉「子」組件錯誤,邊界本身錯誤無法被捕捉。
- 只能捕捉子組件「渲染」、「建構子」及「各生命週期方法」中的錯誤。
- 牽涉到非同步執行 (ex. setTimeout, callback ) 中的錯誤無法被捕捉。
- 無法捕捉 event handler 中發生的錯誤。
實作驗證
驗證首先目標就是建立出用來發出錯誤的炸彈組件,接著依照官方文件建立 Error Boundary 並以此區隔這些炸彈組件,以此理解當錯誤時的處置方式。
炸彈組件
首先建立以產生渲染錯誤的 TNTBomp 組件,當點選「爆炸」按鈕後隨即拋出錯誤,後續將以此來測試錯誤發生時能否被 Error Boundary 所捕捉;另外,產生 Box 組件來模擬一般組件,就直接在頁面輸出色塊樣式囉。
import React from 'react'
import styled from 'styled-components'
class TNTBomp extends React.Component {
constructor (props) {
super(props)
this.state = { isExplode: false }
}
handleClick = () => {
// 從這邊拋出去的錯誤不會被 ErrorBoundary 捕捉
// 請使用 try catch 來處理這類錯誤
this.setState(state => ({ ...state, isExplode: true }))
}
render () {
if (this.state.isExplode) {
throw new Error('I crashed!')
}
return <button type='button' className='btn btn-danger' onClick={this.handleClick}>
爆炸!!
</button>
}
}
const Box = styled.div`
padding: 5px 5px 5px 5px;
margin-bottom: 5px;
border: solid;
border-width: 1px;
background: ${props => props.bkColor};
`
export default () => {
return <React.Fragment>
{/* React 16.2 新增 React.Fragment 空白標籤 */}
<h2>Error Boundaries</h2>
<hr />
{/* bomp without error boundary */}
<Box bkColor='#afd3ff'>
<TNTBomp />
</Box>
</React.Fragment>
}
執行後畫面上就會出現一顆 TNTBomp 炸彈埋在藍色 Box 中。
引爆後發現整頁變空白,這樣的用戶體驗也太慘烈了;主要是因為在 React 16 後為了保持頁面完整性,避免組件錯誤後繼續操作而造成邏輯上的麻煩,因此若組件生成錯誤沒有被任何 Error Boundary 捕捉,就會將整個 component tree 卸掉變成空白頁。
使用 Error Boundary 包裹炸彈
直接建立一個 ErrorBoundary 組件,當錯誤發生時會在頁面顯示「被 ErrorBoundary 擋住錯誤」文字,並且將剛剛產生的炸彈給包裹起來;另外,為了在畫面能比較清楚地呈現,就給他個紅色虛線 border 樣式來作標記。
// ...
const ErrorBoundaryBox = styled.div`
padding: 5px 5px 0px 5px;
margin-bottom: 5px;
border: dashed;
border-color: coral;
border-width: 3px;
background: ${props => props.bkColor};
`
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
console.error(error, info)
}
render () {
return <ErrorBoundaryBox>
{this.state.hasError
? <h4>被 ErrorBoundary 擋住錯誤啦!!</h4>
: this.props.children}
</ErrorBoundaryBox>
}
}
export default () => {
return <React.Fragment>
{/* React 16.2 新增 React.Fragment 空白標籤 */}
<h2>Error Boundaries</h2>
<hr />
{/* bomp with error boundary */}
<ErrorBoundary>
<Box bkColor='#afd3ff'>
<TNTBomp />
</Box>
</ErrorBoundary>
</React.Fragment>
}
輸出在畫面上後,紅色虛線的部分就是 Error Boundary 組件區塊,內含一個炸彈組件。
引爆後錯誤就被 Error Boundary 所捕捉,不再影響整體網站的顯示。
巢狀 Error Boundary
接著來觀察一下 Error Boundary 作用域,以下定義一些巢狀 Error Boundary 來包裹炸彈。
// ...
export default () => {
return <React.Fragment>
{/* React.Fragment 為 React 16.2 新增 React.Fragment 空白標籤 */}
<h2>Error Boundaries</h2>
<hr />
{/* bomp in error boundary */}
<ErrorBoundary>
<Box bkColor='#afd3ff'>
<TNTBomp />
</Box>
<Box bkColor='#afd3ff'>
<TNTBomp />
</Box>
{/* catch by the first parent error boundary */}
<ErrorBoundary>
<Box bkColor='#afd3ff'>
<TNTBomp />
</Box>
</ErrorBoundary>
{/* bomp in nested components still can be catched */}
<ErrorBoundary>
<Box bkColor='#afd3ff'>
<Box bkColor='#c1f8aa'>
<TNTBomp />
</Box>
</Box>
</ErrorBoundary>
</ErrorBoundary>
</React.Fragment>
}
從畫面上可以看到炸彈在不同組件中,並且被各自 Error Boundary 所包裹起來。
各自引爆後可以發現錯誤會被最接近自己的 Error Boundary 給捕捉,因此可以利用這特性依據組件顆粒度大小來制定合適的錯誤邊界;例如頁面中若同時需要顯示許多獨立性的資訊區塊,我們就可以為這些獨立區塊建立各自的 Error Boundary 組件,這樣就算發生錯誤也不會影響到彼此資訊的呈現。
參考資訊
React - Error Handling in React 16
React新特性——Protals与Error Boundaries
若有更好的建議或做法再請不吝指導一下囉! 感謝!
希望此篇文章可以幫助到需要的人
若內容有誤或有其他建議請不吝留言給筆者喔 !