React ErrorBoundrry的使用与原理

286 阅读2分钟

概念

ErrorBoundrry是react提出的错误边界捕获的概念,为了解决在渲染等时机中发生错误导致的页面白屏问题,我们可以在ErrorBoundrry提供降级UI,提升用户体验。

它有两个生命周期函数:

使用

1.ErrorBoundrry实现:

根据上面的概念,我们得知实现ErrorBoundrry主要就是依赖getDerivedStateFromErrorcomponentDidCatch配合使用。getDerivedStateFromError负责在错误捕获后更新ErrorBoundary组件的状态,componentDidCatch是降级ui渲染后的钩子,主要用来做错误打印与上报

import React from 'react'

/**
 * 错误边界捕获
 */
export default class ErrorBoundary extends React.Component {
  state = { hasError: false }

  componentDidCatch(error) {
    console.error('[错误边界捕获] 错误信息:', error)
    
    // 上报BoundaryError
    reportBoundaryError(error)
  }

  static getDerivedStateFromError(error) {
    // 更新 state
    return { hasError: true  }
  }

  render() {
    if (this.state.hasError) {
      // 降级后的 UI
      return (
        this.props.errorElement
      )
    }

    return this.props.children
  }
}

  1. ErrorBoundrry使用:

包裹在需要降级UI的组件外,通常是根节点,方便捕获整个应用在render期间发生的所有错误

<ErrorBoundrry>
   <Root />
</ErrorBoundrry>

原理

  1. ErrorBoundrry能捕获哪些时机的异常?
  • render阶段,即组件renderDiff算法发生的阶段
  • commit阶段,即渲染DOMcomponentDidMount/Update执行的阶段
// render阶段
function renderRootSync(root: FiberRoot, lanes: Lanes) {
  ...
  do {
    try {
      workLoopSync();
      break;
    } catch (thrownValue) {
      // 处理错误
      handleError(root, thrownValue);
    }
  } while (true);
  ...
}

// commit阶段  
function commitRootImpl(root, renderPriorityLevel) {
  try {
      commitBeforeMutationEffects();
    } catch (error) {
      // 处理错误
      captureCommitPhaseError(nextEffect, error);
    }
}
  1. getDerivedStateFromError的执行过程
    发生错误被捕获后,getDerivedStateFromError,将其返回结果入队列更新fiber,相当于一次setState
function createClassErrorUpdate(
  fiber: Fiber,
  errorInfo: CapturedValue<mixed>,
  lane: Lane,
): Update<mixed> {
  ...
  // 获取getDerivedStateFromError后执行并将结果返回
  const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
  if (typeof getDerivedStateFromError === 'function') {
    const error = errorInfo.value;
    update.payload = () => {
      logCapturedError(fiber, errorInfo);
      return getDerivedStateFromError(error);
    };
  }
  ...
}


// 将getDerivedStateFromError执行结果入队列更新fiber,相当于一次setState
const update = createClassErrorUpdate(
  fiber,
  errorInfo,
  (SyncLane: Lane),
);
enqueueUpdate(fiber, update);

3.componentDidCatch是ErrorBoundary组件更新后执行的钩子

// This component has already been unmounted.
// We can't schedule any follow up work for the root because the fiber is already unmounted,
// but we can still call the log-only boundary so the error isn't swallowed.
//
// TODO This is only a temporary bandaid for the old reconciler fork.
// We can delete this special case once the new fork is merged.
if (
typeof instance.componentDidCatch === 'function' &&
!isAlreadyFailedLegacyErrorBoundary(instance)
) {
    try {
      instance.componentDidCatch(error, errorInfo);
    } catch (errorToIgnore) {
      // TODO Ignore this error? Rethrow it?
      // This is kind of an edge case.
    }
}