本文转载自微信公众号《魔法程序员k》,作者魔法程序员k。转载本文请联系大神程序员k公众号。前言在我的开源项目中有一个组件是用来发送和显示消息的。这个组件的逻辑非常复杂,是我整个项目的灵魂。单个文件中有1,100多行代码。每次用webstorm编辑这个文件,电脑cpu温度都会飙升,并伴随卡顿。就在前几天,我终于忍不住了。意识到Vue2的optionsAPI的缺陷,决定使用Vue3的CompositionAPI来解决这个问题。本文将和大家分享一下我在优化过程中踩过的坑以及采用的方法。欢迎所有感兴趣的开发者阅读本文。问题分析先来看一下组件的整体代码结构,如下图:image-20210114095802363模板部分占用267行脚本部分占用889行样式部分占用1行供外部引用罪魁祸首是脚本部分,本文要优化的这部分代码,我们仔细看一下脚本中的代码结构:props部分占??6行,data部分占52行,created部分占8行,mounted部分占98行,methods部分占672行,emits部分占6行,computed部分占8行。linewatch部分占用26行。现在罪魁祸首是methods部分,所以我们只需要拆分methods部分的代码,单个文件的代码量就大大减少了。优化方案经过上面的分析,我们已经知道了问题所在,接下来给大家分享一下我一开始想到的方案和最终采用的方案。直接拆分成文件一开始我以为是methods方法占了太多行,于是我在src下创建了一个methods文件夹,将每个组件中的methods方法按照组件名称进行划分,并创建了相应的文件夹,在对应的component文件夹,将methods中的方法拆分成独立的ts文件,最后创建一个index.ts文件,统一导出,在componentModule中使用时,根据需要导入index.ts中暴露的,如下图:image-20210114103824562创建methods文件夹根据组件名称划分各个组件中的方法,创建对应的文件夹,即:message-display将methods中的方法拆分成独立的ts文件,即:ts文件下message-display文件夹下创建一个index.ts文件,即:methods下的index.ts文件index.ts代码如下,我们将split模块的方法导入,然后统一export出来importcompressPicfrom"@/methods/message-display/CompressPic";importpasteHandlefrom"@/methods/message-display/PasteHandle";export{compressPic,pasteHandle};在组件中使用最后,我们可以在组件中根据需要导入它,如下图:}})当我满怀信心地开始运行项目时,发现浏览器的控制台报错,提示这是undefined。突然发现,把代码拆分成文件后,this指向的是那个文件,而不是当前的组件实例。当然这个可以作为参数传入,但我认为这是不合适的。在使用方法的时候传递一个this会产生很多冗余代码,所以这个方案就被我传递了。之前使用mixins的方案就是因为这个问题而以失败告终。在Vue2.x中,官方提供了mixins来解决这个问题。我们使用mixins来定义我们的函数,最后使用mixins将它们混合进去,这样就可以在任何地方使用了。由于mixin是全局混入的,一旦出现同名的mixin,原来的mixin就会被覆盖,所以这个方案不合适,pass。image-20210114111746208CompositionAPI并不适用于以上两种方案,所以CompositionAPI正好弥补了以上方案的不足,成功实现了我们想要实现的需求。我们先来看看什么是CompositionAPI。如文档中所述,我们可以将原始optionsAPI中定义的函数和该函数需要的数据变量组合在一起,放在setup函数中。功能开发完成后,返回setup中组件所需的功能和数据。setup函数是在组件创建之前执行的,所以没有this。这个函数可以接收两个参数:props和context。它们的类型定义如下:interfaceData{[key:string]:unknown}interfaceSetupContext{attrs:Dataslots:Slotsemit:(event:string,...args:unknown[])=>void}functionsetup(props:Data,context:SetupContext):Data我的组件需要获取父组件传过来的props里面的值,需要通过emit来给父组件传递数据,props和context这两个参数刚好解决了我的问题。setup是另外一个函数,也就是说我们可以把所有的函数拆分成独立的ts文件,然后在components中导入,在setup中返回给components,这是一个完美的实现。一开始我们一开始提到的split。实现思路以下内容会涉及到响应式API。如果您对响应式API不熟悉,请先阅读官方文档。我们分析完解决方案,再来看看具体的实现路径:在组件的export对象中添加setup属性,传入props和context在src下创建一个module文件夹,拆分函数代码按组件划分每个组件中的功能根据其功能进一步细分。这里我分为四个文件夹:common-methods公共方法,存放不需要依赖组件实例的方法components-methods组件方法,存放当前组件模板要使用的方法main-entrance是主入口,里面存放的是setup中使用的函数split-method拆分出来的方法,存放的是需要依赖组件实例的方法。setup中按功能拆分的文件也放在主入口文件文件夹中创建一个InitData.ts文件,用于保存和共享当前组件需要的响应式数据变量。接下来,我们将实现上述思路。添加setup选项我们在vue组件的export部分,在object内部添加setup选项,如下:创建模块module我们在src下创建一个module文件夹,用于存放我们拆分的函数代码文件。如下图,我创建的目录是基于将同类文件放在一起的。实现思路中已经说明了各个文件夹的含义,这里不再过多说明。创建InitData.ts文件。我们将在这里定义组件中使用的响应数据,然后在设置中返回它。该文件的部分代码定义如下。完整代码请移步:InitData.tsimport{reactive,Ref,ref,getCurrentInstance,ComponentInternalInstance}from"vue";import{emojiObj,messageDisplayDataType,msgListType,toolbarObj}from"@/type/ComponentDataType";import{Store,useStore}from"vuex";//DOM操作,否则必须返回不会生效null>(null);//响应式数据变量constmessageContent=ref("");constemoticonShowStatus=ref("none");constsenderMessageList=reactive([]);constisBottomOut=ref(true);letlistId=ref("");letmessageStatus=ref(0);letbuddyId=ref("");letbuddyName=ref("");letserverTime=ref("");letmit:(事件:字符串,...args:any[])=>void=()=>{return0;};//存储和当前实例let$store=useStore();letcurrentInstance=getCurrentInstance();exportdefaultfunctioninitData():messageDisplayDataType{//定义集方法,将props中的数据写入当前实例constsetData=(listIdParam:Ref,messageStatusParam:Ref,buddyIdParam:Ref,buddyNameParam:Ref,serverTimeParam:Ref,emitParam:(event:string,...args:any[])=>void)=>{listId=listIdParam;messageStatus=messageStatusParam;buddyId=buddyIdParam;buddyName=buddyNameParam;serverTime=serverTimeParam;emit=emitParam;};constsetProperty=(storeParam:Store,instanceParam:ComponentInternalInstance|null)=>{$store=storeParam;currentInstance=instanceParam;};//返回组件需要的Datareturn{messagesContainer,msgInputContainer,selectImg,$store,emoticonShowStatus,currentInstance,//...其他部分省略....emit}}??细心的开发人员可能已经发现,我在导出函数之外定义了响应变量。之所以这样做,是因为一些特殊的设置原因。在后面的踩坑章节,我会详细说明我为什么要这么做在组件中使用并定义了相应的死变量后,我们就可以在组件中导入使用了。部分代码如下,完整代码请移步:message-display.vueimportinitDatafrom"@/module/message-display/main-entrance/InitData";exportdefaultdefineComponent({setup(props,context){//初始化const{createDisSrc,resourceObj,messageContent,emoticonShowStatus,emojiList,toolbarList,senderMessageList,isBottomOut,audioCtx,arrFrequency,pageStart,pageEnd,pageNo,pageSize,sessionMessageData,msgListPanelHeight,isLoading,isLastPage,msgTotals,isFirstLoading,messagesContainer,msgInputContainer,selectImg}=initData();//返回组件需要使用的方法msgListPanelHeight,isLoading,isLastPage,msgTotals,isFirstLoading,messagesContainer,msgInputContainer,selectImg};}})我们定义好post-response变量后,可以拆分出来导入文件中的initData函数访问存储在其中的变量。为了访问文件中的initData,我将页面中的所有事件监视器拆分为文件,并将它们放在EventMonitoring.ts中。事件监视器的处理函数需要访问存储在initData中的变量。接下来我们看看如何访问,部分代码如下,完整代码请移步EventMonitoring.ts/module/message-display/main-entrance/InitData";import{SetupContext}from"@vue/runtime-core";import_from"lodash";exportdefaultfunctioneventMonitoring(props:messageDisplayPropsType,context:SetupContext):{userID:ComputedRef;onlineUsers:ComputedRef;}|void{const$store=useStore();constcurrentInstance=getCurrentInstance();//获取传递的参数constdata=initData();//将props改为响应式constprop=toRefs(props);//获取data中的数据constsenderMessageList=data.senderMessageList;constsessionMessageData=data.sessionMessageData;constpageStart=data.pageStart;constpageEnd=data.pageEnd;constpageNo=data.pageNo;constisLastPage=data.isLastPage;constmsgTotals=data.msgTotals;constmsgListPanelHeight=data.msgListPanelHeight;constisLoading=data.isLoading;constisFirstLoading=data.isFirstLoading;constlistId=data.listId;constmessageStatus=data.messageStatus;constbuddyId=data.buddyId;constbuddyName=data.buddyName;constserverTime=data.serverTime;constmessagesContainer=data.messagesContainerasRef;//监听listID变化watch(prop.listId,(newMsgId:string)=>{listId.value=newMsgId;messageStatus.value=prop.messageStatus.value;buddyId.value=prop.buddyId.value;buddyName.value=prop.buddyName.value;serverTime.value=prop.serverTime.value;//消息id改变,清除消息列表数据senderMessageList.length=0;//初始化分页数据sessionMessageData.length=0;pageStart.value=0;pageEnd.value=0;pageNo.value=1;isLastPage.value=false;msgTotals.value=0;msgListPanelHeight.value=0;isLoading.value=false;isFirstLoading.value=true;});}和代码中一样,在文件中使用的时候,取出initData中对应的变量,一个d当你需要修改它的值的时候,只需要修改它的值到这个地步,compositionAPI的基本使用如下,讲解完后,分享一下我在实现过程中踩过的坑,以及我的解决方法。踩坑分享今天是星期四。我决定在星期一使用CompositionAPI来重构我的组件。直到昨晚才完成重构。我来回踩了很多坑。俗话说,踩的坑越多,越强大。这句话还是很有道理的??。接下来给大家分享一下我踩过的一些坑以及我的解决方法。Dom操作我的组件需要对dom进行操作。您可以在optionsAPI中使用this.$refs.xxx来访问组件dom。设置中没有这个。翻了翻官方文档,发现需要通过ref定义,如下所示: