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

相关文章
|
2月前
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
163 64
|
2月前
|
JavaScript 前端开发 API
Vue 3 中 v-model 与 Vue 2 中 v-model 的区别是什么?
总的来说,Vue 3 中的 `v-model` 在灵活性、与组合式 API 的结合、对自定义组件的支持等方面都有了明显的提升和改进,使其更适应现代前端开发的需求和趋势。但需要注意的是,在迁移过程中可能需要对一些代码进行调整和适配。
141 60
|
25天前
|
JavaScript API 数据处理
vue3使用pinia中的actions,需要调用接口的话
通过上述步骤,您可以在Vue 3中使用Pinia和actions来管理状态并调用API接口。Pinia的简洁设计使得状态管理和异步操作更加直观和易于维护。无论是安装配置、创建Store还是在组件中使用Store,都能轻松实现高效的状态管理和数据处理。
91 3
|
2月前
|
前端开发 JavaScript 测试技术
Vue3中v-model在处理自定义组件双向数据绑定时,如何避免循环引用?
Web 组件化是一种有效的开发方法,可以提高项目的质量、效率和可维护性。在实际项目中,要结合项目的具体情况,合理应用 Web 组件化的理念和技术,实现项目的成功实施和交付。通过不断地探索和实践,将 Web 组件化的优势充分发挥出来,为前端开发领域的发展做出贡献。
54 8
|
2月前
|
存储 JavaScript 数据管理
除了provide/inject,Vue3中还有哪些方式可以避免v-model的循环引用?
需要注意的是,在实际开发中,应根据具体的项目需求和组件结构来选择合适的方式来避免`v-model`的循环引用。同时,要综合考虑代码的可读性、可维护性和性能等因素,以确保系统的稳定和高效运行。
51 1
|
2月前
|
JavaScript
Vue3中使用provide/inject来避免v-model的循环引用
`provide`和`inject`是 Vue 3 中非常有用的特性,在处理一些复杂的组件间通信问题时,可以提供一种灵活的解决方案。通过合理使用它们,可以帮助我们更好地避免`v-model`的循环引用问题,提高代码的质量和可维护性。
57 1
|
2月前
|
JavaScript
在 Vue 3 中,如何使用 v-model 来处理自定义组件的双向数据绑定?
需要注意的是,在实际开发中,根据具体的业务需求和组件设计,可能需要对上述步骤进行适当的调整和优化,以确保双向数据绑定的正确性和稳定性。同时,深入理解 Vue 3 的响应式机制和组件通信原理,将有助于更好地运用 `v-model` 实现自定义组件的双向数据绑定。
|
19天前
|
JavaScript
vue使用iconfont图标
vue使用iconfont图标
105 1
|
29天前
|
JavaScript 关系型数据库 MySQL
基于VUE的校园二手交易平台系统设计与实现毕业设计论文模板
基于Vue的校园二手交易平台是一款专为校园用户设计的在线交易系统,提供简洁高效、安全可靠的二手商品买卖环境。平台利用Vue框架的响应式数据绑定和组件化特性,实现用户友好的界面,方便商品浏览、发布与管理。该系统采用Node.js、MySQL及B/S架构,确保稳定性和多功能模块设计,涵盖管理员和用户功能模块,促进物品循环使用,降低开销,提升环保意识,助力绿色校园文化建设。
|
2月前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱前端的大一学生,专注于JavaScript与Vue,正向全栈进发。博客分享Vue学习心得、命令式与声明式编程对比、列表展示及计数器案例等。关注我,持续更新中!🎉🎉🎉
55 1
vue学习第一章