《React进阶》说说React异步组件的前世今生
时间:2023-03-12 14:22:00
科技观察
本文转载自微信公众号《前端分享》,作者前端分享。转载本文请联系前端分享♂。APreface今天我们来谈谈React中异步组件的现状和未来。异步组件很可能是未来从数据交互到UI展示的一种平滑的技术方案。因此,既然我们要深入了解React,推进React,那么了解异步组件是很有必要的。老规矩,我们今天还带着问题开始思考?(自测掌握程度)1什么是React异步组件,它解决什么问题?2componentDidCatch如何捕捉渲染阶段的错误并进行弥补。3React.lazy是如何实现动态加载的?4为什么React.lazy应该在Supsonse里面。5Supsonse的原理是什么?二次理解:异步组件1什么是异步组件?我们先想想现在的React应用使用ajax或者fetch进行数据交互的场景。基本上是这样的。在类组件中,componentDidMount和函数组件在effect中进行数据交互,拿到数据后渲染UI视图。那么,组件的渲染是否可以等待异步数据请求完成,拿到数据后再进行渲染呢?对于上面的情况,第一感觉是难以置信。如果可以中断渲染,等待数据请求,再渲染?那就是Susponse,上面说的不可能的事情,Susponse做到了,React16.6是新的,Susponse允许组件“等待”一个异步操作,直到异步操作结束才渲染。传统模式:渲染组件->请求数据->再次渲染组件。异步模式:请求数据->渲染组件。2打开暂停模式。传统模式下的数据交互应该是这样的。functionIndex(){const[userInfo,setUserInfo]=React.useState(0)React.useEffect(()=>{/*请求数据交互*/getUserInfo().then(res=>{setUserInfo(res)})},[])return
{userInfo.name}
;}exportdefaultfunctionHome(){return
}过程:页面初始化挂载,RequestuseEffect中的数据,通过useState改变数据,两次更新组件渲染数据。那么如果使用Susponse异步模式,可以这样写:functionFutureAsyncComponent(){constuserInfo=getUserInfo()return
{userInfo.name}
;}/*future异步模式*/exportdefaultfunctionHome(){return
loading...
}>
}当数据还没有被loadedyet,会在Suspense中展示回退的内容,弥补请求数据中的转场效果。虽然目前版本还不能正式使用这种模式,但React未来会支持这样的代码形式。三溯源:从componentDidCatch到SuspenseSuspense是如何让上述不可能变成可能的?这从componentDidCatch开始。React在推出v16时,新增了一个生命周期函数componentDidCatch。如果一个组件定义了componentDidCatch,那么当该组件中的所有子组件在渲染过程中都抛出异常时,就会调用componentDidCatch函数。componentDidCatch使用componentDidCatch来捕获异常,它接受两个参数:1error-抛出的错误。2info-具有componentStack键的对象,其中包含有关引发错误的组件的堆栈信息。我们来模拟一个子组件渲染失败:/*正常组件可以渲染*/functionChildren(){return
hello,letuslearnReact
}/*非React组件将无法正常渲染*/functionChildren1(){return}exportdefaultclassIndexextendsReact.Component{componentDidCatch(error,info){console.log(error,info)}render(){return
}}如上,我们模拟在渲染失败的场景下,将一个非React组件Children1渲染成一个普通的React组件,这样渲染阶段就会报错,报错信息会被componentDidCatch捕获。报错信息如下:对于上述,如果子组件在渲染时出错,会导致整个组件渲染失败,无法显示。普通组件Children也会受到牵连。这时候我们就需要在componentDidCatch中进行一些补救措施。比如我们发现componentDidCatch失败了,我们可以给Children1添加一个状态控制。如果渲染失败,则终止Children1的渲染。functionErroMessage(){return
渲染出错~
}exportdefaultclassIndexextendsReact.Component{state={errorRender:false}componentDidCatch(error,info){/*补救措施*/this.setState({errorRender:true})}render(){return
{this.state.errorRender?:}
}}如果出错,通过setState重新渲染并移除失败的组件,让组件可以正常渲染,也不影响子组件的挂载。componentDidCatch一方面捕获渲染阶段发生的错误,另一方面可以在生命周期内执行sideeffects来挽回渲染异常带来的损失。componentDidCatch的原理componentDidCatch的原理应该很好理解。在内部,try{}catch(error){}可用于捕获渲染错误并处理渲染错误。try{//尝试渲染子组件}catch(error){//出现错误,componentDidCatch被调用了,}能否将componentDidCatch的思想迁移到Suspense,再回到我们的异步组件,如果异步代码是同步执行的,肯定不会正常渲染。我们还是需要先请求数据,等待数据返回,然后再使用返回的数据进行渲染。那么重点就是这句话,如何停止同步渲染等待异步数据请求呢?可以抛出异常吗?异常可以停止代码的执行,当然也可以停止渲染。Suspense是通过抛出异常暂停的渲染。Suspense需要一个createFetcher函数来封装异步操作。当试图从createFetcher返回的结果中读取数据时,有两种可能:一种是数据已经准备好了,那么直接返回结果;还有一种可能是异步操作还没有结束,数据还没有准备好。这时候createFetcher就会抛出一个“异常”。这个“异常”是正常的代码错误吗?不是的,这个异常是一个封装了请求数据的Promise对象,里面包含了真正的数据请求方法。由于Suspense可以抛出异常,所以可以通过类似componentDidCatch}的try{}catch{来获取异常。得到这个异常后怎么办?我们知道这个异常是一个Promise,那么接下来就是执行这个Promise了。成功状态后,获取数据,然后再次渲染组件。这时候渲染中已经读取到了正常的数据,那么就可以正常渲染了。接下来我们来模拟一下createFetcher和Suspense。我们来模拟一个简单的createFetcher/****@param{*}fn函数,请求数据交互,返回一个Promise*/functioncreateFetcher(fn){constfetcher={status:'pedding',result:null,p:null}returnfunction(){constgetDataPromise=fn()fetcher.p=getDataPromisegetDataPromise.then(result=>{/*成功获取数据*/fetcher.result=resultfetcher.status='resolve'})if(fetcher.status==='pedding'){/*第一次执行中断渲染,第二次*/throwfetcher}/*第二次执行*/if(fetcher.status)returnfetcher.result}}返回一个函数,在渲染阶段执行,第一个组件rendering,因为status=pedding,异常fetcher被抛给Susponse,渲染中止。Susponse会在内部componentDidCatch处理fetcher并执行getDataPromise.then。此时状态已经是resolve状态,可以正常返回数据。接下来,Susponse再次渲染该组件,此时可以正常获取数据。我们模拟一个简单的SuspenseexportclassSuspenseextendsReact.Component{state={isRender:true}componentDidCatch(e){/*异步请求,渲染fallback*/this.setState({isRender:false})const{p}=ePromise.resolve(p).then(()=>{/*数据请求后,渲染真正的组件*/this.setState({isRender:true})})}render(){const{isRender}=this.stateconst{children,fallback}=this.propsreturnisRender?children:fallback}}使用componentDidCatch捕获异步请求,如果有异步请求渲染fallback,等到异步请求执行完毕,渲染真正的组件,这样整个异步过程就是完全的。但是为了让大家理解流程,只是模拟了一个异步流程,实际流程要比这复杂的多。流程图:四种实践:从Suspense到React.lazyReact.lazy简介Suspense带来的异步组件革命尚未取得实质性成果,目前版本还未正式投入使用,但React.lazy是最佳实践对于当前版本的Suspense。我们都知道React.lazy和Suspense可以实现懒加载和按需加载,非常有利于代码切分,不会让初始化时加载大量文件,减少首屏时间。React.lazy基本上使用constLazyComponent=React.lazy(()=>import('./text'))React.lazy接受一个动态调用import()的函数。它必须返回需要解析默认导出的React组件的Promise。先来看看基本用法:constLazyComponent=React.lazy(()=>import('./test.js'))exportdefaultfunctionIndex(){return
loading...