使用Vue3的CompositionAPI来优化代码量(上)

简介: 使用Vue3的CompositionAPI来优化代码量(上)

前言


在我的开源项目中有一个组件是用来发送消息和展示消息的,这个组件的逻辑很复杂也是我整个项目的灵魂所在,单文件代码有1100多行。我每次用webstorm编辑这个文件时,电脑cpu温度都会飙升并伴随着卡顿。


就在前几天我终于忍不住了,意识到了Vue2的optionsAPI的缺陷,决定用Vue3的CompositionAPI来解决这个问题,本文就跟大家分享下我在优化过程中踩到的坑以及我所采用的解决方案,欢迎各位感兴趣的开发者阅读本文。


问题分析


我们先来看看组件的整体代码结构,如下图所示:


640.png

                         image-20210114095802363  


  • 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中暴露出来的模块,如下图所示:


640.png

                            image-20210114103824562  


  • 创建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。


640.png

                              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属性,传入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文件夹,用于存放我们拆分出来的功能代码文件。


如下所示,为我创建好的目录,我的划分依据是将相同类别的文件放到一起,每个文件夹的所代表的含义已在实现思路进行说明,此处不作过多解释。


640.png


创建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的一些特殊原因,在下面的踩坑章节我将会详解我为什么要这样做。

相关文章
|
6天前
|
JavaScript 前端开发 安全
Vue 3
Vue 3以组合式API、Proxy响应式系统和全面TypeScript支持,重构前端开发范式。性能优化与生态协同并进,兼顾易用性与工程化,引领Web开发迈向高效、可维护的新纪元。(238字)
289 139
|
1月前
|
开发工具 iOS开发 MacOS
基于Vite7.1+Vue3+Pinia3+ArcoDesign网页版webos后台模板
最新版研发vite7+vue3.5+pinia3+arco-design仿macos/windows风格网页版OS系统Vite-Vue3-WebOS。
233 11
|
16天前
|
JavaScript 安全
vue3使用ts传参教程
Vue 3结合TypeScript实现组件传参,提升类型安全与开发效率。涵盖Props、Emits、v-model双向绑定及useAttrs透传属性,建议明确声明类型,保障代码质量。
130 0
|
2月前
|
缓存 前端开发 大数据
虚拟列表在Vue3中的具体应用场景有哪些?
虚拟列表在 Vue3 中通过仅渲染可视区域内容,显著提升大数据列表性能,适用于 ERP 表格、聊天界面、社交媒体、阅读器、日历及树形结构等场景,结合 `vue-virtual-scroller` 等工具可实现高效滚动与交互体验。
338 1
|
2月前
|
缓存 JavaScript UED
除了循环引用,Vue3还有哪些常见的性能优化技巧?
除了循环引用,Vue3还有哪些常见的性能优化技巧?
180 0
|
3月前
|
JavaScript
vue3循环引用自已实现
当渲染大量数据列表时,使用虚拟列表只渲染可视区域的内容,显著减少 DOM 节点数量。
112 0
|
1月前
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
215 2
|
3天前
|
缓存 JavaScript
vue中的keep-alive问题(2)
vue中的keep-alive问题(2)
45 0
|
4月前
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
649 0