上节课我们介绍了render props,这节课我们来介绍一个新的概念,错误边界。

回顾

在开始这节课的内容之前,我们对上节课的内容来做一个简单的回顾:

  • children props通过标签体传入结构
  • render props通过组件标签属性传入结构,通常用render函数属性

以上便是上节课的内容,接下来我们来开始这节课内容的学习

错误边界是什么

我们之前好像从来就没有提到过这么一个概念,这个错误边界到底是个什么呢?该怎么去理解呢?其实有其他编程语言基础的朋友应该都知道编程过程中有一个异常捕捉机制,这个错误边界也是类似的一个机制,用来捕获后代组件的错误,并且渲染备用页面。

引出错误边界

我们现在光用语言来描述了错误边界是个什么,但是我们还是不知道具体该怎么用,那么我们来通过一个案例来展示一下:

// Parent.jsx
export default class App extends Component {
  render() {
    return (
      <div>
        <Parent />
      </div>
    );
  }
}
// Child.jsx
export default class Child extends Component {
  state = {
    users: [
      { id: "001", name: "Jack", age: 18 },
      { id: "002", name: "Tom", age: 19 },
      { id: "003", name: "Bob", age: 20 },
    ]
  }
  render() {
    return (
      <div>
        <h2>Child Component</h2>
        {
          this.state.users.map(userObj => <h4 key={userObj.id}>{userObj.name}----{userObj.age}</h4>)
        }
      </div>
    )
  }
}

我们来看这段代码,我们的父组件是Parent组件,在该组件中渲染了Child组件。我们再来看看Child组件,该组件中我们设置了state,并且在组件中遍历展示了state到页面上。那么我们来看一下最终的结果是什么样子:

image-20220215123722768

我们砍价页面上展示出了我们组件中渲染的所有信息。但是我们到这一步呢,其实和错误边界还没有任何关系,因为我们没有出错,所以说没有错误边界这一概念出场的机会,但是实际开发中我们谁也不能保证说我们自己开发的组件一点错误都不会有。即便说我们开发的组件没有问题,如果后端返回过来的数据有问题的话是不是也会导致报错?

比如我们上面的Child组件,如果我们渲染的不是state,而是接收到的props,而后端返回的props是一个字符串的话,我们调用map方法的时候是不是一定会报错?那么我们该怎么处理呢?这时候我们是不是就要请出我们的错误边界了?

分析

那么怎么用呢?我们来分析一下,我们页面上第一个大标题是不是我们的父组件的内容?而下面的从Child Component开始是不是都是子组件的内容了?那么我们一个比较人性化的做法是不是让父组件正常展示,然后子组件如果没有出错就正常展示,否则则展示一句话来安抚一下用户,使得用户体验稍微友好一点?

所谓错误边界,首先有错误,其次有边界,什么意思呢,我们子组件出错了,那就只让子组件有问题,不要去影响其他组件,不能因为一个子组件出了问题导致整个页面所以组件都没法正常展示。

使用

那么我们应该怎么使用错误边界呢?首先我们要明确一点,如果一个组件代码报了错,那么这个组件还能不能被成功挂载?是不是不能了啊?那么我们能通过该组件的生命周期钩子来实现吗?是不是就不能了?那么唯一的解决办法是什么?是不是在这个组件的父组件中来做一些手脚?那么好,我们来修改一下代码,让Child组件故意翻一个错误:

export default class Child extends Component {
  state = {
    users: "abc"
  }
  render() {
    return (
      <div>
        <h2>Child Component</h2>
        {
          this.state.users.map(userObj => <h4 key={userObj.id}>{userObj.name}----{userObj.age}</h4>)
        }
      </div>
    )
  }
}

我们来看一下代码,我们让state中的users属性是一个字符串,这样的话我们在字符串上调用map方法一定是会报错的,那么我们看一下页面吧:

image-20220215125611467

第一,页面没有渲染出来,其次控制台报错。这是我们想要的结果吗?肯定不是啊,这一个组件影响了整个页面是一个很不好的状况,我们想要的结果是让Parent组件能够正常渲染,只是Child组件有问题,那么我们应该怎么处理呢?

export default class Parent extends Component {
  static getDerivedStateFromError(error) {
    console.log("The error is: ", error);
  }
  render() {
    return (
      <div>
        <h2>Parent Component</h2>
        <Child />
      </div>
    )
  }
}

我们说过,如果要对某个组件进行错误边界的限制,那么就一定要在这个组件的父组件中来做手脚。那么来看我们的父组件,我们在父组件中定义了一个静态方法。不知道大家记不记得有一个生命周期钩子叫getDerivedStateFromProps,这个钩子是通过Props来生成一个派生的状态,不过这次我们不是用这个钩子,而是用getDerivedStateFromError方法,这个方法是一个很特殊的方法,当这个组件的子组件报了错,就会马上调用这个方法,而且调用这个方法的时候会把报的错误传进来,那么我们来打印一下这个错误:

image-20220215131105847

我们可以看得出来,我们这个方法是成功调用了,但是并没有解决报错的问题。我们来回想一下我们在学生命周期的时候,在介绍生命周期钩子getDerivedStateFromProps的时候,是怎么说的?是不是要返回null或者一个状态对象啊?getDerivedStateFromError其实也一样,我们也要返回一个状态对象出来,那么我们来处理一下吧:

export default class Parent extends Component {
  state = { hasError: null }
  static getDerivedStateFromError(error) {
    return { hasError: error }
  }
  render() {
    return (
      <div>
        <h2>Parent Component</h2>
        {this.state.hasError ? <h2>Net Error</h2> : <Child />}
      </div>
    )
  }
}

我们来看一下代码,我们首先初始化了一个state,让hasError属性为空,然后再在getDerivedStateFromError方法中来返回一个新的状态对象,这样的话我们就可以通过state来判断子组件到底有没有出错,然后来决定要不要展示子组件。那么我们来看一下效果:

image-20220215132126548

我们来看,控制台中依然报Child组件中字符串不能用map的错,但是页面成功渲染了,并没有因为Child组件而使得整个页面都无法正常展示。

那么我们再来看一段代码:

export default class Parent extends Component {
  state = { hasError: null }
  static getDerivedStateFromError(error) {
    return { hasError: error }
  }
  componentDidCatch(error) {
    console.log("The error is: ", error);
  }
  render() {
    return (
      <div>
        <h2>Parent Component</h2>
        {this.state.hasError ? <h2>Net Error</h2> : <Child />}
      </div>
    )
  }
}

其他的都没有变,就是加了一个方法,这个方法是干什么的呢?当这个组件的子组件出错了,就会触发getDerivedStateFromError方法和componentDidCatch方法的调用,前者是用来修改状态用于判断页面是否渲染子组件,后者则是统计错误并反馈给服务器的。我们来看一下效果

image-20220215132826047

我们现在就成功调用了这个方法,当然了,我们现在就只写了打印这个错误的操作。

总结

  • 错误边界用来捕获后代组件的错误,并且渲染备用页面。
  • 所谓错误边界,首先有错误,其次有边界
  • getDerivedStateFromError捕获错误并生成新的状态对象
  • componentDidCatch统计错误反馈给服务器

Copyright statement:The articles of this site are all original if there is no special explanation, indicate the source please when you reprint.

Link of this article:https://work.lynchow.com/article/extend_error_boundary/