vue - 进阶

响应式: 进阶

customRef()

创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。

  • 类型

    function customRef<T>(factory: CustomRefFactory<T>): Ref<T>
    
    type CustomRefFactory<T> = (
      track: () => void,
      trigger: () => void
    ) => {
      get: () => T
      set: (value: T) => void
    }
    
  • 详细信息

    customRef() 预期接收一个工厂函数作为参数,这个工厂函数接受 tracktrigger 两个函数作为参数,并返回一个带有 getset 方法的对象。

    一般来说,track() 应该在 get() 方法中调用,而 trigger() 应该在 set() 中调用。然而事实上,你对何时调用、是否应该调用他们有完全的控制权。

  • 示例

    创建一个防抖 ref,即只在最近一次 set 调用后的一段固定间隔后再调用:

    import { customRef } from 'vue'
    
    export function useDebouncedRef(value, delay = 200) {
      let timeout
      return customRef((track, trigger) => {
        return {
          get() {
            track() //告诉Vue数据msg很重要你要对msg进行持续关注,一旦msg变化就去更新
            return value
          },
          set(newValue) {
            clearTimeout(timeout)
            timeout = setTimeout(() => {
              value = newValue
              trigger() //通知vue,数据变化了
            }, delay)
          }
        }
      })
    }
    

    在组件中使用:

    <script setup>
    import { useDebouncedRef } from './debouncedRef'
    const text = useDebouncedRef('hello')
    </script>
    
    <template>
      <input v-model="text" />
    </template>
    

内置组件

Teleport(瞬间移动组件)

Teleport 直译是 “传送”,作用是 将组件的部分内容 “传送” 到 DOM 树中的指定位置,而不受父组件的 DOM 层级限制。这在处理模态框、弹窗、通知等组件时非常有用,可避免父组件的样式(如 overflow: hiddenz-index)对其产生影响。

核心作用

打破组件的 DOM 层级限制,让子元素渲染到页面的任意位置(通常是 <body> 或专门的容器),解决样式隔离和层级覆盖问题。

使用语法

<teleport to="目标DOM选择器">
  <!-- 需要传送的内容 -->
  <div class="modal">这是一个弹窗</div>
</teleport>
  • to:必填属性,指定目标位置(如 body#app.modal-container 等有效的 CSS 选择器)。

示例:弹窗组件

<!-- Modal.vue -->
<template>
  <teleport to="body">
    <div class="modal-overlay">
      <div class="modal-content">
        <h3>弹窗标题</h3>
        <p>弹窗内容</p>
        <button @click="close">关闭</button>
      </div>
    </div>
  </teleport>
</template>

<script setup>
const close = () => {
  // 关闭逻辑
};
</script>

<style>
.modal-overlay {
  position: fixed; /* 不受父组件定位影响 */
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0,0,0,0.5);
  z-index: 999; /* 确保在最上层 */
}
</style>

特点

  1. 内容仍属于原组件:虽然 DOM 位置变了,但组件的生命周期、事件处理、数据绑定仍受原组件控制(如 close 方法仍能访问原组件的状态)。
  2. 条件渲染:可配合 v-if 控制是否渲染(如 <teleport to="body" v-if="showModal">)。
  3. 多个 Teleport 到同一目标:目标位置会按顺序追加内容,不会覆盖。

Suspense(异步加载管理器)- 实验阶段

Suspense 用于 管理异步组件或带有异步操作的组件,提供 “加载中” 和 “加载完成 / 失败” 的状态处理,简化异步内容的渲染逻辑。

核心作用

等待异步操作(如异步组件加载、setup 中的异步请求)完成后再渲染组件,并在等待期间显示占位内容(如加载动画)。

使用前提

  • 异步组件:通过 defineAsyncComponent 定义的组件。
  • 组件的 setup 函数返回 Promise(或使用 await 异步获取数据)。

使用语法

<suspense>
  <!-- 异步内容(成功状态) -->
  <template #default>
    <AsyncComponent /> <!-- 异步组件或带异步操作的组件 -->
  </template>
  
  <!-- 加载中状态(等待时显示) -->
  <template #fallback>
    <div>加载中...</div>
  </template>
</suspense>
  • #default:异步操作完成后渲染的内容。
  • #fallback:异步操作等待期间显示的占位内容。

示例 1:异步组件

<!-- 父组件 -->
<template>
  <suspense>
    <template #default>
      <AsyncUser /> <!-- 异步加载的组件 -->
    </template>
    <template #fallback>
      <div>加载用户组件中...</div>
    </template>
  </suspense>
</template>

<script setup>
import { defineAsyncComponent } from 'vue';

// 定义异步组件(动态导入)
const AsyncUser = defineAsyncComponent(() => import('./AsyncUser.vue'));
</script>

示例 2:组件内异步数据

<!-- AsyncData.vue(子组件) -->
<template>
  <div>用户数据:{{ user.name }}</div>
</template>

<script setup>
// setup 中使用 await 异步获取数据
const fetchUser = async () => {
  const res = await fetch('/api/user');
  return res.json();
};

const user = await fetchUser(); // 异步操作
</script>

<!-- 父组件中使用 -->
<template>
  <suspense>
    <template #default>
      <AsyncData />
    </template>
    <template #fallback>
      <div>加载数据中...</div>
    </template>
  </suspense>
</template>

注意事项

  1. 错误处理Suspense 本身不处理错误,需配合 onErrorCaptured 或错误边界组件(Error Boundary)捕获异步操作中的错误。
  2. 仅支持顶层异步Suspense 只能捕获其直接子组件的异步操作,深层嵌套组件的异步逻辑不会触发 fallback
  3. 与路由结合:在 Vue Router 中,可将异步组件作为路由组件,配合 Suspense 实现路由切换时的加载状态。

总结

特性 核心功能 典型场景
Teleport 将内容渲染到指定 DOM 位置 模态框、弹窗、通知组件
Suspense 管理异步操作的加载状态 异步组件、数据请求的加载提示

两者均为 Vue 3 新增特性,Teleport 解决了 DOM 层级限制问题,Suspense 简化了异步内容的状态管理,合理使用可显著提升组件的灵活性和用户体验。

全局API

app.component()

如果同时传递一个组件名字符串及其定义,则注册一个全局组件;如果只传递一个名字,则会返回用该名字注册的组件 (如果存在的话)。

  • 类型

    interface App {
      component(name: string): Component | undefined
      component(name: string, component: Component): this
    }
    
  • 示例

    import { createApp } from 'vue'
    
    const app = createApp({})
    
    // 注册一个选项对象
    app.component('MyComponent', {
      /* ... */
    })
    
    // 得到一个已注册的组件
    const MyComponent = app.component('MyComponent')
    

app.config

每个应用实例都会暴露一个 config 对象,其中包含了对这个应用的配置设定。你可以在挂载应用前更改这些属性 (下面列举了每个属性的对应文档)。

import { createApp } from 'vue'

const app = createApp(/* ... */)

console.log(app.config)
posted @ 2025-09-15 15:11  【唐】三三  阅读(13)  评论(0)    收藏  举报