新聞中心
本文轉(zhuǎn)載自微信公眾號(hào)「神奇的程序員k」,作者神奇的程序員k 。轉(zhuǎn)載本文請(qǐng)聯(lián)系神奇的程序員k公眾號(hào)。

十多年的蘭坪網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開(kāi)發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。營(yíng)銷型網(wǎng)站建設(shè)的優(yōu)勢(shì)是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整蘭坪建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無(wú)論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。成都創(chuàng)新互聯(lián)公司從事“蘭坪網(wǎng)站設(shè)計(jì)”,“蘭坪網(wǎng)站推廣”以來(lái),每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
前言
在我的開(kāi)源項(xiàng)目中有一個(gè)組件是用來(lái)發(fā)送消息和展示消息的,這個(gè)組件的邏輯很復(fù)雜也是我整個(gè)項(xiàng)目的靈魂所在,單文件代碼有1100多行。我每次用webstorm編輯這個(gè)文件時(shí),電腦cpu溫度都會(huì)飆升并伴隨著卡頓。
就在前幾天我終于忍不住了,意識(shí)到了Vue2的optionsAPI的缺陷,決定用Vue3的CompositionAPI來(lái)解決這個(gè)問(wèn)題,本文就跟大家分享下我在優(yōu)化過(guò)程中踩到的坑以及我所采用的解決方案,歡迎各位感興趣的開(kāi)發(fā)者閱讀本文。
問(wèn)題分析
我們先來(lái)看看組件的整體代碼結(jié)構(gòu),如下圖所示:
image-20210114095802363
- template部分占用267行
- script部分占用889行
- style部分為外部引用占用1行
罪魁禍?zhǔn)拙褪莝cript部分,本文要優(yōu)化的就是這一部分的代碼,我們?cè)賮?lái)細(xì)看下script中的代碼結(jié)構(gòu):
- props部分占用6行
- data部分占用52行
- created部分占用8行
- mounted部分占用98行
- methods部分占用672行
- emits部分占用6行
- computed部分占用8行
- watch部分占用26行
現(xiàn)在罪魁禍?zhǔn)资莔ethods部分,那么我們只需要把methods部分的代碼拆分出去,單文件代碼量就大大減少了。
優(yōu)化方案
經(jīng)過(guò)上述分析后,我們已經(jīng)知道了問(wèn)題所在,接下來(lái)就跟大家分享下我一開(kāi)始想到的方案以及最終所采用的方案。
直接拆分成文件
一開(kāi)始我覺(jué)得既然methods方法占用的行數(shù)太多,那么我在src下創(chuàng)建一個(gè)methods文件夾,把每個(gè)組件中的methods的方法按照組件名進(jìn)行劃分,創(chuàng)建對(duì)應(yīng)的文件夾,在對(duì)應(yīng)的組件文件夾內(nèi)部,將methods中的方法拆分成獨(dú)立的ts文件,最后創(chuàng)建index.ts文件,將其進(jìn)行統(tǒng)一導(dǎo)出,在組件中使用時(shí)按需導(dǎo)入index.ts中暴露出來(lái)的模塊,如下圖所示:
image-20210114103824562
- 創(chuàng)建methods文件夾
- 把每個(gè)組件中的methods的方法按照組件名進(jìn)行劃分,創(chuàng)建對(duì)應(yīng)的文件夾,即:message-display
- 將methods中的方法拆分成獨(dú)立的ts文件,即:message-display文件夾下的ts文件
- 創(chuàng)建index.ts文件,即:methods下的index.ts文件
index.ts代碼
如下所示,我們將拆分的模塊方法進(jìn)行導(dǎo)入,然后統(tǒng)一export出去
- import compressPic from "@/methods/message-display/CompressPic";
- import pasteHandle from "@/methods/message-display/PasteHandle";
- export { compressPic, pasteHandle };
在組件中使用
最后,我們?cè)诮M件中按需導(dǎo)入即可,如下所示:
- import { compressPic, pasteHandle } from "@/methods/index";
- export default defineComponent({
- mounted() {
- compressPic();
- pasteHandle();
- }
- })
運(yùn)行結(jié)果
當(dāng)我自信滿滿的開(kāi)始跑項(xiàng)目時(shí),發(fā)現(xiàn)瀏覽器的控制臺(tái)報(bào)錯(cuò)了,提示我this未定義,突然間我意識(shí)到將代碼拆分成文件后,this是指向那個(gè)文件的,并沒(méi)有指向當(dāng)前組件實(shí)例,當(dāng)然可以將this作為參數(shù)傳進(jìn)去,但我覺(jué)得這樣并不妥,用到一個(gè)方法就傳一個(gè)this進(jìn)去,會(huì)產(chǎn)生很多冗余代碼,因此這個(gè)方案被我pass了。
使用mixins
前一個(gè)方案因?yàn)閠his的問(wèn)題以失敗告終,在Vue2.x的時(shí)候官方提供了mixins來(lái)解決this問(wèn)題,我們使用mixin來(lái)定義我們的函數(shù),最后使用mixins進(jìn)行混入,這樣就可以在任意地方使用了。
由于mixins是全局混入的,一旦有重名的mixin原來(lái)的就會(huì)被覆蓋,所以這個(gè)方案也不合適,pass。
image-20210114111746208
使用CompositionAPI
上述兩個(gè)方案都不合適,那 么CompositionAPI就剛好彌補(bǔ)上述方案的短處,成功的實(shí)現(xiàn)了我們想要實(shí)現(xiàn)的需求。
我們先來(lái)看看什么是CompositionAPI,正如文檔所述,我們可以將原先optionsAPI中定義的函數(shù)以及這個(gè)函數(shù)需要用到的data變量,全部歸類到一起,放到setup函數(shù)里,功能開(kāi)發(fā)完成后,將組件需要的函數(shù)和data在setup進(jìn)行return。
setup函數(shù)在創(chuàng)建組件之前執(zhí)行,因此它是沒(méi)有this的,這個(gè)函數(shù)可以接收2個(gè)參數(shù): 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
我的組件需要拿到父組件傳過(guò)來(lái)的props中的值,需要通過(guò)emit來(lái)向父組件傳遞數(shù)據(jù),props和context這兩個(gè)參數(shù)正好解決了我這個(gè)問(wèn)題。
setup又是個(gè)函數(shù),也就意味著我們可以將所有的函數(shù)拆分成獨(dú)立的ts文件,然后在組件中導(dǎo)入,在setup中將其return給組件即可,這樣就很完美的實(shí)現(xiàn)了一開(kāi)始我們一開(kāi)始所說(shuō)的的拆分。
實(shí)現(xiàn)思路
接下來(lái)的內(nèi)容會(huì)涉及到響應(yīng)性API,如果對(duì)響應(yīng)式API不了解的開(kāi)發(fā)者請(qǐng)先移步官方文檔。
我們分析出方案后,接下來(lái)我們就來(lái)看看具體的實(shí)現(xiàn)路:
- 在組件的導(dǎo)出對(duì)象中添加setup屬性,傳入props和context
- 在src下創(chuàng)建module文件夾,將拆分出來(lái)的功能代碼按組件進(jìn)行劃分
- 將每一個(gè)組件中的函數(shù)進(jìn)一步按功能進(jìn)行細(xì)分,此處我分了四個(gè)文件夾出來(lái)
- common-methods 公共方法,存放不需要依賴組件實(shí)例的方法
- components-methods 組件方法,存放當(dāng)前組件模版需要使用的方法
- main-entrance 主入口,存放setup中使用的函數(shù)
- split-method 拆分出來(lái)的方法,存放需要依賴組件實(shí)例的方法,setup中函數(shù)拆分出來(lái)的文件也放在此處
- 在主入口文件夾中創(chuàng)建InitData.ts文件,該文件用于保存、共享當(dāng)前組件需要用到的響應(yīng)式data變量
- 所有函數(shù)拆分完成后,我們?cè)诮M件中將其導(dǎo)入,在setup中進(jìn)行return即可
實(shí)現(xiàn)過(guò)程
接下來(lái)我們將上述思路進(jìn)行實(shí)現(xiàn)。
添加setup選項(xiàng)
我們?cè)趘ue組件的導(dǎo)出部分,在其對(duì)象內(nèi)部添加setup選項(xiàng),如下所示:
創(chuàng)建module模塊
我們?cè)趕rc下創(chuàng)建module文件夾,用于存放我們拆分出來(lái)的功能代碼文件。
如下所示,為我創(chuàng)建好的目錄,我的劃分依據(jù)是將相同類別的文件放到一起,每個(gè)文件夾的所代表的含義已在實(shí)現(xiàn)思路進(jìn)行說(shuō)明,此處不作過(guò)多解釋。
創(chuàng)建InitData.ts文件
我們將組件中用到的響應(yīng)式數(shù)據(jù),統(tǒng)一在這里進(jìn)行定義,然后在setup中進(jìn)行return,該文件的部分代碼定義如下,完整代碼請(qǐng)移步: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否則不會(huì)生效
- const messagesContainer = ref
(null); - const msgInputContainer = ref
(null); - const selectImg = ref
(null); - // 響應(yīng)式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與當(dāng)前實(shí)例
- let $store = useStore();
- let currentInstance = getCurrentInstance();
- export default function initData(): messageDisplayDataType {
- // 定義set方法,將props中的數(shù)據(jù)寫(xiě)入當(dāng)前實(shí)例
- 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
- }
- }
??細(xì)心的開(kāi)發(fā)者可能已經(jīng)發(fā)現(xiàn),我把響應(yīng)式變量定義在導(dǎo)出的函數(shù)外面了,之所以這么做是因?yàn)閟etup的一些特殊原因,在下面的踩坑章節(jié)我將會(huì)詳解我為什么要這樣做。
在組件中使用
定義完相應(yīng)死變量后,我們就可以在組件中導(dǎo)入使用了,部分代碼如下所示,完整代碼請(qǐng)移步:message-display.vue
- import initData from "@/module/message-display/main-entrance/InitData";
- export default defineComponent({
- setup(props, context) {
- // 初始化組件需要的data數(shù)據(jù)
- 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
- };
- }
- })
我們定義后響應(yīng)式變量后,就可以在拆分出來(lái)的文件中導(dǎo)入initData函數(shù),訪問(wèn)里面存儲(chǔ)的變量了。
在文件中訪問(wèn)initData
我將頁(yè)面內(nèi)所有的事件監(jiān)聽(tīng)也拆分成了文件,放在了EventMonitoring.ts中,在事件監(jiān)聽(tīng)的處理函數(shù)是需要訪問(wèn)initData里存儲(chǔ)的變量的,接下來(lái)我們就來(lái)看下如何訪問(wèn),部分代碼如下所示,完整代碼請(qǐng)移步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();
- // 獲取傳遞的參數(shù)
- const data = initData();
- // 將props改為響應(yīng)式
- const prop = toRefs(props);
- // 獲取data中的數(shù)據(jù)
- 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
; - // 監(jiān)聽(tīng)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發(fā)生改變,清空消息列表數(shù)據(jù)
- senderMessageList.length = 0;
- // 初始化分頁(yè)數(shù)據(jù)
- 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;
- });
- }
正如代碼中那樣,在文件中使用時(shí),拿出initData中對(duì)應(yīng)的變量,需要修改其值時(shí),只需要修改他的value即可。
至此,有關(guān)compositionAPI的基本使用就跟大家講解完了,下面將跟大家分享下我在實(shí)現(xiàn)過(guò)程中所踩的坑,以及我的解決方案。
踩坑分享
今天是周四,我周一開(kāi)始決定使用CompositionAPI來(lái)重構(gòu)我這個(gè)組件的,一直搞到昨天晚上才重構(gòu)完成,前前后后踩了很多坑,正所謂踩坑越多你越強(qiáng),這句話還是很有道理的??。
接下來(lái)就跟大家分享下我踩到的一些坑以及我的解決方案。
dom操作
我的組件需要對(duì)dom進(jìn)行操作,在optionsAPI中可以使用this.$refs.xxx來(lái)訪問(wèn)組件dom,在setup中是沒(méi)有this的,翻了下官方文檔后,發(fā)現(xiàn)需要通過(guò)ref來(lái)定義,如下所示:
{ ulContainer[i] = el }">
訪問(wèn)vuex
在setup中訪問(wèn)vuex需要通過(guò)useStore()來(lái)訪問(wèn),代碼如下所示:
- import { useStore } from "vuex";
- const $store = useStore();
- console.log($store.state.token);
訪問(wèn)當(dāng)前實(shí)例
在組件中需要訪問(wèn)掛載在globalProperties上的東西,在setup中就需要通過(guò)getCurrentInstance()來(lái)訪問(wèn)了,代碼如下所示:
- 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 + "上線"
- });
無(wú)法訪問(wèn)$options
我重構(gòu)的websocket插件是將監(jiān)聽(tīng)消息接收方法放在options上的,需要通過(guò)this.$options.xxx來(lái)訪問(wèn),文檔翻了一圈沒(méi)找到有關(guān)在setup中使用的內(nèi)容,那看來(lái)是不能訪問(wèn)了,那么我只能選擇妥協(xié),把插件掛載在options上的方法放到globalProperties上,這樣問(wèn)題就解決了。
內(nèi)置方法只能在setup中訪問(wèn)
如上所述,我們使用到了getCurrentInstance和useStore,這兩個(gè)內(nèi)置方法還有initData中定義的那些響應(yīng)式數(shù)據(jù),只有在setup中使用時(shí)才能拿到數(shù)據(jù),否則就是null。
我的文件是拆分出去的,有些函數(shù)是運(yùn)行在某個(gè)拆分出來(lái)的文件中的,不可能都在setup中執(zhí)行一遍的,響應(yīng)式變量也不可能全當(dāng)作參數(shù)進(jìn)行傳遞的,為了解決這個(gè)問(wèn)題,我有試過(guò)使用provide注入然后通過(guò)inject訪問(wèn),結(jié)果運(yùn)行后發(fā)現(xiàn)不好使,控制臺(tái)報(bào)黃色警告說(shuō)provide和inject只能運(yùn)行在setup中,我直接裂開(kāi),當(dāng)時(shí)發(fā)了一條沸點(diǎn)求助了下,到了晚上也沒(méi)得到解決方案??。
經(jīng)過(guò)一番求助后,我的好友@前端印象給我提供了一個(gè)思路,成功的解決了這個(gè)問(wèn)題,也就是我上面initData的做法,將響應(yīng)式變量定義在導(dǎo)出函數(shù)的外面,這樣我們?cè)诓鸱殖鰜?lái)的文件中導(dǎo)入initData方法時(shí),里面的變量都是指向同一個(gè)地址,可以直接訪問(wèn)存儲(chǔ)在里面的變量且不會(huì)將其進(jìn)行初始化。
至于getCurrentInstance和useStore訪問(wèn)出現(xiàn)null的情景,還有props、emit的使用問(wèn)題,我們可以在initData的導(dǎo)出函數(shù)內(nèi)部定義set方法,在setup里的方法中獲取到實(shí)例后,通過(guò)set方法將其設(shè)置進(jìn)我們定義的變量中。
至此,問(wèn)題就完美解決了,最后跟大家看下優(yōu)化后的組件代碼,393行??
圖片
image-20210114201837539
項(xiàng)目地址
項(xiàng)目地址:chat-system-github
在線體驗(yàn)地址:chat-system
分享標(biāo)題:使用Vue3的CompositionAPI來(lái)優(yōu)化代碼量
網(wǎng)站鏈接:http://m.5511xx.com/article/cogiioj.html


咨詢
建站咨詢
