Ant Design Pro生產環境出錯白屏的處理方法

背景

在使用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解決白屏的實現方法

下面咱們結合具體的代碼,給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增長邊界處理,由於咱們但願出錯之後菜單欄還要可以正常使用。
最好的錯誤邊界處理策略是根組件提供一個統一的錯誤處理,不一樣的排版組件提示根據排版提供更友好的錯誤處理。

參考文檔

React錯誤邊界
Antd Pro根組件

相關文章
相關標籤/搜索