FE-wuhao / TrivialKnowledge

琐碎的知识点,单开一个仓库
2 stars 1 forks source link

React错误边界处理 #15

Open FE-wuhao opened 4 years ago

FE-wuhao commented 4 years ago

错误边界的作用

FE-wuhao commented 4 years ago

window.onerror

作用


**分析:**
window.onerror捕获当前错误的各种信息,报错错误内容,错误发生所在行等等,同时返回值是一个布尔值,true代表在console中打印,false代表不打印
FE-wuhao commented 4 years ago

React提供的ErrorBoundary

作用

class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; }

static getDerivedStateFromError(error) { message.error(error); return { hasError: true }; }

componentDidCatch(error, errorInfo) { message.error(error); message.error(errorInfo); }

render() { if (this.state.hasError) { return (

页面开了会儿小差...

    </div>
  );
}

return this.props.children;

} }

export default ErrorBoundary;

**分析:** 上面的代码可以分为三块,下面逐块分析

***

 ```js
  static getDerivedStateFromError(error) {
    message.error(error);
    return { hasError: true };
  }

这是react提供的一个特定的生命周期方法,用来渲染备用UI。此处的hasError是ErrorBoundary组件内的一个state,如上所示,通过设定它的值为true来切换备用UI


  componentDidCatch(error, errorInfo) {
    message.error(error);
    message.error(errorInfo);
  }

这是react提供的另一个声明周期方法,用来打印错误信息。如上图所示,通过message或者console将当前捕获的错误信息打印出来


if (this.state.hasError) {
      return (
        <div className={styles.container}>
          <h1>页面开了会儿小差...</h1>
          <div className={styles['btn-wrapper']}>
            <div className={styles.btn}>
              <Button
                type="primary"
                onClick={() => {
                  history.push('/');
                }}
              >
                返回主页
              </Button>
            </div>
          </div>
        </div>
      );
    }

    return this.props.children;

这里就是备用UI了。根据hasError的状态,如果为true说明捕获到错误了,显示准备好的UI;如果为false,则正常显示ErrorBoundary包裹的子组件,值得一提的是,ErrorBoundary组件写好后,一般作为最顶层组件,包裹我们的业务组件,具体代码如下所示:

<ErrorBoundary>
  <div>这里是实际业务组件</div>
</ErrorBoundary>
FE-wuhao commented 4 years ago

React不可捕获的异常测试

前置知识:

在开发环境下,如果报错会弹出一个错误提示页面,以帮助开发者发现问题解决问题,如下图所示: image 拉到最下面,可以看到这样一串文字:

This screen is visible only in development. It will not appear if the app crashes in production. Open your browser’s developer console to further inspect this error. Click the 'X' or hit ESC to dismiss this message.

他告诉我们可以通过点击右上角的X关掉这个提示,显示出页面崩溃的真实模样。

image

明白这一点才可以正常进行我们的测试,下面正式开始测试

测试手段:

直接通过在代码中写入throw new Error('测试错误边界');来进行异常抛出

公用的错误边界组件:

import React from 'react';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    console.log(error);
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.log(error);
    console.log(errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>页面开了会儿小差...</h1>;
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

备用UI:

image

测试项目:

  1. 事件处理函数内的错误
// 顶层组件
import React from 'react';
import ErrorBoundary from './component/ErrorBoundary/index'
import ErrorBoundaryTest from './pages/ErrorBoundary/Child/index'

function App() {
  return (
    <ErrorBoundary>
      <ErrorBoundaryTest />
    </ErrorBoundary>

  );
}

export default App;
// 测试组件
import React from 'react';

function ErrorBoundaryTest() {

  return (
      <div onClick={() =>{
      throw new Error('测试错误边界');// 在这里,div的onClick事件中抛出异常
      }}>这里是子组件 </div> 
  );
}

export default ErrorBoundaryTest;

结果图: image image

分析: 由上两张结果图可以看出,点击div确实触发了onClick时间并抛出了异常,但是并没有显示备用UI,即点击了错误页面右上角的X以后,并没有显示“页面开了会儿小差...”。由此可以看出,React的ErrorBoundary解决方案确实无法应对事件处理函数内的错误

  1. 异步代码中的错误

顶层代码相同,不再重复书写

// 测试组件
import React from 'react';

function ErrorBoundaryTest() {
  setTimeout(() => { // 在这里写好定时,定5S后自动抛出异常
  throw new Error('测试错误边界');
}, 5000)

  return (
      <div onClick={() =>{
      // throw new Error('测试错误边界');
      }}>这里是子组件 </div> 
  );
}

export default ErrorBoundaryTest;

结果图: image image

分析: 经过五秒的定时,同样抛出了异常,但同样ErrorBoundary并未捕获到。

  1. 错误边界代码自身的错误 公共ErrorBoundary组件做出一点点修改,改为该组件自身抛出异常。
    
    // 公共ErrorBoundary组件
    import React from 'react';

class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; }

static getDerivedStateFromError(error) { console.log(error); return { hasError: true }; }

componentDidCatch(error, errorInfo) { console.log(error); console.log(errorInfo); }

render() { throw new Error('测试错误边界'); // 在这里让错误边界自身发生错误 if (this.state.hasError) { return

页面开了会儿小差...

; }

return this.props.children;

} }

export default ErrorBoundary;

```js
// 顶层组件
import React from 'react';
import ErrorBoundary from './component/ErrorBoundary/index'
import ErrorBoundaryTest from './pages/ErrorBoundary/Child/index'

function App() {
  return (
    <ErrorBoundary>
      <ErrorBoundaryTest />
    </ErrorBoundary>

  );
}

export default App;
// 子组件
import React from 'react';

function ErrorBoundaryTest() {
//   setTimeout(() => {
//   throw new Error('测试错误边界');
// }, 5000)

  return (
      <div onClick={() =>{
      // throw new Error('测试错误边界');
      }}>这里是子组件 </div> 
  );
}

export default ErrorBoundaryTest;

结果图: image image 分析: 一如既往,抛出了异常但并未显示备用UI,不同的是,这次异常直接引发了白屏。

FE-wuhao commented 4 years ago

总结:

经过上面缜密的测试,确实发现react错误边界的捕获范围有限。想要完整的捕获异常,需同时辅以window.onerror,捕获到所有的错误并进行相应处理。

jiangminbai commented 3 years ago

点赞