ホームページ > 記事 > ウェブフロントエンド > Vue3 が CompositionAPI を使用してコード サイズを最適化する方法
まず、コンポーネントの全体的なコード構造を見てみましょう:
テンプレート部分は 267 行を占めます
スクリプト部分889 行を占めます
スタイル部分は外部参照のために 1 行を占めます
最適化計画
上記の分析後、問題はすでにわかっています。次に、最初に考えた計画と採用した最終的な解決策を共有します。 。 プラン。ファイルに直接分割
最初は、methods メソッドが多すぎる行数を占めるため、src の下にmethods フォルダーを作成し、それぞれのメソッドを配置しました。各コンポーネント内のメソッドがコンポーネント名に従って分割され、対応するフォルダーが作成されます。対応するコンポーネント フォルダー内で、メソッド内のメソッドが独立した ts ファイルに分割されます。最後に、index.ts ファイルが作成され、処理されます。統一された方法で、コンポーネントで使用されるときにオンデマンドでindex.tsで公開されるモジュールをインポートします。は次のとおりです。分割モジュールのメソッドをインポートします。
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 はそのファイルを指し、現在のコンポーネント インスタンスを指しません。もちろん、これをパラメータとして渡すこともできますが、これは適切ではないと思います。メソッドを使用する場合、これを入力すると冗長なコードが大量に生成されるため、この計画はパスしました。
ミックスインの使用
以前の解決策はこの問題のため失敗に終わりました。Vue2.x では、この問題を解決するためにミックスインが正式に提供されました。関数を定義し、最後にミックスインを使用して関数を混合し、どこでも使用できるようにします。
ミックスインはグローバルに混在するため、同じ名前のミックスインが存在すると元のミックスインは上書きされるため、この解決策は適切ではありません。パスします。CompositionAPI を使用する
上記の 2 つのソリューションが適切でない場合、CompositionAPI は上記のソリューションの欠点を補い、達成したいニーズをうまく達成します。 。
まず、CompositionAPI とは何かを見てみましょう。ドキュメントに記載されているように、元の optionsAPI で定義された関数と、この関数が使用する必要があるデータ変数をセットアップ関数にグループ化できます。関数の開発が完了したら、コンポーネントに必要な関数とデータをセットアップで返します。 setup 関数はコンポーネントを作成する前に実行されるため、これはありません。この関数は props と context の 2 つのパラメータを受け取ることができます。それらの型は次のように定義されています:interface Data { [key: string]: unknown } interface SetupContext { attrs: Data slots: Slots emit: (event: string, ...args: unknown[]) => void } function setup(props: Data, context: SetupContext): DataMy コンポーネント親コンポーネントから渡された props の値を取得するには、emit を通じて親コンポーネントにデータを渡す必要があります。props と context の 2 つのパラメーターがこの問題を解決してくれました。 setup は関数です。つまり、すべての関数を独立した ts ファイルに分割し、それらをコンポーネントにインポートし、セットアップ時にコンポーネントに返すことができます。これは完璧です。これにより、前述の分割が実現されます。初めに。
実装アイデア
次のコンテンツにはレスポンシブ API が含まれます。開発者がレスポンシブ API に慣れていない場合は、まず公式ドキュメントを参照してください。
ソリューションを分析した後、具体的な実装パスを見てみましょう: setup 属性をコンポーネントのエクスポート オブジェクトに追加し、props とcontextsplit-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方法将其设置进我们定义的变量中。
以上がVue3 が CompositionAPI を使用してコード サイズを最適化する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。