React懒加载原理深入解析
时间:2023-03-28 11:15:31
HTML
类代码分割React的懒加载import()原理React.lazy原理悬疑原理参考一、代码切分(一)为什么要进行代码切分?现在前端项目基本都使用打包技术,比如Webpack,JS逻辑代码会生成一个bundle。bundle.js文件体积会越来越大,因为页面只有先请求资源后才会渲染,这会严重影响页面的首屏加载。为了解决此类问题,避免代码包体积过大,我们可以通过技术手段对代码包进行拆分,创建多个包,运行时动态加载。现在像Webpack、Browserify等打包器都支持代码拆分技术。(2)什么时候考虑代码拆分?这里介绍一个平时开发中可能会遇到的场景,比如比较大的第三方库或者插件(比如JS版的PDF预览库)只能在单页应用的一个页面上使用(SPA)即非首页这种情况可以考虑代码拆分,提高首屏加载速度。2、React的懒加载示例代码:importReact,{Suspense}from'react';constOtherComponent=React.lazy(()=>import('./OtherComponent'));functionMyComponent(){return(
Loading...
}>
);}如上面代码,通过import(),React.lazy和Suspense一起实现React的懒加载,也就是我们常说的运行时动态加载,即将OtherComponent组件文件拆分打包成一个新的package(bundle)文件,只有在本地渲染OtherComponent组件时才会下载.那么上面提到的代码拆分和动态加载是如何实现的呢?让我们一起探讨一下它是如何工作的。import()原理import()函数是TS39提出的一种动态加载模块的标准化实现,它的返回是一个promise。import()在浏览器宿主环境中的参考实现如下:__tempModuleLoadingVariable"+Math.random().toString(32).substring(2);script.type="module";script.textContent=`import*asmfrom"${url}";window.${tempGlobal}=m;`;script.onload=()=>{resolve(window[tempGlobal]);deletewindow[tempGlobal];script.remove();};script.onerror=()=>{reject(newError("FailedtoloadmodulescriptwithURL"+url));deletewindow[tempGlobal];script.remove();};document.documentElement.appendChild(script);});}当Webpack解析为import()语法时,代码拆分是自动执行的。React.lazy原理以下React源码基于React.lazy16.8.0版本源码如下:exportfunctionlazy
(ctor:()=>Thenable):LazyComponent{letlazyType={?typeof:REACT_LAZY_TYPE,_ctor:ctor,//React使用这些字段来存储结果。_status:-1,_result:null,};returnlazyType;}你可以看到它返回了一个LazyComponent对象。而对于LazyComponent的解析:...caseLazyComponent:{constelementType=workInProgress.elementType;returnmountLazyComponent(current,workInProgress,elementType,updateExpirationTime,renderExpirationTime,);}...functionmountLazyComponent(_current,workInProgress,elementType,updateExpirationTime,renderExpirationTime,){...让Component=readLazyComponentType(elementType);...}//Pending=0,Resolved=1,Rejected=2exportfunctionreadLazyComponentType(lazyComponent:LazyComponent):T{conststatus=lazyComponent._status;constresult=lazyComponent._result;switch(status){caseResolved:{constComponent:T=result;返回组件;}caseRejected:{consterror:mixed=result;抛出错误;}casePending:{constthenable:Thenable=result;然后抛出;}default:{//lazyComponent第一次被流染lazyComponent._status=待处理;constctor=lazyComponent._ctor;constthenable=ctor();thenable.then(moduleObject=>{if(lazyComponent._status===Pending){constdefaultExport=moduleObject.default;lazyComponent._status=Resolved;lazyComponent._result=defaultExport;}},error=>{if(lazyComponent._status===Pending){lazyComponent._status=Rejected;lazyComponent._result=error;}},);//处理同步thenables。switch(lazyComponent._status){案例解决:returnlazyComponent._result;案例被拒绝:抛出lazyComponent._result;}lazyComponent._result=thenable;然后抛出;}}}注:如果readLazyComponentType函数多次处理同一个lazyComponent,则可能进入Pending、Rejected等情况中从上面的代码可以看出,对于React.lazy()最初返回的LazyComponent对象,其_status默认为-1,所以在第一次渲染时,会进入readLazyComponentType函数中的默认逻辑,而这里会异步执行import(url)操作,因为还没有等待,接下来会检查模块是否Resolved。如果已经Resolved(已经加载),则直接返回moduleObject.default(动态加载模块的默认export),否则通过throwout向上层抛出thenable。为什么要扔?这就涉及到Suspense的工作原理了,接下来我们来分析一下。Suspense的原理由于React捕获异常,处理了很多代码逻辑,这里就不贴源码了。有兴趣可以阅读throwException中的逻辑,其中包括如何处理捕获到的异常。简述处理过程。React捕捉到异常后,会判断异常是否是thenable。如果是,它将找到SuspenseComponent。如果thenable处于挂起状态,它将呈现其子项作为回退值。一旦thenable被解析,那么SuspenseComponent的Subcomponents将被重新渲染一次。为了便于理解,我们也可以使用componentDidCatch来实现自己的Suspense组件,如下:==null&&typeoferr==='object'&&typeoferr.then==='function'){this.setState({promise:err},()=>{err.then(()=>{this.setState({promise:null})})})}}render(){const{fallback,children}=this.propsconst{promise}=这个。状态返回<>{承诺?fallback:children}}}参考前端高级面试题详细答案总结至此,我们已经分析完了React的懒加载原理。简单来说,React使用React.lazy和import()实现渲染时的动态加载,使用Suspense处理异步加载资源时页面应该如何显示。