Home > Article > Web Front-end > How Vue3 uses CompositionAPI to optimize code size
Let’s first take a look at the overall code structure of the component:
The template part occupies 267 lines
The script part occupies 889 lines
The style part occupies 1 line for external reference
The culprit is the script part. This part of the code is what we want to optimize in this article. Let’s take a closer look. The code structure in the script below:
The props part occupies 6 lines
The data part occupies 52 lines
The created part occupies 8 lines
The mounted part occupies 98 lines
The methods part occupies 672 lines
The emits part occupies 6 lines
The computed part occupies 8 lines
The watch part occupies 26 lines
Now the culprit is the methods part, so we only need to split the code in the methods part, and the amount of code in a single file will be greatly reduced.
Optimization plan
After the above analysis, we already know the problem. Next, I will share with you the plan I initially thought of and the final solution I adopted. plan.
Split directly into files
At first I thought that since the methods method takes up too many lines, then I created a methods folder under src and put each The methods in each component are divided according to the component name, and the corresponding folder is created. Within the corresponding component folder, the methods in the methods are split into independent ts files. Finally, the index.ts file is created and processed. Export in a unified manner and import modules exposed in index.ts on demand when used in components.
Create methods folder
Divide the methods in each component according to the component name and create the corresponding folder, that is :message-display
Split the methods in methods into independent ts files, that is: ts files under the message-display folder
Create the index.ts file, that is: the index.ts file under methods
index.ts code
is as follows, we Import the split module methods, and then export them in a unified way
import compressPic from "@/methods/message-display/CompressPic"; import pasteHandle from "@/methods/message-display/PasteHandle"; export { compressPic, pasteHandle };
Use in the component
Finally, we can import it in the component as needed, as follows Display:
import { compressPic, pasteHandle } from "@/methods/index"; export default defineComponent({ mounted() { compressPic(); pasteHandle(); } })
Running result
When I started running the project with confidence, I found that the browser console reported an error, prompting me that this was undefined, and suddenly Suddenly I realized that after splitting the code into files, this points to that file and does not point to the current component instance. Of course, you can pass this as a parameter, but I don’t think this is appropriate. If you use a method, just pass it. Entering this will produce a lot of redundant code, so I passed this plan.
Using mixins
The previous solution ended in failure because of this problem. In Vue2.x, mixins were officially provided to solve this problem. We use mixins to solve this problem. Define our function and finally use mixins to mix it in so that it can be used anywhere.
Since mixins are mixed globally, once there is a mixin with the same name, the original one will be overwritten, so this solution is not suitable, pass.
Use CompositionAPI
If the above two solutions are not suitable, then CompositionAPI just makes up for the shortcomings of the above solutions and successfully achieves the needs we want to achieve.
Let’s first take a look at what CompositionAPI is. As stated in the document, we can group the functions defined in the original optionsAPI and the data variables that this function needs to use into the setup function. , after the function development is completed, return the functions and data required by the component in the setup.
The setup function is executed before creating the component, so it does not have this. This function can receive 2 parameters: props and context. Their types are defined as follows:
interface Data { [key: string]: unknown } interface SetupContext { attrs: Data slots: Slots emit: (event: string, ...args: unknown[]) => void } function setup(props: Data, context: SetupContext): Data
My component needs To get the value in the props passed from the parent component, you need to pass data to the parent component through emit. The two parameters props and context just solved this problem for me.
setup is a function, which means that we can split all functions into independent ts files, then import them in the component, and return them to the component in setup. This is perfect. It achieves the split we mentioned at the beginning.
Implementation Ideas
The following content will involve the responsive API. If developers are not familiar with the responsive API, please go to the official documentation first.
After we analyze the solution, let’s take a look at the specific implementation path:
Add the setup attribute to the export object of the component, pass in props and context
Create the module folder under src and divide the separated function codes into components
Divide the components in each component The functions are further subdivided by function. Here I have divided them into four folders
common-methods public methods, which store methods that do not depend on component instances
components-methods Component methods, stores the methods that need to be used in the current component template
main-entrance Main entrance, stores the functions used in 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方法将其设置进我们定义的变量中。
The above is the detailed content of How Vue3 uses CompositionAPI to optimize code size. For more information, please follow other related articles on the PHP Chinese website!