• Ant Design Pro生产环境出错白屏的处理方法


    背景

    在使用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增加边界处理,因为我们希望出错以后菜单栏还要能够正常使用。
    最好的错误边界处理策略是根组件提供一个统一的错误处理,不同的排版组件提示根据排版提供更友好的错误处理。

    参考文档

    React错误边界
    Antd Pro根组件

  • 相关阅读:
    计网第一章——基本概念
    计网第二章——应用层
    命令行测试邮件发送工具mailsend-go
    CentOS-7-x86_64-DVD-2009 rpm包列表(centos7.9)
    CentOS-7-x86_64-Everything-2009 rpm包列表(CentOS7.9)
    Centos发行版ISO镜像中rpm包列表
    nginx使用记录
    centos resolv.conf
    python cookbook
    ansible中变量和主机名
  • 原文地址:https://www.cnblogs.com/pheye/p/12442252.html
Copyright © 2020-2023  润新知