Error Boundary的定义
Error Boundary是一种组件,或者说是类组件。它需要定义 getDerivedStateFromError 或者 componentDidCatch 生命周期方法。
它自身有三种功能:
- 捕获渲染期间的错误
- 打印这些错误
- 降级展示UI
Error Boundary捕获错误的原理
React的工作流程分为render阶段和commi阶段。所以React需要分别捕获这两个阶段抛出的错误。
在render阶段的入口函数中,renderRootSync和renderRootConcurrent在分别调用workLoopSync和WorkLoopConcurrent方法时都会通过tryCatch语句进行包裹。
这样就能捕获同步执行render阶段和commit阶段抛出的错误了。
捕获render阶段错误的方法叫handleError,捕获commit阶段错误的方法叫captureCommitPhaseError。
不管是handleError还是captureCommitPhaseError,都会从当前发生错误的节点的父节点开始向上遍历,找到最近的Error Boundary。
找到Error Boundary后会做两件事:构建callback 和 执行callback。
构建callback
通过createClassErrorUpdate方法会创建update对象,然后针对
- 定义getDerivedStateFromError方法,将update.payload属性赋值为匿名函数,在匿名函数中会执行getDerivedStateFromError这个方法。再将update.callback属性复制为一个匿名函数,在函数中会执行logCapturedError方法抛出React的提示信息、
- 定义componentDidCatch方法,将update.callback属性赋值为匿名函数,在匿名函数中将执行logCapturedError抛出React的提示信息,并将错误信息和栈信息作为参数执行componentDidCatch生命周期函数
如果没有找到Error Boundary,将遍历到根节点,在根节点构建callback。创建update对象,然后赋值update.callback属性为匿名函数,在函数中执行onUncaughtError方法抛出未捕获的错误,和执行logCapturedError抛出React的提示信息。
执行callback
在render阶段的beginWork方法中执行processUpdateQueue将计算这些update,主要是执行保存在update.payload的匿名函数,间接执行getDerivedStateFromError方法将返回值合并到this.state中。
并将构造的回调函数update.callback都保存在fiber.updateQueue.effects数组中。
在commit阶段的layout阶段,执行commitUpdateQueue方法依次执行fiber.updateQueue.effects中保存的callback。在此时将执行上述构造的各个回调函数。
包括:getDerivedStateFromError构造的logCapturedError、componentDidCatch构造的logCapturedError和componentDidCatch,根节点构造的onUncaughtError和logCapturedError。
降级展示UI
在handleError方法中会先调用throwException方法。在throwException方法中对于ClassComponent类型的组件会执行 workInProgress.flags |= ShouldCapture,然后创建update (和构造callback的update逻辑一致),然后将update插入到workInProgress fiber.updateQueue中构成单向环状链表。
执行完throwException方法后会执行completeUnitOfWork方法,在completeUniOfWork方法中会执行unwindWork方法。unwindWork方法会针对包含ShouldCapture的workInProgress fiber节点(即在throwException方法中赋值)再执行 workInProgress.flags = (flags & ~ShouldCapture) | DidCapture(意思就是把ShouldCapture从flags中删除再合并DidCapture)。然后返回 workInProgress。
因为整个 try catch 语句被包裹在do while循环中,所以会再次执行workLoopConcurrent方法再开始执行beginWork方法。首先会根据ClassComponent执行updateClassComponent。调用processUpdateQueue计算update(即getDerivedStateFromError方法拿到返回的结果)得到最新的state(将结果合并到this.state)然后执行finishClassComponent方法执行this.render方法。直到最后执行完commit阶段就会页面上展示降级后的UI。
总结
捕获渲染期间的错误 是通过 handError 方法结合 try catch语句实现的。
打印这些错误 是通过 logCapturedError 方法和 componentDidCatch 生命周期函数实现的。
降级展示UI 是通过getDerivedStateFromError实现的。