当前位置: 首页 > 科技观察

为什么Hook没有ErrorBoundary?

时间:2023-03-14 21:04:17 科技观察

大家好,我是Kason。在很多充分使用Hooks开发的团队中,使用ClassComponent的场景只有“使用ClassComponent创建ErrorBoundary”。可以说,如果Hooks有以下两种生命周期函数替代,ClassComponent完全可以放弃:getDerivedStateFromErrorcomponentDidCatch那为什么还没有标准的Hook呢?今天我们就从上面两个生命周期函数的实现原理和移植到Hook的代价来谈谈这个问题。ErrorBoundary实现原理ErrorBoundary可以捕获后代组件中“React工作流”中的错误。“React工作流”是指:渲染阶段,即“组件渲染”和“Diff算法”发生的阶段。commit阶段是“渲染DOM”和“componentDidMount/Update执行”的阶段。这就是为什么“事件回调中发生的错误”无法被ErrorBoundary捕获的原因——事件回调不属于“React工作流”。如何捕捉错误“渲染阶段”的整体执行流程如下:do{try{//渲染阶段workLoop()的具体执行流程;休息;}catch(thrownValue){handleError(root,thrownValue);}}而(真);可以发现,如果在“renderphase”发生错误,就会被捕获,并执行handleError方法。同理,“提交阶段”的整体执行流程如下:phase”,它将被捕获并执行captureCommitPhaseError方法。getDerivedStateFromError的原理。如何处理捕获的错误?我们知道ClassComponent中this.setState的第一个参数不仅可以接收“新状态”,还可以接收“改变状态的函数”作为参数://This.setState(this.state.num+1)可以这样实现//也可以实现this.setState(num=>num+1)getDerivedStateFromError,利用了this.setState中“函数改变状态”的特性。捕获到错误后,即:对于“渲染阶段”,执行handleError后。对于“提交阶段”,在执行captureCommitPhaseError之后。在ErrorBoundary对应的组件中会触发类似如下的更新:更新。componentDidCatch的原理我们再来看另一个ErrorBoundary相关的生命周期函数——componentDidCatch。ClassComponent中this.setState的第二个参数可以接收一个“回调函数”作为参数:this.setState(newState,()=>{//...callback})当触发更新渲染到页面时,回调将触发。这就是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中的实现)应该类似于下面这样:functionErrorBoundary({children}:{children:ReactNode}){const[errorMsg,updateError]=useState(空);useErrorBoundary((e:Error)=>{//捕获错误,触发更新updateError(e);})return(

{errorMsg?'Error:'+errorMsg.toString():children}
)}其中useErrorBoundary的触发方式与useEffect类似:useErrorBoundary((e:Error)=>{//...})//类似于useEffect(()=>{//...})作者模仿ClassComponent中ErrorBoundary和useEffect的实现原理,实现了原生的Hooks——useErrorBoundary。有兴趣的朋友可以在useErrorBoundary在线例子[1]中体验一下效果。总结ClassComponent中ErrorBoundary的实现使用了this.setState的回调函数特性,需要额外的开发成本才能在Hooks中完全实现相同的功能。笔者猜测,这也是没有提供对应的nativeHooks的原因之一。参考资料[1]UseErrorBoundary在线示例:https://codesandbox.io/s/angry-mountain-xstgj4?file=/src/App.js。