Mula-mula mari kita lihat struktur kod keseluruhan komponen:
Bahagian templat menduduki 267 baris
Bahagian skrip menduduki 889 baris
Bahagian gaya menduduki 1 baris untuk rujukan luaran
Pesalahnya ialah bahagian skrip ini kami ingin mengoptimumkan dalam artikel ini. Mari kita lihat dengan lebih dekat struktur kod dalam skrip di bawah:
Bahagian prop menduduki 6 baris
Bahagian data menduduki 52 baris
Bahagian yang dicipta menduduki 8 baris
Bahagian yang dipasang menduduki 98 baris
Bahagian kaedah menduduki 672 baris
Bahagian pemancar menduduki 6 baris
Bahagian yang dikira menduduki 8 baris
Bahagian jam tangan menduduki 26 baris
Sekarang penyebabnya ialah bahagian kaedah, maka kita hanya perlu membahagikan kod dalam bahagian kaedah, dan jumlah kod dalam satu fail akan dikurangkan dengan banyak.
Pelan pengoptimuman
Selepas analisis di atas, kita sudah tahu masalahnya Seterusnya, saya akan berkongsi dengan anda rancangan yang saya fikirkan pada asalnya dan penyelesaian akhir yang saya pakai .
Pisah terus ke dalam fail
Pada mulanya saya fikir kerana kaedah kaedah mengambil terlalu banyak baris, saya mencipta folder kaedah di bawah src dan meletakkan setiap Kaedah dalam setiap komponen dibahagikan mengikut nama komponen, dan folder yang sepadan dibuat Dalam folder komponen yang sepadan, kaedah dalam kaedah dibahagikan kepada fail ts bebas Akhirnya, fail index.ts dibuat dan diproses cara bersatu dan modul import terdedah dalam index.ts atas permintaan apabila digunakan dalam komponen.
Buat folder kaedah
Bahagikan kaedah dalam setiap komponen mengikut nama komponen dan buat folder yang sepadan, iaitu :paparan mesej
Pisah kaedah dalam kaedah kepada fail ts bebas, iaitu: fail ts di bawah folder paparan mesej
Buat index.ts fail, iaitu: fail index.ts di bawah kaedah
kod index.ts
adalah seperti berikut, kami Import kaedah modul split , dan kemudian mengeksportnya secara seragam
import compressPic from "@/methods/message-display/CompressPic"; import pasteHandle from "@/methods/message-display/PasteHandle"; export { compressPic, pasteHandle };
dan menggunakannya dalam komponen
Akhirnya, kita boleh mengimportnya seperti yang diperlukan dalam komponen, seperti Paparan berikut:
import { compressPic, pasteHandle } from "@/methods/index"; export default defineComponent({ mounted() { compressPic(); pasteHandle(); } })
Hasil berjalan
Apabila saya mula menjalankan projek dengan yakin, saya mendapati bahawa konsol penyemak imbas melaporkan ralat, menggesa saya bahawa ini tidak ditentukan Tiba-tiba Saya menyedarinya selepas itu membelah kod ke dalam fail, ini menunjuk ke fail itu dan tidak menunjuk kepada contoh komponen semasa Sudah tentu, anda boleh lulus ini sebagai parameter, tetapi saya rasa ini tidak sesuai Jika anda menggunakan kaedah, lulus sahaja ia. Memasukkan ini akan menghasilkan banyak kod berlebihan, jadi saya lulus pelan ini.
Gunakan mixin
Penyelesaian sebelum ini gagal kerana masalah ini Dalam Vue2.x, mixin disediakan secara rasmi untuk menyelesaikan masalah ini . Tentukan fungsi kita dan akhirnya gunakan mixin untuk mencampurkannya supaya ia boleh digunakan di mana-mana sahaja.
Memandangkan mixin bercampur secara global, setelah ada mixin dengan nama yang sama, yang asal akan ditimpa, jadi penyelesaian ini tidak sesuai, lulus.
Gunakan CompositionAPI
Kedua-dua penyelesaian di atas tidak sesuai, maka CompositionAPI hanya menebus kekurangan penyelesaian di atas dan berjaya mencapai keperluan yang ingin kita capai.
Mari kita lihat dahulu apa itu CompositionAPI Seperti yang dinyatakan dalam dokumen, kita boleh mengumpulkan semua fungsi yang ditakrifkan dalam optionsAPI asal dan pembolehubah data yang perlu digunakan oleh fungsi ini ke dalam fungsi persediaan pembangunan fungsi selesai, kembalikan fungsi dan data yang diperlukan oleh komponen dalam persediaan.
Fungsi persediaan dilaksanakan sebelum mencipta komponen, jadi ia tidak mempunyai ini. Fungsi ini boleh menerima 2 parameter: prop dan konteksnya ditakrifkan seperti berikut:
interface Data { [key: string]: unknown } interface SetupContext { attrs: Data slots: Slots emit: (event: string, ...args: unknown[]) => void } function setup(props: Data, context: SetupContext): Data
Komponen saya keperluan Untuk mendapatkan nilai dalam prop yang diluluskan daripada komponen induk, anda perlu menghantar data kepada komponen induk melalui emit. Kedua-dua parameter prop dan konteks hanya menyelesaikan masalah ini untuk saya.
Persediaan ialah fungsi, yang bermaksud kita boleh membahagikan semua fungsi kepada fail ts bebas, kemudian mengimportnya dalam komponen dan mengembalikannya kepada komponen dalam persediaan Ini sempurna pada permulaannya.
Idea Pelaksanaan
Kandungan berikut akan melibatkan API responsif Jika pembangun tidak biasa dengan API responsif, sila pergi ke dokumentasi rasmi dahulu.
Selepas kami menganalisis penyelesaian, mari kita lihat laluan pelaksanaan khusus:
Tambah atribut persediaan pada objek eksport komponen, masukkan prop dan konteks
Buat folder modul di bawah src dan bahagikan kod fungsi yang dipisahkan kepada komponen
Bahagikan kod fungsi dalam setiap komponen Fungsinya lebih jauh dibahagikan mengikut fungsi. Di sini saya telah membahagikannya kepada empat folder
kaedah umum kaedah biasa, yang menyimpan kaedah yang tidak bergantung pada contoh komponen
kaedah komponen kaedah komponen, menyimpan kaedah yang perlu digunakan dalam templat komponen semasa
pintu masuk utama pintu masuk utama, menyimpan fungsi yang digunakan dalam persediaan
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方法将其设置进我们定义的变量中。
Atas ialah kandungan terperinci Cara Vue3 menggunakan CompositionAPI untuk mengoptimumkan saiz kod. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!