美文网首页js css html
【转载】为什么Hook没有ErrorBoundary?

【转载】为什么Hook没有ErrorBoundary?

作者: 涅槃快乐是金 | 来源:发表于2022-06-15 23:57 被阅读0次

    原文:https://segmentfault.com/a/1190000041974765

    大家好,我卡颂。

    在很多全面使用Hooks开发的团队,唯一使用ClassComponent的场景就是使用ClassComponent创建ErrorBoundary

    可以说,如果Hooks存在如下两个生命周期函数的替代品,就能全面抛弃ClassComponent了:

    • getDerivedStateFromError
    • componentDidCatch

    那为什么还没有对标的Hook呢?

    今天我们从上述两个生命周期函数的实现原理,以及要移植到Hook上需要付出的成本来谈论这个问题。

    欢迎加入人类高质量前端框架群,带飞

    ErrorBoundary实现原理

    ErrorBoundary可以捕获子孙组件中React工作流程内的错误。

    React工作流程指:

    • render阶段,即组件renderDiff算法发生的阶段
    • commit阶段,即渲染DOMcomponentDidMount/Update执行的阶段

    这也是为什么事件回调中发生的错误无法被ErrorBoundary捕获 —— 事件回调并不属于React工作流程

    如何捕获错误

    render阶段的整体执行流程如下:

    do {
      try {
        // render阶段具体的执行流程
        workLoop();
        break;
      } catch (thrownValue) {
        handleError(root, thrownValue);
      }
    } while (true);
    

    可以发现,如果render阶段发生错误,会被捕获并执行handleError方法。

    类似的,commit阶段的整体执行流程如下:

    try {
      // ...具体执行流程
    } catch (error) {
      captureCommitPhaseError(current, nearestMountedAncestor, error);
    }
    

    如果commit阶段发生错误,会被捕获并执行captureCommitPhaseError方法。

    getDerivedStateFromError原理

    捕获后的错误如何处理呢?

    我们知道,ClassComponentthis.setState第一个参数,除了可以接收新的状态,也能接收改变状态的函数作为参数:

    // 可以这样
    this.setState(this.state.num + 1)
    
    // 也可以这样
    this.setState(num => num + 1)
    

    getDerivedStateFromError的实现,就借助了this.setState改变状态的函数这一特性。

    当捕获错误后,即:

    • 对于render阶段handleError执行后
    • 对于commit阶段captureCommitPhaseError执行后

    会在ErrorBoundary对应组件中触发类似如下更新:

    this.setState(
      getDerivedStateFromError.bind(null, error)
    )
    

    这就是为什么getDerivedStateFromError要求开发者返回新的state —— 本质来说,他就是触发一次新的更新。

    componentDidCatch原理

    再来看另一个ErrorBoundary相关的生命周期函数 —— componentDidCatch

    ClassComponentthis.setState的第二个参数,可以接收回调函数作为参数:

    this.setState(newState, () => {
      // ...回调
    })
    
    

    当触发的更新渲染到页面后,回调会触发。

    这就是componentDidCatch的实现原理。

    当捕获错误后,会在ErrorBoundary对应组件中触发类似如下更新:

    this.setState(this.state, componentDidCatch.bind(this, error))

    处理“未捕获”的错误

    可以发现,React运行流程中的错误,都已经被React自身捕获了,再交由ErrorBoundary处理。

    如果没有定义ErrorBoundary,这些被捕获的错误需要重新抛出,营造错误未被捕获的感觉

    那这一步在哪里执行呢?

    this.setState类似,ReactDOM.render(element, container[, callback])第三个参数也能接收回调函数

    如果开发者没有定义ErrorBoundary,那么React最终会在ReactDOM.render的回调中抛出错误。

    可以发现,在ClassComponentErrorBoundary的实现完全依赖了ClassComponent已有的特性。

    Hooks本身并不存在类似this.setState的回调特性,所以实现起来会比较复杂。

    实现Hooks中的ErrorBoundary

    除了上述谈到的阻碍,FunctionComponentClassComponent在源码层面的运行流程也有细节上的差异,要照搬实现也有一定难度。

    如果一定要实现,在最大程度复用现有基础设施的指导方针下,useErrorBoundaryErrorBoundaryHooks中的实现)的使用方式应该类似如下:

    function ErrorBoundary({children}: {children: ReactNode}) {
      const [errorMsg, updateError] = useState<Error | null>(null);
    
      useErrorBoundary((e: Error) => {
        // 捕获到错误,触发更新
        updateError(e);
      })
    
      return (
        <div>
          {errorMsg ? '报错:' + errorMsg.toString() : children}
        </div>
      )
    }
    

    其中useErrorBoundary的触发方式类似useEffect

    useErrorBoundary((e: Error) => {
      // ...
    })
    
    // 类似
    useEffect(() => {
      // ...
    })
    

    笔者仿照ClassComponentErrorBoundary的实现原理与useEffect的实现原理,实现了原生Hooks —— useErrorBoundary

    感兴趣的朋友可以在useErrorBoundary在线示例体验效果。

    总结

    ErrorBoundaryClassComponent中的实现使用了this.setState的回调函数特性,这使得Hooks中要完全实现同样功能,需要额外开发成本。

    笔者猜测,这是没有提供对应原生Hooks的原因之一。

    相关文章

      网友评论

        本文标题:【转载】为什么Hook没有ErrorBoundary?

        本文链接:https://www.haomeiwen.com/subject/xhufvrtx.html