为什么Hook没有ErrorBoundary?

更新日期: 2022-06-13阅读: 916标签: Hook

大家好,我卡颂。

在很多全面使用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原理

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

我们知道,ClassComponent中this.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。

ClassComponent中this.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的回调中抛出错误。

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

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

实现Hooks中的ErrorBoundary

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

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

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(() => {
  // ...
})

笔者仿照ClassComponent中ErrorBoundary的实现原理与useEffect的实现原理,实现了原生Hooks —— useErrorBoundary。

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

总结

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

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

作者:卡颂,来源:https://segmentfault.com/a/1190000041974765

链接: https://www.fly63.com/article/detial/11722

分享 10 个可以使用 Vue.js 制作的有用的自定义钩hook

Vue.js 是我使用的第一个 JavaScript 框架。 我可以说 Vue.js 是我进入 JavaScript 世界的第一扇门之一。 目前,Vue.js 仍然是一个很棒的框架。 我认为有了组合 API,Vue.js 只会增长得更多

pytest插件探索——hook开发

conftest.py可以作为最简单的本地plugin调用一些hook函数,以此来做些强化功能。pytest整个框架通过调用如下定义良好的hooks来实现配置,收集,执行和报告这些过程:

React中useState Hook 示例

到 React 16.8 目前为止,如果编写函数组件,然后遇到需要添加状态的情况,咱们就必须将组件转换为类组件。编写 class Thing extends React.Component,将函数体复制到render()方法中,修复缩进,最后添加需要的状态。

useContext Hook 是如何工作的?

所有这些新的React Hook之间都有一个宗旨:就是为了使函数组件像类组件一样强大。useContext hook 与其它几个有点不一样,但它在特定场景下还是很有用的。React 的 Context API 是一种在应用程序中深入传递数据的方法

结合React的Effect Hook分析组件副作用的清除

我们在DidMount的时候通过ID订阅了好友的在线状态,并且为了防止内存泄漏,我们需要在WillUnmount清除订阅,但是当组件已经显示在屏幕上时,friend prop 发生变化时会发生什么?

结合高阶函数聊聊useMemo和useCallback

useCallback和useMemo是其中的两个 hooks,本文旨在通过解决一个需求,结合高阶函数,深入理解useCallback和useMemo的用法和使用场景。 之所以会把这两个 hooks 放到一起说,是因为他们的主要作用都是性能优化

关于为什么使用React新特性Hook的一些实践与浅见

Hook是对函数式组件的一次增强,使得函数式组件可以做到class组件的state和生命周期。Hook的语法更加简练易懂,消除了class的生命周期方法导致的重复逻辑代码,解决了高阶组件难以理解和使用困难的问题。

React封装强业务hook的一个例子

最近因为使用列表展示的需求有点多,就想着把列表分页筛选的逻辑抽象一下。看了umi的一个useTable的hook,也不能满足业务需要,于是就自己写了一个,支持本地分页筛选和接口分页筛选。

React官方团队出手,补齐原生Hook短板

然而实际上,由于回调函数被useCallback缓存,形成闭包,所以点击的效果始终是sendMessage()。这就是「闭包陷阱」。以上代码的一种解决方式是「为useCallback增加依赖项」

实现一个自定义 React Hook:UseLocalStorageState

最近做需求,需要将数据保存到 localStorage 里,在组件初始化的时候获取,然后修改该值的时候,要保存到本地的 localStorage 中。很显然,这些逻辑完全可以封装为一个 React Hook

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!