在使用Ant Design Pro開發時,若是是組件渲染出錯,生產環境下會直接致使整個頁面白屏,形成了很是差的用戶體驗。通常來講,當頁面出錯時,提示這個頁面出錯就好了,左邊的菜單欄應該還要可以正常使用,這樣的用戶體驗會好一些。html
可是組件渲染時因爲不能在父組件使用try...catch捕獲,所以一直是個比較難處理的問題。React 16引入了「錯誤邊界(Error Boundaries)
」之後,如今能夠優雅地處理這個問題,達到上面說的效果。react
經過錯誤邊界處理之後,渲染組件錯誤後的效果圖:
git
根據官網介紹:github
「錯誤邊界是一種 React 組件,這種組件能夠捕獲並打印發生在其子組件樹任何位置的 JavaScript 錯誤,而且,它會渲染出備用 UI,而不是渲染那些崩潰了的子組件樹。錯誤邊界在渲染期間、生命週期方法和整個組件樹的構造函數中捕獲錯誤。」antd
這個聽起來有點拗口,簡單說,只要在組件中定義static getDerivedStateFromError()
或componentDidCatch()
,這個組件就是一個錯誤邊界。當它的子組件出錯時,這個組件能夠感知到而後根據實際狀況處理,防止整個組件樹直接崩潰。函數
static getDerivedStateFromError()
的使用場景是渲染備用UI(就是本文的應用場景)
componentDidCatch()
的使用場景是打印/記錄錯誤信息(好比發送到sentry等BUG記錄工具,本文沒用到)工具
下面咱們結合具體的代碼,給Antd Pro的基礎排版組件src/layouts/BasicLayout.jsx
增長錯誤邊界的處理,當具體頁面出現錯誤時,提示用戶出錯,左邊菜單還能繼續使用。
咱們使用的版本是Ant Design Pro v4,BasicLayout.jsx已經使用函數式組件實現,可是邊界處理目還不能經過React Hook去作,所以仍是要先改回類組件的實現方式。代碼以下:測試
// src/layouts/BasicLayout.jsx // ...省略無關代碼,改回類組件 class BasicLayout extends React.Component { componentDidMount() { const { dispatch } = this.props; if (dispatch) { dispatch({ type: 'user/fetchCurrent', }); } } handleMenuCollapse = payload => { const { dispatch } = this.props; if (dispatch) { dispatch({ type: 'global/changeLayoutCollapsed', payload, }); } }; // get children authority render () { const { dispatch, children, settings, location = { pathname: '/', }, } = this.props; const props = this.props; const authorized = getAuthorityFromRouter(props.route.routes, location.pathname || '/') || { authority: undefined, }; return (<> <ProLayout logo={logo} menuHeaderRender={(logoDom, titleDom) => ( <Link to="/"> {logoDom} {titleDom} </Link> )} onCollapse={this.handleMenuCollapse} menuItemRender={(menuItemProps, defaultDom) => { if (menuItemProps.isUrl || menuItemProps.children) { return defaultDom; } return <Link to={menuItemProps.path}>{defaultDom}</Link>; }} breadcrumbRender={(routers = []) => [ { path: '/', breadcrumbName: formatMessage({ id: 'menu.home', defaultMessage: 'Home', }), }, ...routers, ]} itemRender={(route, params, routes, paths) => { const first = routes.indexOf(route) === 0; return first ? ( <Link to={paths.join('/')}>{route.breadcrumbName}</Link> ) : ( <span>{route.breadcrumbName}</span> ); }} footerRender={footerRender} menuDataRender={menuDataRender} formatMessage={formatMessage} rightContentRender={rightProps => <RightContent {...rightProps} />} {...props} {...settings} > <Authorized authority={authorized.authority} noMatch={noMatch}> {children} </Authorized> </ProLayout> <SettingDrawer settings={settings} onSettingChange={config => dispatch({ type: 'settings/changeSetting', payload: config, }) } /> </> ); } };
而後在這個代碼基礎上,增長錯誤邊界的處理代碼:fetch
class BasicLayout extends React.Component { constructor(props) { super(props); // 默認沒有錯誤 this.state = { hasError: false }; } // 增長錯誤邊界代碼,當發生錯誤時,state中的hasError會變成true static getDerivedStateFromError() { return { hasError: true }; } render () { const { hasError } = this.state; return (<> {/* 省略無關代碼 */} <ProLayout > <Authorized authority={authorized.authority} noMatch={noMatch}> {/* 出現錯誤的時候,渲染錯誤提示組件 */} {hasError ? <Result status="error" title="程序發生錯誤,請反饋給服務提供商" /> : children} </Authorized> </ProLayout> <>) } }
完整的代碼文件點擊這裏下載this
https://raw.githubusercontent.com/pheye/shopify-admin/master/src/layouts/BasicLayout.jsx
咱們以官方提供的示例工程爲例,直接讓分析頁面出錯,在render()
裏面,加一句throw new Error('渲染出錯');
,就能看到渲染出錯時的效果了。
須要注意:錯誤邊界僅能夠捕獲其子組件的錯誤,沒法捕獲自身的錯誤。
所以src/layouts/BasicLayout.js
中的錯誤邊界,能夠確保頁面出錯時左邊的菜單欄仍是正常工做,可是若是是BasicLayout.js
自己的側邊欄或者頭部出錯也同樣會白屏。src/layouts
的其餘文件沒有加錯誤邊界,出錯也還會白屏。要解決這個問題,能夠對根組件作一層組裝,增長邊界處理,給用戶更友好的提示。
可是對根組件的處理仍是替代不了單獨對BasicLayout.js
增長邊界處理,由於咱們但願出錯之後菜單欄還要可以正常使用。
最好的錯誤邊界處理策略是根組件提供一個統一的錯誤處理,不一樣的排版組件提示根據排版提供更友好的錯誤處理。