本文转载自微信公众号「神奇的程序员k」,作者神奇的程序员k 。转载本文请联系神奇的程序员k公众号。
十多年的兰坪网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。营销型网站建设的优势是能够根据用户设备显示端的尺寸不同,自动调整兰坪建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。成都创新互联公司从事“兰坪网站设计”,“兰坪网站推广”以来,每个客户项目都认真落实执行。
前言
在我的开源项目中有一个组件是用来发送消息和展示消息的,这个组件的逻辑很复杂也是我整个项目的灵魂所在,单文件代码有1100多行。我每次用webstorm编辑这个文件时,电脑cpu温度都会飙升并伴随着卡顿。
就在前几天我终于忍不住了,意识到了Vue2的optionsAPI的缺陷,决定用Vue3的CompositionAPI来解决这个问题,本文就跟大家分享下我在优化过程中踩到的坑以及我所采用的解决方案,欢迎各位感兴趣的开发者阅读本文。
问题分析
我们先来看看组件的整体代码结构,如下图所示:
image-20210114095802363
罪魁祸首就是script部分,本文要优化的就是这一部分的代码,我们再来细看下script中的代码结构:
现在罪魁祸首是methods部分,那么我们只需要把methods部分的代码拆分出去,单文件代码量就大大减少了。
优化方案
经过上述分析后,我们已经知道了问题所在,接下来就跟大家分享下我一开始想到的方案以及最终所采用的方案。
直接拆分成文件
一开始我觉得既然methods方法占用的行数太多,那么我在src下创建一个methods文件夹,把每个组件中的methods的方法按照组件名进行划分,创建对应的文件夹,在对应的组件文件夹内部,将methods中的方法拆分成独立的ts文件,最后创建index.ts文件,将其进行统一导出,在组件中使用时按需导入index.ts中暴露出来的模块,如下图所示:
image-20210114103824562
index.ts代码
如下所示,我们将拆分的模块方法进行导入,然后统一export出去
- import compressPic from "@/methods/message-display/CompressPic";
- import pasteHandle from "@/methods/message-display/PasteHandle";
- export { compressPic, pasteHandle };
在组件中使用
最后,我们在组件中按需导入即可,如下所示:
- import { compressPic, pasteHandle } from "@/methods/index";
- export default defineComponent({
- mounted() {
- compressPic();
- pasteHandle();
- }
- })
运行结果
当我自信满满的开始跑项目时,发现浏览器的控制台报错了,提示我this未定义,突然间我意识到将代码拆分成文件后,this是指向那个文件的,并没有指向当前组件实例,当然可以将this作为参数传进去,但我觉得这样并不妥,用到一个方法就传一个this进去,会产生很多冗余代码,因此这个方案被我pass了。
使用mixins
前一个方案因为this的问题以失败告终,在Vue2.x的时候官方提供了mixins来解决this问题,我们使用mixin来定义我们的函数,最后使用mixins进行混入,这样就可以在任意地方使用了。
由于mixins是全局混入的,一旦有重名的mixin原来的就会被覆盖,所以这个方案也不合适,pass。
image-20210114111746208
使用CompositionAPI
上述两个方案都不合适,那 么CompositionAPI就刚好弥补上述方案的短处,成功的实现了我们想要实现的需求。
我们先来看看什么是CompositionAPI,正如文档所述,我们可以将原先optionsAPI中定义的函数以及这个函数需要用到的data变量,全部归类到一起,放到setup函数里,功能开发完成后,将组件需要的函数和data在setup进行return。
setup函数在创建组件之前执行,因此它是没有this的,这个函数可以接收2个参数: props和context,他们的类型定义如下:
- interface Data {
- [key: string]: unknown
- }
- interface SetupContext {
- attrs: Data
- slots: Slots
- emit: (event: string, ...args: unknown[]) => void
- }
- function setup(props: Data, context: SetupContext): Data
我的组件需要拿到父组件传过来的props中的值,需要通过emit来向父组件传递数据,props和context这两个参数正好解决了我这个问题。
setup又是个函数,也就意味着我们可以将所有的函数拆分成独立的ts文件,然后在组件中导入,在setup中将其return给组件即可,这样就很完美的实现了一开始我们一开始所说的的拆分。
实现思路
接下来的内容会涉及到响应性API,如果对响应式API不了解的开发者请先移步官方文档。
我们分析出方案后,接下来我们就来看看具体的实现路:
实现过程
接下来我们将上述思路进行实现。
添加setup选项
我们在vue组件的导出部分,在其对象内部添加setup选项,如下所示:
创建module模块
我们在src下创建module文件夹,用于存放我们拆分出来的功能代码文件。
如下所示,为我创建好的目录,我的划分依据是将相同类别的文件放到一起,每个文件夹的所代表的含义已在实现思路进行说明,此处不作过多解释。
创建InitData.ts文件
我们将组件中用到的响应式数据,统一在这里进行定义,然后在setup中进行return,该文件的部分代码定义如下,完整代码请移步:InitData.ts
- import {
- reactive,
- Ref,
- ref,
- getCurrentInstance,
- ComponentInternalInstance
- } from "vue";
- import {
- emojiObj,
- messageDisplayDataType,
- msgListType,
- toolbarObj
- } from "@/type/ComponentDataType";
- import { Store, useStore } from "vuex";
- // DOM操作,必须return否则不会生效
- const messagesContainer = ref
(null); - const msgInputContainer = ref
(null); - const selectImg = ref
(null); - // 响应式Data变量
- const messageContent = ref
(""); - const emoticonShowStatus = ref
("none"); - const senderMessageList = reactive([]);
- const isBottomOut = ref
(true); - let listId = ref
(""); - let messageStatus = ref
(0); - let buddyId = ref
(""); - let buddyName = ref
(""); - let serverTime = ref
(""); - let emit: (event: string, ...args: any[]) => void = () => {
- return 0;
- };
- // store与当前实例
- let $store = useStore();
- let currentInstance = getCurrentInstance();
- export default function initData(): messageDisplayDataType {
- // 定义set方法,将props中的数据写入当前实例
- const setData = (
- 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;
- };
- const setProperty = (
- storeParam: Store
, - instanceParam: ComponentInternalInstance | null
- ) => {
- $store = storeParam;
- currentInstance = instanceParam;
- };
- // 返回组件需要的Data
- return {
- messagesContainer,
- msgInputContainer,
- selectImg,
- $store,
- emoticonShowStatus,
- currentInstance,
- // .... 其他部分省略....
- emit
- }
- }
??细心的开发者可能已经发现,我把响应式变量定义在导出的函数外面了,之所以这么做是因为setup的一些特殊原因,在下面的踩坑章节我将会详解我为什么要这样做。
在组件中使用
定义完相应死变量后,我们就可以在组件中导入使用了,部分代码如下所示,完整代码请移步:message-display.vue
- import initData from "@/module/message-display/main-entrance/InitData";
- export default defineComponent({
- setup(props, context) {
- // 初始化组件需要的data数据
- 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();
- // 返回组件需要用到的方法
- return {
- createDisSrc,
- resourceObj,
- messageContent,
- emoticonShowStatus,
- emojiList,
- toolbarList,
- senderMessageList,
- isBottomOut,
- audioCtx,
- arrFrequency,
- pageStart,
- pageEnd,
- pageNo,
- pageSize,
- sessionMessageData,
- msgListPanelHeight,
- isLoading,
- isLastPage,
- msgTotals,
- isFirstLoading,
- messagesContainer,
- msgInputContainer,
- selectImg
- };
- }
- })
我们定义后响应式变量后,就可以在拆分出来的文件中导入initData函数,访问里面存储的变量了。
在文件中访问initData
我将页面内所有的事件监听也拆分成了文件,放在了EventMonitoring.ts中,在事件监听的处理函数是需要访问initData里存储的变量的,接下来我们就来看下如何访问,部分代码如下所示,完整代码请移步EventMonitoring.ts)
- import {
- computed,
- Ref,
- ComputedRef,
- watch,
- getCurrentInstance,
- toRefs
- } from "vue";
- import { useStore } from "vuex";
- import initData from "@/module/message-display/main-entrance/InitData";
- import { SetupContext } from "@vue/runtime-core";
- import _ from "lodash";
- export default function eventMonitoring(
- props: messageDisplayPropsType,
- context: SetupContext
- ): {
- userID: ComputedRef
; - onlineUsers: ComputedRef
; - } | void {
- const $store = useStore();
- const currentInstance = getCurrentInstance();
- // 获取传递的参数
- const data = initData();
- // 将props改为响应式
- const prop = toRefs(props);
- // 获取data中的数据
- const senderMessageList = data.senderMessageList;
- const sessionMessageData = data.sessionMessageData;
- const pageStart = data.pageStart;
- const pageEnd = data.pageEnd;
- const pageNo = data.pageNo;
- const isLastPage = data.isLastPage;
- const msgTotals = data.msgTotals;
- const msgListPanelHeight = data.msgListPanelHeight;
- const isLoading = data.isLoading;
- const isFirstLoading = data.isFirstLoading;
- const listId = data.listId;
- const messageStatus = data.messageStatus;
- const buddyId = data.buddyId;
- const buddyName = data.buddyName;
- const serverTime = data.serverTime;
- const messagesContainer = data.messagesContainer as Ref
; - // 监听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中对应的变量,需要修改其值时,只需要修改他的value即可。
至此,有关compositionAPI的基本使用就跟大家讲解完了,下面将跟大家分享下我在实现过程中所踩的坑,以及我的解决方案。
踩坑分享
今天是周四,我周一开始决定使用CompositionAPI来重构我这个组件的,一直搞到昨天晚上才重构完成,前前后后踩了很多坑,正所谓踩坑越多你越强,这句话还是很有道理的??。
接下来就跟大家分享下我踩到的一些坑以及我的解决方案。
dom操作
我的组件需要对dom进行操作,在optionsAPI中可以使用this.$refs.xxx来访问组件dom,在setup中是没有this的,翻了下官方文档后,发现需要通过ref来定义,如下所示:
{ ulContainer[i] = el }">
访问vuex
在setup中访问vuex需要通过useStore()来访问,代码如下所示:
- import { useStore } from "vuex";
- const $store = useStore();
- console.log($store.state.token);
访问当前实例
在组件中需要访问挂载在globalProperties上的东西,在setup中就需要通过getCurrentInstance()来访问了,代码如下所示:
- import { getCurrentInstance } from "vue";
- const currentInstance = getCurrentInstance();
- currentInstance?.appContext.config.globalProperties.$socket.sendObj({
- code: 200,
- token: $store.state.token,
- userID: $store.state.userID,
- msg: $store.state.userID + "上线"
- });
无法访问$options
我重构的websocket插件是将监听消息接收方法放在options上的,需要通过this.$options.xxx来访问,文档翻了一圈没找到有关在setup中使用的内容,那看来是不能访问了,那么我只能选择妥协,把插件挂载在options上的方法放到globalProperties上,这样问题就解决了。
内置方法只能在setup中访问
如上所述,我们使用到了getCurrentInstance和useStore,这两个内置方法还有initData中定义的那些响应式数据,只有在setup中使用时才能拿到数据,否则就是null。
我的文件是拆分出去的,有些函数是运行在某个拆分出来的文件中的,不可能都在setup中执行一遍的,响应式变量也不可能全当作参数进行传递的,为了解决这个问题,我有试过使用provide注入然后通过inject访问,结果运行后发现不好使,控制台报黄色警告说provide和inject只能运行在setup中,我直接裂开,当时发了一条沸点求助了下,到了晚上也没得到解决方案??。
经过一番求助后,我的好友@前端印象给我提供了一个思路,成功的解决了这个问题,也就是我上面initData的做法,将响应式变量定义在导出函数的外面,这样我们在拆分出来的文件中导入initData方法时,里面的变量都是指向同一个地址,可以直接访问存储在里面的变量且不会将其进行初始化。
至于getCurrentInstance和useStore访问出现null的情景,还有props、emit的使用问题,我们可以在initData的导出函数内部定义set方法,在setup里的方法中获取到实例后,通过set方法将其设置进我们定义的变量中。
至此,问题就完美解决了,最后跟大家看下优化后的组件代码,393行??
图片
image-20210114201837539
项目地址
项目地址:chat-system-github
在线体验地址:chat-system
分享文章:使用Vue3的CompositionAPI来优化代码量
转载注明:http://www.shufengxianlan.com/qtweb/news34/93334.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联