当前位置: 首页 > Web前端 > vue.js

Vue异步组件实现原理

时间:2023-03-31 18:49:50 vue.js

为什么异步组件需要异步组件Vue作为单页应用,在加载首页时经常会遇到加载慢的问题,导致用户体验不佳。这是因为在打包单页应用的时候,页面会把脚本打包成一个文件,这个文件包含了所有的业务和非业务代码,脚本文件太大会拖慢页面渲染的速度。在优化首屏性能时,最常用的方法是拆分文件和分离代理。按需加载的概念也是在这个前提下引入的。所以在Vue开发过程中,我们会把一些非首屏组件设计成不同的组件,一些不影响初始视觉体验的组件也可以设计成异步组件。定义异步组件在Vue中,注册组件时使用工厂函数来定义组件。该工厂函数将异步解析组件定义。工厂函数只有在组件需要渲染时才会被触发,并将结果缓存起来以供后续使用。使用。//注册全局组件时,定义为异步组件Vue.component("async-component",(resolve,reject)=>{setTimeout(()=>{//使用定时器模拟异步加载process//resolve需要返回一个组件定义对象,其属性与定义普通组件的属性一致resolve({template:"

async-component
"})},2000)})//注册本地异步组件constvm=newVue({components:{asyncComponent:()=>import('./test.vue')}})异步组件流程分析在组件基础的分析流程中,我们分析了实例的挂载过程就是基于渲染函数创建Vnode和基于Vnode创建真实DOM的过程。在创建Vnode的过程中,如果遇到组件占位符,就会调用createComponent。在该方法中,将合并选项并为子组件安装钩子函数。异步组件的处理也是在这个函数中进行的。函数createComponent(Ctor:Class|Function|Object|void,data:?VNodeData,context:Component,children:?Array,tag?:string):VNode|数组|void{if(isUndef(Ctor)){return}/***这里的baseCtor为Vue构造函数*/constbaseCtor=context.$options._base//asynccomponent//异步组件letasyncFactoryif(isUndef(Ctor.cid)){asyncFactory=CtorCtor=resolveAsyncComponent(asyncFactory,baseCtor)if(Ctor===undefined){//返回异步组件的占位符节点,它被呈现为//作为注释节点但保留所有原始信息节点。//该信息将用于异步服务器渲染和水合作用。返回createAsyncPlaceholder(asyncFactory,data,context,children,tag)}}data=data||{}//合成器配置resolveConstructorOptions(Ctor)//安装组件钩子函数installComponentHooks(data)常量名称=Ctor.options.name||tagconstvnode=newVNode(`vue-component-${Ctor.cid}${name?`-${name}`:''}`,data,undefined,undefined,undefined,context,{Ctor,propsData,listeners,tag,children},asyncFactory)returnvnode}在注册异步组件时,Vue.component(name,options)的第二个参数工厂函数不是普通对象,所以无论是全局注册还是本地注册,Vue不会执行.extend方法生成子类的构造函数,所以上面的createComponent方法中,Ctor.cid不会存在,代码会进入异步组件的分支异步组件的核心是resolveAsyncComponent函数。我们主要关心工厂函数的处理部分。我们来看看精简代码函数resolveAsyncComponent(factory:Function,baseCtor:Class):Class|void{if(owner&&!isDef(factory.owners)){constowners=factory.owners=[owner]letsync=truelettimerLoading=nulllettimerTimeout=null;(owner:any).$on('hook:destroyed',()=>remove(owners,owner))constresolve=once((res:Object|Class)=>{})constreject=once(reason=>{})constres=factory(resolve,reject)//如果同步解决则返回returnfactory.loading?factory.loadingComp:factory.resolved}}根据上面的代码,我们可以将异步组件工厂函数的编写方法总结为三步:定义异步请求成功和请求失败的处理函数同步执行组件定义的工厂函数返回请求成功函数处理resolve和reject函数,是once方法执行的结果。once方法的作用是防止多次调用异步组件,这样resolve和reject只会执行一次。functiononce(fn:Function):Function{//使用闭包特性将called用作标志letcalled=falsereturnfunction(){if(!called){called=truefn.apply(this,arguments)}}}看resolve,reject处理逻辑constforceRender=(renderCompleted:boolean)=>{for(leti=0,l=owners.length;i)=>{//专用的组件构造函数,并缓存在已解析的属性中factory.resolved=ensureCtor(res,baseCtor)if(!sync){//强制刷新视图forceRender(true)}else{owners.length=0}})//组件加载失败处理程序constreject=once(reason=>{process.env.NODE_ENV!=='production'&&warn(`无法解析异步组件t:${String(factory)}`+(reason?`\nReason:${reason}`:''))if(isDef(factory.errorComp)){factory.error=trueforceRender(true)}})创建组件构造函数后,将重新渲染视图。由于Vue是一种数据驱动的渲染尝试,组件加载后,没有数据变化,所以需要手动更新视图。forceRender函数会获取调用异步组件的每一个实例,然后在Vue原型上执行$forceUpdate方法刷新视图。在异步加载组件的过程中,由于Ctor是undefined,所以会同步创建一个comment节点。异步组件加载完成后,触发$forceUpdate再次执行createEmptyVNode。这是因为Ctor不是undefined,所以会进行正常的组件渲染流程。Ctor=resolveAsyncComponent(asyncFactory,baseCtor)if(Ctor===undefined){//返回异步组件的占位符节点,它呈现//作为注释节点但保留节点的所有原始信息。//该信息将用于异步服务器渲染和水合作用。returncreateAsyncPlaceholder(asyncFactory,data,context,children,tag)}Promise异步组件另一种写异步组件的方法是在工厂函数中返回一个Promise对象,而在ES6中引入了import来加载Module,import是加载的方法运行时的模块,可以和require类比,import是异步加载的,require是同步加载的,import会返回一个Promise对象具体用法Vue.component("asyncComponent",()=>import('./test.vue'))清除Promise异步组件的注册方法后,继续分析异步过程。constres=factory(resolve,reject)if(isObject(res)){if(isPromise(res)){if(isUndef(factory.resolved)){res.then(resolve,reject)}}}在工厂函数中使用import引入异步组件时,工厂函数会返回一个Promise对象,加载成功则执行resolve,加载失败则执行reject。判断一个对象是否是Promise对象最简单的方法就是是否有then和catch方法.catch==='function')}高级异步组件为了在使用时有更好的体验,可以使用加载组件在加载异步组件的过程中显示一个等待状态,并使用错误组件来处理组件未能加载等待的错误消息。定义异步组件时,工厂函数可以返回一个对象,该对象可以定义需要加载的组件组件,加载过程中显示的加载组件,加载错误显示的错误组件。在组件渲染过程中,同样进入异步组件的分支Vue.component("asyncComponent",()=>{//需要加载的组件component:import('./test.vue'),//加载过程中显示加载的加载组件:LoadingComponent,//组件加载错误时显示的组件error:ErrorComponent,//加载组件显示的延迟时间,默认为200,如果组件还没有延迟时间后加载成功,会显示加载组件延迟:200,//组件加载超时时间,如果超过这个时间加载组件,则认为组件加载成功,认为组件加载成功加载失败,错误组件用于提示超时:3000})对于高级异步组件,工厂函数返回一个对象。对于高级异步组件我们来看看异步组件Vue的处理过程){//高级异步组件处理流程//组件加载过程是Promise,异步组件的处理方法同上res.component.then(resolve,reject)if(isDef(res.error)){//当定义错误组件时,创建错误组件的子类构造函数,并保存到errorCompfactory.errorComp=ensureCtor(res.error,baseCtor)}if(isDef(res.loading)){//创建加载-time组件子类构造函数并保存到loadingCompfactory.loadingComp=ensureCtor(res.loading,baseCtor)if(res.delay===0){//立即显示加载组件factory.loading=true}else{timerLoading=setTimeout(()=>{timerLoading=null//指定时间延迟后,组件还未加载加载失败,加载时显示组件if(isUndef(factory.resolved)&&isUndef(factory.error)){factory.loading=trueforceRender(false)}//默认显示加载组件延迟时间为200ms},res.delay||200)}}if(isDef(res.timeout)){timerTimeout=setTimeout(()=>{timerTimeout=nullif(isUndef(factory.resolved)){//异步组件在指定时间内没有加载成功,触发加载失败reject(process.env.NODE_ENV!=='production'?`timeout(${res.timeout}ms)`:null)}},res.timeout)}}}通过分析上面的代码可以看出,高级组件的加载过程与Promise组件,附加加载失败和加载过程中的处理逻辑,通过delay属性延迟加载组件的显示,并通过timeout属性指定超时时间。webpack异步组件使用在使用webpack打包Vue应用的时候,我们可以将异步组件的代码分离出来。webpack为异步组件的加载提供了两种require.ensure()的写法:这是webpack为异步组件提供的写法。webpack打包时,会静态解析代码中的require.ensure(),将模块添加到一个单独的chunk中,其中该函数的第三个参数为单独的代码块名称Vue.component("asyncComponent",(resolve,reject)=>{require.ensure([],()=>{resolve(require('./test.vue'))},"asyncComponent")//这里的asyncComponent是chunkName})import(/*webpackChunkName:"chunkName"*/,component):在ES6中,import方式是推荐的写法,通过注释webpackChunkNameVue.component('asyncComponent',()=>import(/*webpackChunkName:"asyncComponent"*/,'./test.vue'))