讲师中心 微信公众号
AI工具推荐 视频效率加速

Vue3如何用CompositionAPI优化代码量

小晨同学_3640

小晨同学_3640

发布时间:2023-05-28 16:55:23

|

1310人浏览过

|

来源于亿速云

转载

我们先来看看组件的整体代码结构:

  • template部分占用267行

  • script部分占用889行

  • style部分为外部引用占用1行

罪魁祸首就是script部分,本文要优化的就是这一部分的代码,我们再来细看下script中的代码结构:

  • props部分占用6行

  • data部分占用52行

    立即学习前端免费学习笔记(深入)”;

  • created部分占用8行

  • mounted部分占用98行

  • methods部分占用672行

  • emits部分占用6行

  • computed部分占用8行

  • watch部分占用26行

现在罪魁祸首是methods部分,那么我们只需要把methods部分的代码拆分出去,单文件代码量就大大减少了。

优化方案

经过上述分析后,我们已经知道了问题所在,接下来就跟大家分享下我一开始想到的方案以及最终所采用的方案。

直接拆分成文件

一开始我觉得既然methods方法占用的行数太多,那么我在src下创建一个methods文件夹,把每个组件中的methods的方法按照组件名进行划分,创建对应的文件夹,在对应的组件文件夹内部,将methods中的方法拆分成独立的ts文件,最后创建index.ts文件,将其进行统一导出,在组件中使用时按需导入index.ts中暴露出来的模块。

  • 创建methods文件夹

  • 把每个组件中的methods的方法按照组件名进行划分,创建对应的文件夹,即:message-display

  • 将methods中的方法拆分成独立的ts文件,即:message-display文件夹下的ts文件

  • 创建index.ts文件,即:methods下的index.ts文件

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。

使用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属性,传入props和context

  • 在src下创建module文件夹,将拆分出来的功能代码按组件进行划分

  • 将每一个组件中的函数进一步按功能进行细分,此处我分了四个文件夹出来

    • common-methods 公共方法,存放不需要依赖组件实例的方法

    • components-methods 组件方法,存放当前组件模版需要使用的方法

    • main-entrance 主入口,存放setup中使用的函数

    • split-method 拆分出来的方法,存放需要依赖组件实例的方法,setup中函数拆分出来的文件也放在此处

  • 在主入口文件夹中创建InitData.ts文件,该文件用于保存、共享当前组件需要用到的响应式data变量

  • 所有函数拆分完成后,我们在组件中将其导入,在setup中进行return即可

实现过程

接下来我们将上述思路进行实现。

添加setup选项

我们在vue组件的导出部分,在其对象内部添加setup选项,如下所示:

<template>   <!---其他内容省略--> </template> <script lang="ts"> export default defineComponent({   name: "message-display",   props: {     listId: String, // 消息id     messageStatus: Number, // 消息类型     buddyId: String, // 好友id     buddyName: String, // 好友昵称     serverTime: String // 服务器时间   },   setup(props, context) {     // 在此处即可写响应性API提供的方法,注意⚠️此处不能用this   } } </script>

创建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<HTMLDivElement | null>(null); const msgInputContainer = ref<HTMLDivElement | null>(null); const selectImg = ref<HTMLImageElement | null>(null); // 响应式Data变量 const messageContent = ref<string>(""); const emoticonShowStatus = ref<string>("none"); const senderMessageList = reactive([]); const isBottomOut = ref<boolean>(true); let listId = ref<string>(""); let messageStatus = ref<number>(0); let buddyId = ref<string>(""); let buddyName = ref<string>(""); let serverTime = ref<string>(""); 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<string>,     messageStatusParam: Ref<number>,     buddyIdParam: Ref<string>,     buddyNameParam: Ref<string>,     serverTimeParam: Ref<string>,     emitParam: (event: string, ...args: any[]) => void   ) => {     listId = listIdParam;     messageStatus = messageStatusParam;     buddyId = buddyIdParam;     buddyName = buddyNameParam;     serverTime = serverTimeParam;     emit = emitParam;   };   const setProperty = (     storeParam: Store<any>,     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<any> ): {   userID: ComputedRef<string>;   onlineUsers: ComputedRef<number>; } | 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<HTMLDivElement>;      // 监听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来定义,如下所示:

<template> <div ref="msgInputContainer"></div> <ul v-for="(item, i) in list" :ref="el => { ulContainer[i] = el }"></ul> </template>  <script lang="ts">   import { ref, reactive, onBeforeUpdate } from "vue";   setup(){     export default defineComponent({     // DOM操作,必须return否则不会生效     // 获取单一dom     const messagesContainer = ref<HTMLDivElement | null>(null);     // 获取列表dom     const ulContainer = ref<HTMLUListElement>([]);     const list = reactive([1, 2, 3]);     // 列表dom在组件更新前必须初始化     onBeforeUpdate(() => {        ulContainer.value = [];     });     return {       messagesContainer,       list,       ulContainer     }   })   } </script>

访问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方法将其设置进我们定义的变量中。

热门AI工具

更多
Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

509

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1638

2024.03.01

DOM是什么意思
DOM是什么意思

dom的英文全称是documentobjectmodel,表示文件对象模型,是w3c组织推荐的处理可扩展置标语言的标准编程接口;dom是html文档的内存中对象表示,它提供了使用javascript与网页交互的方式。想了解更多的相关内容,可以阅读本专题下面的文章。

6441

2024.08.14

Golang WebSocket与实时通信开发
Golang WebSocket与实时通信开发

本专题系统讲解 Golang 在 WebSocket 开发中的应用,涵盖 WebSocket 协议、连接管理、消息推送、心跳机制、群聊功能与广播系统的实现。通过构建实际的聊天应用或实时数据推送系统,帮助开发者掌握 如何使用 Golang 构建高效、可靠的实时通信系统,提高并发处理与系统的可扩展性。

175

2025.12.22

PHP WebSocket 实时通信开发
PHP WebSocket 实时通信开发

本专题系统讲解 PHP 在实时通信与长连接场景中的应用实践,涵盖 WebSocket 协议原理、服务端连接管理、消息推送机制、心跳检测、断线重连以及与前端的实时交互实现。通过聊天系统、实时通知等案例,帮助开发者掌握 使用 PHP 构建实时通信与推送服务的完整开发流程,适用于即时消息与高互动性应用场景。

376

2026.01.19

Python WebSocket实时通信与异步服务开发实践
Python WebSocket实时通信与异步服务开发实践

本专题聚焦 Python 在实时通信场景中的开发实践,系统讲解 WebSocket 协议原理、长连接管理、消息推送机制以及异步服务架构设计。内容包括客户端与服务端通信实现、连接稳定性优化、消息队列集成及高并发处理策略。通过完整案例,帮助开发者构建高效稳定的实时通信系统,适用于聊天应用、实时数据推送等场景。

375

2026.03.18

WebSocket 前端开发与实战技巧
WebSocket 前端开发与实战技巧

聚焦 WebSocket 在前端项目中的工程化实践,涵盖原生 JavaScript WebSocket 连接的封装与状态管理、Vue 3 中 WebSocket 的 Composable 封装(useWebSocket)、React 中自定义 Hook 管理连接生命周期、心跳检测(Ping/Pong 定时器)与自动断线重连的实现策略、指数退避重连算法、消息序列化协议(JSON / Protobuf / MessagePack)的选型与性

348

2026.05.25

WebSocket发送和接收数据教程合集
WebSocket发送和接收数据教程合集

本专题整合了WebSocket发送与接收数据教程合集,阅读专题下面的文章了解更多详细内容。

281

2026.05.25

Conan私有仓库搭建教程
Conan私有仓库搭建教程

本专题系统的讲解Conan私有仓库的搭建流程,涵盖仓库服务部署、存储目录配置、用户认证、权限划分和远程地址添加,并介绍内部C++依赖包的上传、下载及版本维护方法。

0

2026.09.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Vue3.x 工具篇--十天技能课堂
Vue3.x 工具篇--十天技能课堂

共26课时 | 2.4万人学习

Vue3.x 核心篇--十天技能课堂
Vue3.x 核心篇--十天技能课堂

共30课时 | 2.3万人学习

Vue3.x新特性篇--十天基础课堂
Vue3.x新特性篇--十天基础课堂

共20课时 | 1.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn