• 作者:老汪软件技巧
  • 发表时间:2024-08-25 10:06
  • 浏览量:

为什么需要异步组件?

vue作为单页应用在首页加载时长会遇到加载缓慢问题,我们可以利用异步组件实现只有当页面需要渲染该组件时才进行引入。

vue官网的介绍

使用方式

另外一种可以通过对象配置的方式

import { defineAsyncComponent } from 'vue'const AsyncComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    // ...从服务器获取组件
    resolve(/* 获取到的组件 */)
  })
})
// ... 像使用其他一般组件一样使用 `AsyncComp`

第二种

import { defineAsyncComponent } from 'vue'const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
)

对象配置的方式

{
    name: 'OperationsMorningReport',
    component: defineAsyncComponent({
      loader: () => import('../report-template/dispatch-abnormal-tmpl.vue'),
      delay: 0
    })
  }

支持的配置内容

interface AsyncComponentOptions {
  loader: AsyncComponentLoader
  // 自定义加载中组件
  loadingComponent?: Component
  // 加载错误组件
  errorComponent?: Component
  // 延迟加载时长
  delay?: number
  // 设置超时时间
  timeout?: number
  suspensible?: boolean
  // 加载错误回调函数
  onError?: (
    error: Error,
    retry: () => void,
    fail: () => void,
    attempts: number
  ) => any
}

举个例子:

<template>
  <div>
    hello page
    <button @click="showSubComponent = true">展示异步组件button>
    <AsyncComp v-if="showSubComponent"/>
    <subComponent v-if="showSubComponent"/>
  div>
template>
​
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'
const showSubComponent = ref(false)
const AsyncComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    // ...从服务器获取组件
    console.log('async component')
    resolve({
      template: '
Async Component
'
  }) }) }) const subComponent = defineAsyncComponent(() => import('./sub.vue'))
script> ​ <style scoped lang="scss">style> ​

只有在点了按钮之后才会加载sub.vue文件

_异步组件的原理_异步组件是什么

源码学习

异步组件的实现思路:vue源码packages\runtime-core\src\apiAsyncComponent.ts

看defineAsyncComponent函数的定义,传入source即需要异步加载的对象,返回一个组件

export function defineAsyncComponent(source){
    return defineComponent({})
}

defineAsyncComponent函数主要分为3部分

如果我们传的source是个函数,会直接定义loader,如果是个对象,会将loader等一系列参数做解构,默认延迟加载时间是200毫秒

export function defineAsyncComponent<
  T extends Component = { new (): ComponentPublicInstance },
>(source: AsyncComponentLoader | AsyncComponentOptions): T {
  if (isFunction(source)) {
    source = { loader: source }
  }
// 处理参数
  const {
    loader,
    loadingComponent,
    errorComponent,
    delay = 200,
    timeout, // undefined = never times out
    suspensible = true,
    onError: userOnError,
  } = source
​
  let pendingRequest: Promise<ConcreteComponent> | null = null
  let resolvedComp: ConcreteComponent | undefinedlet retries = 0
  // 重试次数
  const retry = () => {
    retries++
    pendingRequest = null
    return load()
  }
​
    // 加载异步组件
  const load = ()=>{}
    // 返回组件
  return defineComponent() as T
}

什么时候会执行到load函数加载异步组件呢?入口是在返回的setup函数中,发现在下面调用了load函数

setup函数是什么时候调用的?当开始展示异步组件的时候,会调用setup函数

return defineComponent({
    name: 'AsyncComponentWrapper',
​
    __asyncLoader: load,
​
    get __asyncResolved() {
      return resolvedComp
    },
​
    setup() {
      const instance = currentInstance!
​
      // already resolved
      if (resolvedComp) {
        return () => createInnerComp(resolvedComp!, instance)
      }
    // 加载失败回调函数
      const onError = (err: Error) => {
        pendingRequest = null
        handleError(
          err,
          instance,
          ErrorCodes.ASYNC_COMPONENT_LOADER,
          !errorComponent /* do not throw in dev if user provided error component */,
        )
      }  
​
      const loaded = ref(false)
      const error = ref()
      const delayed = ref(!!delay)
    // 延迟加载
      if (delay) {
        setTimeout(() => {
          delayed.value = false
        }, delay)
      }
    // 超时处理
      if (timeout != null) {
        setTimeout(() => {
          if (!loaded.value && !error.value) {
            const err = new Error(
              `Async component timed out after ${timeout}ms.`,
            )
            onError(err)
            error.value = err
          }
        }, timeout)
      }
​
      load()
        .then(() => {
        // 加载成功后添加缓存
          loaded.value = true
          if (instance.parent && isKeepAlive(instance.parent.vnode)) {
            // parent is keep-alive, force update so the loaded component's
            // name is taken into account
            instance.parent.effect.dirty = true
            queueJob(instance.parent.update)
          }
        })
        .catch(err => {
          onError(err)
          error.value = err
        })
​
      return () => {
        if (loaded.value && resolvedComp) {
          return createInnerComp(resolvedComp, instance)
        } else if (error.value && errorComponent) {
          return createVNode(errorComponent, {
            error: error.value,
          })
        } else if (loadingComponent && !delayed.value) {
          return createVNode(loadingComponent)
        }
      }
    },
  })

load函数,load函数中执行了loader,而loader就是我们传入的异步组件,如果是个函数则执行传入的函数,

对于第一种方式创建的异步组件,直接返回template配置的内容

对于import引入的组件,引用的文件

const load = (): Promise<ConcreteComponent> => {
    let thisRequest: Promise<ConcreteComponent>
    return (
      pendingRequest ||
      (thisRequest = pendingRequest =
        loader()
          .catch(err => {
            // 异常处理
          })
          .then((comp: any) => {
            if (thisRequest !== pendingRequest && pendingRequest) {
              return pendingRequest
            }
            
            // interop module default
            if (
              comp &&
              (comp.__esModule || comp[Symbol.toStringTag] === 'Module')
            ) {
              comp = comp.default
            }
            
            resolvedComp = comp
            return comp
          }))
    )
  }