3-8 Vue 组件高级应用和插件

38 阅读3分钟

原文链接(格式更好):《3-8 Vue 组件高级应用和插件》

组件高级应用

动态组件

官方文档:cn.vuejs.org/guide/essen…

基本语法

<template>
  <div>
    <!-- 将根据 comName 的值渲染对应的组件 -->
    <component :is="comName" />
  </div>
</template>

<script setup lang="ts">
  import Content1 from './Content1'
  const comName = Content1
</script>

使用场景

异步动态组件

官方文档:cn.vuejs.org/guide/compo…

<!-- <template> -->
  <div>
    <button @click="handleChange">切换</button>

    <!-- 将根据 comName.component 的值渲染对应的组件 -->
    <component :is="comName.component" />
  </div>
</template>

<script setup lang="ts">
  import { defineAsyncComponent } from 'vue'

  const comName = reactive({
    name: '',
    component: ''
  })

  const handleChange = () => {
    comName.name = comName.name === 'Content1' ? 'Content2' : 'Content1'

    // defineAsyncComponent 异步加载,当需要的时候才进行加载
    comName.component = defineAsyncComponent(() => import(`./${comName.name}.vue`))
  }
</script>

动态组件切换时,生命周期也是正常的销毁、创建,不会保留。

KeepAlive

官方文档:cn.vuejs.org/guide/built…

作用:在多个组件间动态切换时缓存被移除的组件实例,不会触发创建、销毁的生命周期。

配合动态组件使用更佳

<template>
  <div>
    <button @click="handleChange">切换</button>

    <!-- 将根据 comName.component 的值渲染对应的组件 -->
    <KeepAlive>
      <component :is="comName.component" />
    </KeepAlive>
  </div>
</template>

<script setup lang="ts">
  import { defineAsyncComponent } from 'vue'

  const comName = reactive({
    name: '',
    component: ''
  })

  const handleChange = () => {
    comName.name = comName.name === 'Content1' ? 'Content2' : 'Content1'

    // defineAsyncComponent 异步加载,当需要的时候才进行加载
    comName.component = defineAsyncComponent(() => import(`./${comName.name}.vue`))
  }
</script>

由于配合defineAsyncComponent使用,切换时将不会销毁组件,但每次还是会“新”创建,即不会有“销毁”的生命周期。

其他内置组件

官方文档:cn.vuejs.org/api/built-i…

Teleport - 传送门

官方文档:cn.vuejs.org/guide/built…

作用:将其插槽内容渲染到 DOM 中的另一个位置。

<teleport to="body">
  <h1>我将显示在 body 元素内</h1>
</teleport>

Transition - 过渡态

官方文档:cn.vuejs.org/guide/built…

作用:制作基于状态变化的过渡和动画

<button @click="show = !show">Toggle</button>
<Transition>
  <p v-if="show">hello</p>
</Transition>
.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}

Suspense - 悬停

官方文档:cn.vuejs.org/guide/built…

作用:用来在组件树中协调对异步依赖的处理

插件

基本使用

// custom.js
export default {
  install(app, options) {
    console.log(app, options)
    
    // 其他业务逻辑...    
  }
}

// main.js
app.use(customPlugin, { xx: 1 })

场景使用

  • 注册全局的组件
  • 注册全局的方法,通过挂载到 Vue 的原型链上实现
  • 注册全局的自定义指令
// custom.js
export default {
  install(app, options) {
    console.log(app, options)
    
    // 其他业务逻辑...    
    
    // app.component('MyCom', MyCom) 注册组件
    // app.config.globalProperties.$xx = xx 注册到原型链上
    // app.directive('auth', (el, binding) => {}) 注册自定义指令
  }
}

面试题

手写异步组件

import { type Component, ref } from 'vue'

// Vue 源码中,异步组件的返回值是 Promise<Component>
type AsyncComponentLoader = () => Promise<Component>

// Vue 源码中,异步组件的配置项
interface AsyncComponentOptions {
  loader: AsyncComponentLoader
  loadingComponent?: Component
  errorComponent?: Component
  delay?: number
  timeout?: number
  suspensible?: boolean
  onError?: (error: Error, retry: () => void, fail: () => void, attempts: number) => any
}

/**
 * 定义异步组件
 *
 * @param source 异步组件的加载器或配置对象
 * @returns 返回一个包装后的组件
 */
export function defineAsyncComponent(
  source: AsyncComponentLoader | AsyncComponentOptions
): Component {
  // --------------- 以下为手写的代码 ---------------

  // 如果source是一个函数,则将其视为loader
  if (typeof source === 'function') {
    source = { loader: source }
  }

  // 如果source没有loader属性,则抛出错误
  if (!source.loader) {
    throw new Error('Loader is required for async component')
  }

  // 提取loader、delay和timeout属性
  const { loader, delay = 0, timeout = 0, loadingComponent, errorComponent } = source
  let InnerComp: Component

  return {
    name: 'AsyncComponentWrapper',
    setup() {
      // 定义loaded、loading和error的响应式引用
      const loaded = ref(false)
      const loading = ref(false)
      const error = ref<Error | null>(null) // 添加类型注解并初始化为null

      // 定义loader函数,用于异步加载组件
      const loaderFn = async () => {
        // 标记为async以支持await语法
        loading.value = true
        try {
          // 使用await等待加载完成,将加载完成的组件赋值给InnerComp
          InnerComp = await loader() // 使用await等待加载完成
          loaded.value = true
        } catch (e) {
          // 如果发生错误,将错误赋值给error引用,并使用Error对象表示未知错误
          error.value = e instanceof Error ? e : new Error('Unknown error')
        } finally {
          loading.value = false // 只设置loading为false,不改变loaded的值
        }

        let timer: number | undefined
        if (timeout) {
          timer = setTimeout(() => {
            if (!loaded.value) {
              // 如果超时且组件未加载完成,则将超时错误赋值给error引用
              error.value = new Error('timeout') // 使用Error对象表示超时错误
            } else {
              clearTimeout(timer)
            }
          }, timeout)
        }
      }

      // 如果存在延迟,则使用setTimeout延迟执行loader函数,否则直接执行loader函数
      if (delay) setTimeout(loaderFn, delay)
      else loaderFn()

      return () => {
        if (loaded.value) {
          // 如果组件已加载完成,则返回加载完成的组件类型InnerComp
          return { type: InnerComp }
        } else if (error.value) {
          // 这里可以添加错误处理逻辑,例如显示一个错误组件或提示信息
          return errorComponent || '错误' // 假设存在一个错误组件ErrorComponent
        } else {
          // 在组件加载过程中显示一个加载指示器或占位符
          return loadingComponent || '加载中' // 假设存在一个加载指示器组件LoadingIndicator
        }
      }
    }
  }
}