vue3中的异步组件

1,129 阅读3分钟

1.背景

异步组件的概念最早在vue2就已经提出,而在vue3中因为增加了函数式编程,因此更加完整。 1.什么是异步组件? 在解释异步组件之前,我们需要先来了解一下webpack拆包的几种方式:async(异步)、initial(同步)、all【对应的配置属性为SplitChunk】 (所谓拆包就是,将什么样的文件的代码进行合并到一个js文件中,如果不拆包那么所有的文件都合并到一个js文件中,那么这个js文件会很大)

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async', //三选一:"initial" 初始化,"all"(默认就是all),"async"(动态加载) 
      minSize: 30000,  // 形成一个新代码块最小的体积,只有 >= minSize 的bundle会被拆分出来
      maxSize: 0, //拆分之前最大的数值,默认为0,即不做限制
      minChunks: 1, //引入次数,如果为2 那么一个资源最少被引用两次才可以被拆分出来
      maxAsyncRequests: 5,// 按需加载的最大并行请求数
      maxInitialRequests: 3, // 一个入口最大并行请求数
      automaticNameDelimiter: '~', // 文件名的连接符
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
};

其中all是默认的,而我们这里要讨论的是splitchunk值为async的时候,即按照异步方式拆包,意味着模块只要是异步方式导入的就需要单独拆出来,那webpack怎么识别它是异步导入的呢?在vue2中使用import()函数引入的vue组件。那么这个组件就是异步组件,拆包时会被拆除单独的包。

2.vue2中的异步组件

使用方式:import()函数是重点标识

<template>
	<component :is="myComponent"></component>
	<button @click="switchCmp">切换组件</button>
</template>
<script>
  export default {
  	components: {
      'myComponent1': () => import('./my-component1'), // 引入方式二 
      'myComponent2': () => import('./my-component2'),
    },
    data() {
    	return {
      	myComponent: myComponent1
      }
    },
    methods: {
    	switchCmp() {
      	this.myComponent = myComponent2;
      }
    }
  }
</script>

3.vue3中的异步组件

vue3中不再使用import函数了,而是使用defineAsyncComponent()定义异步组件函数api,专门用来定义异步组件。

<template>
  <div>
    <h1>App</h1>
    <hr />
    <Component1 />
    <asyncComponent />
    <asyncComponentOptions />
  </div>
</template>

<script setup>
import ErrorPage from "@/components/ErrorPage.vue";
import LoadingPage from "@/components/LoadingPage.vue";
import { defineAsyncComponent } from "vue";

// 同步写法
import Component1 from "@/components/Component1.vue";

// 异步写法
const asyncComponent = defineAsyncComponent(() =>
  import("@/components/Component1.vue")
);
// const asyncComponent = () => import('@/components/Component1.vue') // vue2 写法

// 带配置项异步写法
const asyncComponentOptions = defineAsyncComponent({
  loader: () => import("@/components/Component1.vue"),
  delay: 300,
  timeout: 5000,
  errorComponent: ErrorPage,
  loadingComponent: LoadingPage,
});
</script>

4.动态组件和异步组件的区别

<template>
    <component :is="componentKey" ref="custom"></component>
</template>

import { reactive, ref, shallowReactive, onActivated, defineAsyncComponent} from 'vue';
 const componentKey = ref(null);
 const components: any = shallowReactive({});
 // customModal得到的是包装过的异步组件
 const customModal = defineAsyncComponent(() => import('./modal/CustomModal.vue'));
 componentKey  = customModal

业务中使用: 点击弹窗开关,然后通过cpPath路径判断是否存在对应的id,如果不存在,那么说明没有加载过这个异步组件,此时需要使用inject,拿到modelRegister对应的函数,然后执行:


<component
    v-for="dialog in dialogCompsMap"
    :is="dialog[1].comp"
    :key="dialog[0]"
    v-bind="dialog[1].props"
    v-on="dialog[1].onEmits"
/>
    
    
    
provide(modelRegister, (cpPath: string) => new Promise(resolve => {
   openGlobalLoading()
   const uuid = UUID() // 获取一个uuid
   dialogCompsMap.value.set(uuid, { // 所有的uuid生成后都存在一个Map中,生成key-对象
      comp: defineAsyncComponent( // 对象中【comp属性】存的就是defineAsyncComponent返回的组件
        () => import(`~/components/Common/Dialog/${cpPath}.vue`).then(res => {
          resolve(uuid) 
          // 这里通过resolve(uuid),这样后面可以通过inject('modelRegister'), 执行await 函数拿到uuid,就可以通过uuid拿到map中的对象,进而得到comp组件。
          return res // import的组件值还是原封不动返回
        }).catch(() => {
          message.error('资源有更新,请刷新页面后重试!')
        }).finally(() => {
          closeGlobalLoading()
        }),
      ),
      props: {},
      onEmits: {},
    })

    triggerRef(dialogCompsMap)
  }))
provide(modelUnInstall, (uuid: string) => { // 在dialog组件的unmounted的时候
    if (dialogCompsMap.value.has(uuid)) {
      dialogCompsMap.value.delete(uuid)
    }

    triggerRef(dialogCompsMap)
  })

// 使用弹窗

<a-button type="link" @click="viewAuditStepHandle">
   {{ formNum ? "查看" : "模拟" }}审批链
</a-button>
// 引入动态加载弹窗组件hooks
const { getModelUUID, openModel } = useDynamicDialog()
const viewAuditStepHandle = async () => {
    const uuid = await getModelUUID('AuditStepDialog/AuditStepDialog') // 获取uuid
    // 通过 openModel打开弹窗
    openModel(uuid, {
      getStepRequest: () => getFormProcessList(),
    })
  }
import { inject, onUnmounted } from 'vue'
import {
  modelOpen,
  modelRegister,
  modelUnInstall,
} from '~/config/symbolVariable.config'

export function useDynamicDialog() {
  const provideModelRegister = inject<(...args: any[]) => string | string[]>(
    modelRegister) as (...args: any[]) => string
  const provideModelOpen = inject<(...args: any[]) => string | string[]>(
    modelOpen) as (...args: any[]) => string
  const provideModelUnInstall = inject<(...args: any[]) => string | string[]>(
    modelUnInstall) as (...args: any[]) => string

  const modelMap = new Map<string, string>()

  async function getModelUUID(cpPath: string) {
    let modelUUID = modelMap.get(cpPath)
    if (!modelUUID) {
      const uuid = await provideModelRegister(cpPath)
      console.log('uuid', uuid)
      modelUUID = uuid
      modelMap.set(cpPath, modelUUID)
    }
    console.log('modelUUID', modelUUID)
    return modelUUID
  }

  function openModel(uuid: string, props = {}, onEmits = {}) {
    provideModelOpen(uuid, props, onEmits)
  }

  onUnmounted(() => {
    provideModelUnInstall(modelMap.values())
  })

  return {
    getModelUUID,
    openModel,
  }
}