>  기사  >  웹 프론트엔드  >  Vue3가 CompositionAPI를 사용하여 코드 크기를 최적화하는 방법

Vue3가 CompositionAPI를 사용하여 코드 크기를 최적화하는 방법

WBOY
WBOY앞으로
2023-05-28 16:55:231104검색

컴포넌트의 전체 코드 구조를 먼저 살펴보겠습니다.

  • 템플릿 부분은 267라인을 차지합니다.

  • 스크립트 부분은 889라인을 차지합니다.

  • 스타일 부분은 외부 참조를 위해 1라인을 차지합니다

범인은 스크립트 부분입니다. 이번 글에서는 코드의 이 부분을 최적화하겠습니다. 스크립트의 코드 구조를 자세히 살펴보겠습니다.

  • props 부분은 6줄을 차지합니다.

  • 데이터 부분은 52줄을 차지합니다.

  • 생성된 부분은 8라인을 차지합니다

  • 마운트된 부분은 98라인을 차지합니다

  • 메소드 부분은 672라인을 차지합니다

  • 방출하는 부분은 6라인을 차지합니다

  • 계산된 부분은 8줄을 차지합니다

  • watch 부분이 26줄을 차지합니다

이제 범인은 메소드 부분이므로 메소드 부분에서만 코드를 분할하면 되고, 한 파일에 들어가는 코드의 양이 대폭 줄어들게 됩니다.

최적화 계획

위의 분석을 통해 우리는 이미 문제를 알고 있습니다. 다음으로 처음에 생각했던 계획과 최종 채택한 계획을 공유하겠습니다.

직접 파일로 나누기

처음에는 메소드 메소드가 너무 많은 줄을 차지한다고 생각하여 src 아래에 메소드 폴더를 생성하고 컴포넌트 이름에 따라 각 컴포넌트의 메소드를 나누어 생성했습니다. 해당 폴더, 해당 컴포넌트 폴더 내의 메소드를 독립적인 ts 파일로 분할하고, 마지막으로 index.ts 파일을 생성하고, 균일하게 내보내고, .ts에 노출된 컴포넌트 모듈에서 사용할 때 요청 시 인덱스를 가져옵니다.

  • 메서드 폴더 만들기

  • 구성 요소 이름에 따라 각 구성 요소의 메서드를 나누고 해당 폴더를 만듭니다. 즉, message-display

  • 메서드의 메서드를 독립 TS 파일로 분할합니다. 즉, 다음과 같습니다. message-display 폴더 아래의 ts 파일

  • index.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();     } })

실행 결과

자신있게 말할 때 프로젝트를 실행하기 시작했는데 브라우저 콘솔에서 이것이 정의되지 않았다는 메시지가 표시되는 오류를 발견했습니다. 갑자기 코드를 파일로 분할한 후 이것이 해당 파일이 아니라 해당 파일을 가리킨다는 것을 깨달았습니다. 이를 현재 구성 요소 인스턴스에 대한 매개 변수로 사용하는 것은 적절하지 않다고 생각하여 메서드에 전달하면 중복된 코드가 많이 생성되므로 이 솔루션을 전달했습니다.

Mixins 사용

이 문제로 인해 이전 솔루션이 실패로 끝났습니다. Vue2.x에서는 이 문제를 해결하기 위해 mixin을 공식적으로 제공했으며, 최종적으로 mixin을 사용하여 혼합했습니다. 어디서나 사용할 수 있습니다.

믹스인은 전역적으로 혼합되기 때문에 같은 이름의 믹스인이 있으면 원래 것을 덮어쓰게 되므로 이 솔루션은 적합하지 않습니다.

CompositionAPI 사용

위의 두 솔루션 중 어느 것도 적합하지 않은 경우 CompositionAPI는 위 솔루션의 단점을 보완하고 우리가 달성하려는 요구 사항을 성공적으로 달성합니다.

먼저 CompositionAPI가 무엇인지 살펴보겠습니다. 문서에 나와 있듯이 원래 optionsAPI에 정의된 모든 함수와 이 함수가 사용해야 하는 데이터 변수를 setup 함수로 그룹화하면 함수 개발이 완료됩니다. . 마지막으로 설정에서 구성요소에 필요한 기능과 데이터를 반환합니다.

설정 함수는 구성 요소를 생성하기 전에 실행되므로 이 함수는 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를 전달받아야 합니다. 상위 구성요소의 값은 내보내기를 통해 상위 구성요소에 전달되어야 합니다. props와 context라는 두 매개변수가 이 문제를 해결해 줍니다.

setup은 함수입니다. 즉, 모든 함수를 독립적인 ts 파일로 분할한 다음 이를 구성 요소로 가져오고 설정의 구성 요소로 반환할 수 있습니다. 이것은 우리가 말한 분할부터 시작해 보겠습니다. 초반쯤.

구현 아이디어

다음 콘텐츠에는 반응형 API가 포함됩니다. 개발자가 반응형 API를 이해하지 못하는 경우 먼저 공식 문서로 이동하세요.

솔루션을 분석한 후 구체적인 구현 경로를 살펴보겠습니다.

  • 구성 요소의 내보내기 개체에 설정 속성을 추가하고 props 및 context를 전달합니다.

  • src 아래에 모듈 폴더를 만듭니다. 분리된 기능 코드를 컴포넌트

  • 로 나누고 각 컴포넌트의 기능을 기능별로 더 세분화합니다. 여기서는 4개의 폴더로 나눴습니다.

    • common-methods public 메소드, 컴포넌트에 의존할 필요가 없는 메소드를 저장합니다. 인스턴스

    • comComponents-methods 컴포넌트 메소드, 현재 컴포넌트 템플릿에서 사용해야 하는 메소드를 저장합니다

    • main-entrance 메인 입구, 설정에 사용되는 기능을 저장합니다

    • 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方法将其设置进我们定义的变量中。

위 내용은 Vue3가 CompositionAPI를 사용하여 코드 크기를 최적화하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제