背景 在使用Ant Design Pro開發時,如果是組件渲染出錯,生產環境下會直接導致整個頁面白屏,造成了非常差的用戶體驗。一般來說,當頁面出錯時,提示這個頁面出錯就行了,左邊的菜單欄應該還要能夠正常使用,這樣的用戶體驗會好一些。 但是組件渲染時由於不能在父組件使用try...catch捕獲,因此 ...
背景
在使用Ant Design Pro開發時,如果是組件渲染出錯,生產環境下會直接導致整個頁面白屏,造成了非常差的用戶體驗。一般來說,當頁面出錯時,提示這個頁面出錯就行了,左邊的菜單欄應該還要能夠正常使用,這樣的用戶體驗會好一些。
但是組件渲染時由於不能在父組件使用try...catch捕獲,因此一直是個比較難處理的問題。React 16引入了“錯誤邊界(Error Boundaries)
”以後,現在可以優雅地處理這個問題,達到上面說的效果。
通過錯誤邊界處理以後,渲染組件錯誤後的效果圖:
錯誤邊界簡介
根據官網介紹:
“錯誤邊界是一種 React 組件,這種組件可以捕獲並列印發生在其子組件樹任何位置的 JavaScript 錯誤,並且,它會渲染出備用 UI,而不是渲染那些崩潰了的子組件樹。錯誤邊界在渲染期間、生命周期方法和整個組件樹的構造函數中捕獲錯誤。”
這個聽起來有點拗口,簡單說,只要在組件中定義static getDerivedStateFromError()
或componentDidCatch()
,這個組件就是一個錯誤邊界。當它的子組件出錯時,這個組件可以感知到然後根據實際情況處理,防止整個組件樹直接崩潰。
static getDerivedStateFromError()
的使用場景是渲染備用UI(就是本文的應用場景)
componentDidCatch()
的使用場景是列印/記錄錯誤信息(比如發送到sentry等BUG記錄工具,本文沒用到)
Antd解決白屏的實現方法
下麵我們結合具體的代碼,給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,
})
}
/>
</>
);
}
};
然後在這個代碼基礎上,增加錯誤邊界的處理代碼:
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>
<>)
}
}
完整的代碼文件點擊這裡下載
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
增加邊界處理,因為我們希望出錯以後菜單欄還要能夠正常使用。
最好的錯誤邊界處理策略是根組件提供一個統一的錯誤處理,不同的排版組件提示根據排版提供更友好的錯誤處理。