Skip to content

Vue3

[TOC]

索引

指令

插值语法

  • mustache{ {variable} },最基础的数据绑定方式,用于将数据动态渲染为纯文本内容。

文本渲染

  • v-textdataProperty,用于将数据作为纯文本动态渲染到元素中。
  • v-htmldataProperty,用于将数据作为原始 HTML 解析并渲染到元素中。
  • v-pre,用于跳过指定元素及其子元素的编译过程,直接保留原始内容。

绑定

  • v-bind表达式,用于动态绑定HTML属性、组件prop或DOM属性到Vue实例的数据。
  • v-on表达式,用于监听 DOM 事件或自定义事件,并执行对应的 JavaScript 代码或方法。
  • v-model表达式,用于在表单输入元素或自定义组件上实现双向数据绑定,是 v-bind 和 v-on 的语法糖。

条件渲染

  • v-show布尔表达式,用于通过切换 CSS 的 display 属性来控制元素的显示与隐藏。
  • v-if、v-else、v-else-if条件表达式,用于实现条件渲染,根据表达式的真假动态控制元素的渲染或销毁。元素会从DOM中移除。

列表渲染

  • v-for表达式,用于基于数据源(数组、对象、数值范围)循环渲染多个元素或组件。
  • key唯一标识,用于标识虚拟 DOM 节点身份的特殊属性,它在 Vue 的响应式更新机制中起到优化渲染性能维护组件状态的关键作用。

其他

  • v-once,用于标记元素或组件仅渲染一次,后续数据变化时不再更新。
  • v-cloak,用于在 Vue 实例完成编译前隐藏未编译的模板内容,避免页面加载时出现原始模板符号({ { } })的短暂闪烁。必需配合 CSS 样式,CSS 必须全局生效

全局API

应用实例

  • createApp()(rootComponent, rootProps?),用于创建一个 Vue 应用实例,是 Vue 3 应用开发的入口函数。
  • app.use()(plugin,...options?),用于为 Vue 应用安装插件(如 Vue Router、Pinia等)或自定义功能扩展。
  • app.mount()(containerSelector | element),将 Vue 应用实例挂载到 DOM 元素。
  • app.component()(name,component?),用于全局注册或获取组件。
  • app.directive()(name, directive?),用于注册或获取全局自定义指令。
  • app.mixin()(mixin),用来全局注册一个混入对象。会将该对象的所有组件选项(data,methods,...)合并到每个组件中。

通用

  • nextTick()(callback?),通常用来在下一次 DOM 更新刷新后执行某些操作。

组合式API

setup

  • setup()(props?, context?),是组件逻辑的入口点,用于替代 Vue2 的 data、methods、computed 等选项。

响应式

  • ref()<T>(initialValue:T),用来定义一个响应式的、可更改的 ref 对象,该对象可以通过.value访问其内部值。
  • reactive()<T extends object>(initialObject:T),用于创建深度响应式的对象或数组。底层通过Proxy实现。
  • computed()<T>(getter | options),用于声明基于响应式依赖的计算属性,适合处理需要动态计算的逻辑,且结果会被缓存。
  • watch()<T>(source,callback,options?),用于监听响应式数据变化并执行回调函数,适合处理异步操作、复杂逻辑或需要观察特定数据变动的场景。
  • watchEffect()(effect,options?),会立即执行并跟踪函数内部访问的所有响应式数据。任何响应式数据发生变化时,回调函数会重新执行。
  • readonly()<T extends object>(object:T),用于将一个响应式对象或普通对象变为只读对象,防止其被修改。
  • shallowReadonly()(target),用于创建一个浅层只读代理对象,其顶层属性为只读,但嵌套对象仍保持可变。

生命周期钩子

  • onBeforeMount()(callback),注册一个钩子,在组件被挂载之前被调用。
  • onMounted()(callback),注册一个钩子,在组件挂载完成后执行。
  • onBeforeUpdate()(callback),注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。
  • onUpdated()(callback),注册一个钩子,在组件因为响应式状态变更而更新其 DOM 树之后调用。
  • onBeforeUnmount()(callback),注册一个钩子,在组件实例被卸载之前调用。
  • onUnmounted()(callback),注册一个钩子,在组件实例被卸载之后调用。
  • onActivated()(callback),注册一个钩子,若组件实例是<KeepAlive>缓存树的一部分,当组件被插入到DOM中时调用。
  • onDeactivated()(callback),注册一个钩子,若组件实例是<KeepAlive>缓存树的一部分,当组件从DOM中被移除时调用。

依赖注入

  • provide()<T>(key,value),提供一个值,可以被后代组件注入。
  • inject()<T>(key,defaultValue?),注入一个由祖先组件或整个应用通过app.provide()提供的值。

script setup

  • <script setup>,是组合式 API 的编译时语法糖,旨在简化组件逻辑的编写,提高代码的可读性和开发效率。
  • defineProps()(propsDefinition),用于在script setup中声明组件的props。
  • defineEmits()(emitsDefinition),在script setup中声明组件可触发的事件。
  • defineExpose()(exposed),在script setup中显式暴露组件实例的公共属性。
  • defineSlots()()@vue3.3,是 script setup 中引入的类型声明函数,用于显式定义组件的插槽类型,为 TypeScript 提供类型检查和智能提示支持。它仅在类型推导阶段生效,不会产生运行时逻辑。
  • useSlots()(),用于在 setup() 函数中访问组件的 插槽内容
  • useAttrs()(),用于在 setup() 或 script setup 中访问组件接收的未声明为 props 的属性,如 class、style、自定义属性和事件监听器。这些属性默认会被自动绑定到组件的根元素,除非设置 inheritAttrs: false,通过 useAttrs() 可以手动控制它们的传递。

组件

基本组件

  • <Transition>mode? type? duration? appear?,用于在元素或组件的挂载卸载DOM 时添加动画效果。它通过自动应用 CSS 类名或 JavaScript 钩子函数实现平滑过渡。
  • <TransitionGroup>tag? move-class? css? ,用于在列表元素的进入、离开和位置变化时应用动画效果,特别适用于动态列表的平滑过渡。
  • <KeepAlive>include? exclude? max?,用于缓存动态组件的实例,避免重复渲染,从而优化性能。
  • <Teleport>to disabled?,用于将子组件或 DOM 元素 “传送”到 DOM 中的其他位置,常用于处理模态框Modal、通知Toast、弹出菜单等需要脱离父容器布局的场景。
  • <Suspense>(),用于管理异步依赖如异步组件或异步数据加载的组件,允许在等待异步操作完成时展示备用内容如加载状态。

特殊元素

  • <component>:is key?,用于动态渲染组件或 HTML元素,通过 :is 属性绑定目标类型。常用于实现标签页切换、动态组件加载等场景。
  • isstring | Component,用于 动态组件解决 HTML 原生元素的解析限制
  • <slot>name?,用于实现组件内容分发(插槽),在组合式API中,它允许父组件向子组件传递模板片段,并支持动态内容和作用域数据传递。
  • <template>,用于定义组件模板的核心标签。
  • refref变量,用于获取 DOM 元素或子组件实例引用的特殊属性。

TS

  • defineComponent()(options),在定义Vue组件时提供类型推导的辅助函数
  • PropType<T><TS类型>,用于 定义复杂props类型 的类型工具,尤其在结合TS时,它可以明确指定 props 的具体结构或构造函数类型。

SSR

  • createSSRApp()(rootComponent,rootProps?),用于 服务端渲染 (SSR) 的应用程序创建函数,它在组合式API中的使用与客户端渲染CSR类似,但需要遵循特定的 SSR 配置和生命周期规则。
  • renderToString()(app),用于SSR的核心函数,它将 Vue 应用实例渲染为 HTML 字符串。在组合式API中,需结合 createSSRApp() 和 SSR 生命周期钩子使用。
  • useId()(),用于为无障碍属性或表单元素生成每个应用内唯一的 ID。

指令

插值语法

mustache

mustache{ {variable} },最基础的数据绑定方式,用于将数据动态渲染为纯文本内容。

  • 语法:

    html
    <div>{{ variable }}</div>
  • 示例:

    html
    <script>
    data() {
      return {
        message: "Hello Vue!",
        count: 42
      };
    }
    </script>
    
    <div>{{ message }}</div>    <!-- 输出:Hello Vue! -->
    <div>{{ count }}</div>      <!-- 输出:42 -->
  • 特性:

  • 响应式:数据变化时,插值内容会自动更新。

  • 支持JS表达式

    html
    <!-- 1. 算术运算 -->
    <div>{{ count + 1 }}</div>         <!-- 输出:43 -->
    <div>{{ count * 2 }}</div>         <!-- 输出:84 -->
    
    <!-- 2. 三元运算符 -->
    <div>{{ count > 0 ? '正数' : '零或负数' }}</div> <!-- 输出:正数 -->
    
    <!-- 3. 方法调用(不推荐) -->
    <div>{{ message.toUpperCase() }}</div> <!-- 输出:HELLO VUE! -->
    
    <!-- 4. 访问对象或数组 -->
    <div>{{ user.name }}</div>       <!-- 输出:Alice -->
    <div>{{ list[0] }}</div>          <!-- 输出:Apple -->
  • 不支持语句和声明

    html
    <!-- 1. 不支持变量声明 -->
    <h2>{{var name = "Hello"}}</h2> <!-- 错误用法 -->
    
    <!-- 2. 不支持流程控制语句if -->
    <h2>{{ if (true) { return message } }}</h2> <!-- 错误用法 -->
  • HTML转义:mustache语法会将内容中的HTML标签自动转义为纯文本,避免 XSS 攻击。

    html
    <div>&lt;span&gt;危险内容&lt;/span&gt;</div> <!-- 转义后结果 -->
  • 不能用于HTML属性:mustache语法不能直接用于HTML属性,需改用 v-bind。

    html
    <div id="{{ dynamicId }}"></div> <!-- 错误用法 -->
    <div v-bind:id="dynamicId"></div> <!-- 正确用法 -->
  • 不推荐方法调用:mustache中调用方法是合法的,但是只推荐逻辑简单且无副作用的方法,复杂逻辑的方法推荐使用computed计算属性。

    html
    <div>{{ reversedMessage }}</div> <!-- 输出:!euV olleH -->
    js
    computed: {
      reversedMessage() {
        return this.message.split('').reverse().join('');
      }
    }
  • 结合过滤器

    • 在 Vue2 中,可通过过滤器格式化内容。
    • 在 Vue3 中已经废弃了过滤器,推荐使用计算属性或方法。
    html
    <div>{{ message | capitalize }}</div>
    js
    filters: {
      capitalize(value) {
        return value.charAt(0).toUpperCase() + value.slice(1);
      }
    }
  • 空白内容处理:如果绑定的值为 null 或 undefined,Mustache 会渲染为空字符串。

    html
    <div>{{ undefinedValue }}</div> <!-- 输出空白 -->
  • 闪烁问题:在未编译完成时,原始 { { } } 可能短暂显示。可以添加v-cloak并通过 CSS 隐藏未编译的模板。

    html
    <div v-cloak>{{ message }}</div>
    css
    [v-cloak] { display: none; }

文本渲染

v-text

v-textdataProperty,用于将数据作为纯文本动态渲染到元素中。

  • dataProperty基本类型,绑定的数据可以是字符串、数字或其他基本类型,非字符串类型会自动转换为字符串。

  • 示例:

    html
    <div v-text="message"></div>  <!-- 输出:Hello Vue! -->
    <div v-text="count"></div>    <!-- 输出:42 -->
    js
    data() {
      return {
        message: "Hello Vue!", // 字符串
        count: 42 // 数字
      };
    }
  • 特性:

  • 覆盖行为

    • v-text 会替换目标元素的全部内容,包括子元素。
    • Mustache 仅替换插值标记位置的内容。
    html
    <!-- 1. 使用 v-text -->
    <div v-text="content">原始内容会被覆盖</div>
    <!-- 渲染结果:<div>动态内容</div> -->
    
    <!-- 2. 使用 Mustache -->
    <div>原始内容 {{ dynamicPart }} 保留</div>
    <!-- 渲染结果:<div>原始内容 动态部分 保留</div> -->
  • 转义行为

    • v-text 会将内容中的 HTML 标签转义为纯文本,防止 XSS 攻击。
    • v-html 会解析数据中的 HTML 标签并渲染。
    html
    <div>&lt;script&gt;alert(1)&lt;/script&gt;</div> <!-- v-text转义后结果 -->
  • 动态绑定:可绑定计算属性、方法返回值或复杂表达式。

    html
    <div v-text="message.toUpperCase()"></div> <!-- 方法 -->
    <div v-text="user.name + '的年龄:' + user.age"></div> <!-- 表达式 -->
  • 闪烁问题:v-text 不存在闪烁问题。

  • 性能优化:对于复杂逻辑,优先使用计算属性而非直接在模板中调用方法。

    html
    <!-- 推荐 -->
    <div v-text="formattedDate"></div>
    
    <!-- 不推荐 -->
    <div v-text="formatDate(timestamp)"></div>
  • 指令冲突:避免在同一元素上同时使用 v-text 和 v-html,或与其他内容类指令混用。

v-html

v-htmldataProperty,用于将数据作为原始 HTML 解析并渲染到元素中。

  • dataPropertyHTMLString,包含有效的 HTML 的字符串。

  • 语法:

    html
    <div v-html="rawHtml"></div>
  • 示例:

    html
    <!-- 模板中使用 -->
    <div v-html="rawHtml"></div> 
    
    <!-- Vue 实例或组件 -->
    <script>
    data() {
      return {
        rawHtml: '<span style="color: red;">红色文字</span>'
      };
    }
    </script>
    
    <!-- 渲染结果 -->
    <div>
      <span style="color: red;">红色文字</span>
    </div>
  • 特性:

  • 对比mustache

    • mustache 会将数据解释为纯文本,直接输出 HTML 标签的源代码。
    • v-html 会解析数据中的 HTML 标签并渲染。
    html
    <div>&lt;span style="color: red;"&gt;红色文字&lt;/span&gt;</div> <!-- mustache渲染结果 -->
    
    <div><span style="color: red;">红色文字</span></div> <!-- v-html渲染结果 -->
  • 动态绑定:v-html 可以绑定动态值,如计算属性、方法返回值等。

    html
    <div v-html="dynamicHTML"></div>
    
    <script>
    computed: {
      dynamicHTML() {
        return this.isError ? '<p class="error">错误信息</p>' : '<p>正常信息</p>';
      }
    }
    </script>
  • XSS攻击

    • v-html 会直接渲染任意 HTML,如果内容来自用户输入或不可信来源,可能导致XSS跨站脚本攻击。需仅渲染可信内容,如后端返回的已消毒的富文本。

      html
      <div v-html="userProvidedContent"></div> <!-- 危险! -->
    • 替代方案:如果需要渲染部分用户输入,可以使用第三方库DOMPurify消毒。

      js
      import DOMPurify from 'dompurify';
      
      data() {
        return {
          userContent: DOMPurify.sanitize(untrustedHTML)
        };
      }
  • 指令冲突:避免在同一元素上同时使用 v-text 和 v-html,或与其他内容类指令混用。

v-pre

v-pre,用于跳过指定元素及其子元素的编译过程,直接保留原始内容。

  • 语法:

    html
    <div v-pre>
      <!-- 内部内容不会被 Vue 编译 -->
    </div>
  • 示例:

    html
    <template>
      <div>
        <!-- 使用 v-pre 展示源码 -->
        <pre v-pre>
          &lt;div&gt;{{ message }}&lt;/div&gt;
          &lt;button @click="show = true"&gt;显示&lt;/button&gt;
        </pre>
      </div>
    </template>
  • 特性:

  • 跳过编译

    • v-pre 会跳过元素及其所有子元素的 Vue 模板编译过程,直接保留原始内容。
    • 即使内容包含 Vue 语法,如 { { } } 或指令,也会直接显示为文本。
  • 作用范围:v-pre 会影响整个元素及其子元素,无法局部禁用编译。若需部分跳过编译,需拆分元素结构。

  • 与动态内容冲突:在 v-pre 区域内,所有 Vue 功能如数据绑定、指令均失效。

  • 性能影响:仅在需要时使用 v-pre,过度使用可能导致模板编译优化机会减少。

绑定

v-bind

v-bind表达式,用于动态绑定HTML属性、组件prop或DOM属性到Vue实例的数据。

  • 语法:

    html
    <!-- 绑定单个属性 -->
    <div v-bind:属性名="表达式"></div>
    
    <!-- 简写形式(推荐) -->
    <div :属性名="表达式"></div>
  • 示例:

    html
    <a :href="url">Vue 官网</a>
    <button :disabled="!isActive">提交</button>
  • 特性:

  • 动态参数

    • 绑定动态参数:通过 [] 语法动态指定绑定的属性名。

      html
      <div :[attrName]="123"></div>
      <!-- 渲染结果:<div data-id="123"></div> -->
      js
      data() {
        return { attrName: "data-id" }
      }
    • 动态参数限制:动态属性名需为字符串,且避免使用大写或特殊字符(空格/引号)。当使用DOM内嵌模板(直接写在HTML文件里的模板),浏览器会强制将其转换为小写。

      html
      <!-- 错误示例 -->
      <div :[dynamicAttr]="value"></div> <!-- HTML模板中会被转换为:[dynamicattr] -->
  • 批量绑定属性:直接绑定一个对象,键为属性名,值为对应数据。

    html
    <div v-bind="attrs"></div>
    <!-- 渲染结果:<div id="container" class="main" data-status="active"></div> -->
    js
    data() {
      return {
        attrs: {
          id: "container",
          class: "main",
          "data-status": "active"
        }
      };
    }
  • 绑定class

    • 对象语法:根据条件动态切换类名。

      html
      <div :class="{ active: isActive, 'text-red': hasError }"></div>
    • 数组语法:绑定多个类名。

      html
      <div :class="[baseClass, isActive ? 'active' : '']"></div>
  • 绑定style

    • 对象语法:绑定内联样式对象。CSS属性名可以是camelCase,也可以是kebab-case的写法。

      html
      <div :style="{ color: textColor, fontSize: fontSize + 'px' }"></div>
    • 数组语法:应用多个样式对象。CSS属性名可以是camelCase,也可以是kebab-case的写法。

      html
      <div :style="[baseStyles, overrides]"></div>
  • 组件prop传递:在组件上使用v-bind可批量传递props。

    html
    <child-component v-bind="propsObj"></child-component>
  • Vue3变化

    • 移除.sync修饰符:Vue3中废弃.sync,改用v-model参数实现双向绑定。

      html
      <!-- Vue 2 -->
      <ChildComponent :title.sync="pageTitle" />
      
      <!-- Vue 3 -->
      <ChildComponent v-model:title="pageTitle" />
    • 合并行为:当同时使用普通属性和v-bind绑定时,后者会覆盖前者。

      html
      <div id="static" :id="dynamicId"></div>
      <!-- 渲染结果:<div id="dynamic"></div> -->

v-on

v-on表达式,用于监听 DOM 事件或自定义事件,并执行对应的 JavaScript 代码或方法。

  • 语法:

    html
    <!-- 监听事件并执行表达式 -->
    <div v-on:事件名="表达式"></div>
    
    <!-- 简写形式(推荐) -->
    <div @事件名="表达式"></div>
  • 示例:

    html
    <button @click="count += 1">点击增加</button>
    <button @mouseover="showTooltip = true">悬停提示</button>
  • 特性:

  • 绑定方法:将事件直接绑定到组件中定义的方法。

    html
    <button @click="handleClick">提交</button>
    js
    methods: {
      handleClick() {
        console.log("按钮被点击");
      }
    }
  • 传递参数

    • 手动传递参数

      html
      <button @click="handleDelete(item.id)">删除</button>
      js
      methods: {
        handleDelete(id) {
          this.items = this.items.filter(item => item.id !== id);
        }
      }
    • 获取原生事件对象$event:当需要同时传递参数和原生事件对象时,显式传递 $event

      html
      <button @click="handleSubmit('参数', $event)">提交</button>
      js
      methods: {
        handleSubmit(arg, event) {
          event.preventDefault();
          console.log("参数:", arg);
        }
      }
  • 事件修饰符:Vue 提供了事件修饰符,用于简化常见的事件处理逻辑。

    • .stop:阻止事件冒泡,等价于 event.stopPropagation()
    • .prevent:阻止默认行为,等价于 event.preventDefault()
    • .capture:使用事件捕获模式,从外到内触发。
    • .self:仅当事件从元素本身触发时(非子元素)才执行回调。
    • .once:事件只触发一次,Vue3 中也可用于组件事件。
    • .passive:表示不阻止默认行为。提升滚动性能,与 addEventListener 的 passive 选项一致。慎用,不可与 .prevent 同时使用。
    • .native:监听原生事件(Vue3已废弃)。
    html
    <!-- 阻止表单默认提交和事件冒泡 -->
    <form @submit.prevent.stop="onSubmit"></form>
    
    <!-- 点击事件最多触发一次 -->
    <button @click.once="handleOnce">仅一次</button>
    
    <!-- 滚动事件使用 passive 修饰符优化性能 -->
    <div @scroll.passive="onScroll"></div>
  • 按键修饰符:用于监听特定键盘按键的事件,如 Enter、Esc 等。

    • 常用按键别名

      html
      <input @keyup.enter="submit" />      <!-- 回车键 -->
      <input @keyup.esc="closeModal" />    <!-- Esc 键 -->
      <input @keyup.space="playPause" />   <!-- 空格键 -->
      <input @keyup.delete="clearInput" />  <!-- 删除键 -->
    • 自定义按键码:按键码keyCode,Vue 2 支持,Vue 3 已废弃。

      html
      <input @keyup.13="submit" /> <!-- Vue2 中支持 -->
    • 组合键修饰符

      html
      <input @keyup.ctrl.enter="save" />   <!-- Ctrl + Enter -->
      <input @keyup.alt.s="save" />        <!-- Alt + S -->
  • 动态事件名:通过动态属性名绑定不同事件。

    html
    <button @[eventName]="handleEvent"></button>
    js
    data() {
      return {
        eventName: "click"
      };
    }
  • 在组件上监听自定义事件:用于父子组件通信,通过 $emit 触发。

    html
    <!-- 父组件监听子组件的自定义事件 -->
    <ChildComponent @custom-event="handleCustomEvent" />
    js
    // 子组件触发事件
    this.$emit("custom-event", arg1, arg2);
  • 避免内联复杂表达式:推荐将逻辑封装到方法中,而非直接在模板中编写复杂表达式。

    html
    <!-- 不推荐 -->
    <button @click="count += 1; logCount()">增加</button>
    
    <!-- 推荐 -->
    <button @click="handleIncrement">增加</button>

v-model@

v-model表达式,用于在表单输入元素或自定义组件上实现双向数据绑定,是 v-bind 和 v-on 的语法糖。

  • 语法:

    html
    <!-- 基础用法 -->
    <input v-model="dataProperty" />
    
    <!-- 等价于手动绑定 -->
    <input 
      :value="dataProperty" 
      @input="dataProperty = $event.target.value" 
    />
  • 特性:

  • 响应式:表单输入的值与 Vue 实例中的 dataProperty 保持同步。

  • 不同类型表单元素的使用

    • 文本输入:input[type=text]和textarea

      html
      <input type="text" v-model="message" />
      <textarea v-model="content"></textarea>
    • 复选框

      • 单个复选框:绑定布尔值。

        html
        <input type="checkbox" v-model="isAgreed" /> 同意协议
      • 多个复选框:绑定数组。

        html
        <input type="checkbox" value="vue" v-model="skills" /> Vue
        <input type="checkbox" value="react" v-model="skills" /> React
        js
        data() { return { skills: [] }; }
    • 单选按钮

      html
      <input type="radio" value="male" v-model="gender" /> 男
      <input type="radio" value="female" v-model="gender" /> 女
      js
      data() { return { gender: "" }; }
    • 下拉选择

      • 单选

        html
        <select v-model="selectedCity">
          <option value="bj">北京</option>
          <option value="sh">上海</option>
        </select>
      • 多选

        html
        <select v-model="selectedTags" multiple>
          <option value="tag1">标签1</option>
          <option value="tag2">标签2</option>
        </select>
  • 修饰符

    • .lazy:将 input 事件改为 change 事件,输入完成后才同步数据。
    • .number:自动将输入值转为数字类型,避免字符串转换问题。
    • .trim:自动去除输入值首尾空格。
    html
    <!-- 输入完成后同步 -->
    <input v-model.lazy="message" />
    
    <!-- 强制转为数字 -->
    <input type="number" v-model.number="age" />
    
    <!-- 去除首尾空格 -->
    <input v-model.trim="username" />
  • 在自定义组件中使用

    • Vue2 的实现:默认使用 value 属性 和 input 事件。

      html
      <!-- 父组件 -->
      <CustomInput v-model="inputValue" />
      
      <!-- 子组件 -->
      <input 
        :value="value" 
        @input="$emit('input', $event.target.value)" 
      />
      js
      // 子组件声明
      props: ["value"],
    • Vue3 的改进

      • 默认使用 modelValue prop 和 update:modelValue 事件。

        html
        <!-- 父组件 -->
        <CustomInput v-model="count"/>
        
        <!-- 等价于以下写法 -->
        <CustomInput :modelValue="count" @update:modelValue="count = $event"/>
        html
        <!-- 子组件 -->
        <template>
          <button @click="hdlUpdateCount">修改Count</button>
        </template>
        
        <script setup>
          const props = defineProps({
            modelValue: {
              type: Number,
              default:0
            }
          })
          const emit = defineEmits(['update:modelValue'])
          function hdlUpdateCount() {
            emit('update:modelValue', 999)
          }
        </script>
      • 支持自定义参数名(多个双向绑定)。

        html
        <!-- 父组件 -->
        <CustomInput v-model:title="pageTitle" v-model:content="pageContent"  />
        
        <!-- 等价于以下写法 -->
        <CustomInput
          :title="pageTitle" @update:title="pageTitle = $event"
          :content="pageContent" @update:content="pageContent = $event"
        />
        html
        <!-- 子组件 -->
        <template>
          <button @click="hdlUpdateTitle">修改Title</button>
          <button @click="hdlUpdateContent">修改Content</button>
        </template>
        
        <script setup>
          const props = defineProps({
            title: {
              type: String,
              default: ''
            }
            content: {
              type: String
              default: ''
            }
          })
          const emit = defineEmits(['update:title', 'update:content'])
          function hdlUpdateTitle() {
            emit('update:title', '修改后的标题')
          }
          function hdlUpdateContent() {
            emit('update:content', '修改后的内容')
          }
        </script>
    • 修改默认行为:通过组件的 model 选项(Vue2)或 v-model 参数(Vue3)自定义.

      js
      // Vue 2 自定义 prop 和事件
      model: {
        prop: "selected",
        event: "change"
      }

条件渲染

v-show

v-show布尔表达式,用于通过切换 CSS 的 display 属性来控制元素的显示与隐藏。

  • 语法:

    html
    <div v-show="布尔表达式"></div>
  • 示例:

    html
    <div v-show="isVisible">此内容会根据 isVisible 显示/隐藏</div>
    js
    data() {
      return { isVisible: true } 
    }
  • 特性:

  • 实现原理

    • 当表达式为 true 时:移除元素的 display: none 样式。 当表达式为 false 时:添加内联样式 display: none
  • 对比v-if

    • DOM操作

      • v-show:始终保留元素,仅切换 display。
      • v-if:条件为假时移除元素。
    • 初始渲染开销

      • v-show:较高,无论条件如何都渲染元素。
      • v-if:较低,条件为假时不渲染。
    • 切换开销

      • v-show:较低,仅修改 CSS。
      • v-if:较高,触发组件销毁 / 重建。
    • 适用场景

      • v-show:频繁切换显示状态,如选项卡。
      • v-if:条件很少变化,如一键展开 / 收起。
  • 不支持<template>元素:v-show必须作用在实际渲染的DOM元素上,不可用于 <template>

    html
    <!-- 错误用法 -->
    <template v-show="condition">
      <div>内容</div>
    </template>
    
    <!-- 正确用法 -->
    <div v-show="condition">
      <div>内容</div>
    </div>

v-if、v-else、v-else-if

v-if、v-else、v-else-if条件表达式,用于实现条件渲染,根据表达式的真假动态控制元素的渲染或销毁。元素会从DOM中移除。

  • 语法:

    html
    <div v-if="条件表达式"></div>
    <div v-else-if="条件表达式"></div>
    <div v-else></div>
  • 示例:

    html
    <div v-if="score >= 90">优秀</div>
    <div v-else-if="score >= 80">良好</div>
    <div v-else-if="score >= 60">及格</div>
    <div v-else>不及格</div>
  • 特性:

  • 条件链必须连续:指令链必须严格遵循 v-ifv-else-ifv-else 的顺序,中间不能插入其他元素。

  • 控制组件/元素的销毁与重建:v-if 为 false 时,元素及其子组件会被销毁并移出 DOM。频繁切换性能会有影响。

  • 结合<template>:使用 <template> 包裹多个元素实现分组条件渲染。

    html
    <template v-if="showSection">
      <h1>标题</h1>
      <p>内容</p>
    </template>
    <template v-else>
      <div>替代内容</div>
    </template>
  • 状态不保留

    • v-if 为 false 时,元素内部状态(如表单输入值)会被销毁,重新渲染时重置。

    • 解决方案:使用 <keep-alive> 包裹元素以缓存状态。

      html
      <keep-alive>
        <component v-if="isActive"></component>
      </keep-alive>
  • 异步组件:若条件依赖异步数据,需确保数据加载完成后再渲染。

    html
    <div v-if="!isLoading && data">
      {{ data.content }}
    </div>

列表渲染

v-for

v-for表达式,用于基于数据源(数组、对象、数值范围)循环渲染多个元素或组件。

  • 语法:

    html
    <元素 v-for="(item, index?) in data" :key="唯一标识">
      {{ item.attr }}
    </元素>
  • itemany,当前遍历的元素值,数组项 / 对象属性值 / 数值。

  • index?string | number,当前项的索引或键名。

  • dataany,支持数组、对象、数值范围。

  • :keyany,用于标识元素唯一性,优化虚拟DOM的更新性能。

  • 特性:

  • 遍历不同类型数据源

    • 遍历数组

      html
      <ul>
        <li v-for="(item, index) in items" :key="item.id">
          {{ index + 1 }}. {{ item.name }} <!-- 输出:1. Apple-->
        </li>
      </ul>
      js
      data() {
        return {
          items: [
            { id: 1, name: "Apple" },
            { id: 2, name: "Banana" }
          ]
        };
      }
    • 遍历对象

      html
      <ul>
        <li v-for="(value, key, index) in obj" :key="key">
          {{ index }}. {{ key }}: {{ value }} <!-- 输出:0. title: Vue 教程-->
        </li>
      </ul>
      js
      data() {
        return {
          obj: { 
            title: "Vue 教程", 
            author: "John" 
          }
        };
      }
    • 遍历数值范围

      html
      <span v-for="n in 5" :key="n">{{ n }} </span> <!-- 输出:1 2 3 4 5 -->
  • :key

    • 作用

      • 唯一标识:帮助 Vue 识别元素身份,避免重复渲染或状态错乱。
      • 性能优化:基于 key 复用已有 DOM 节点,减少不必要的重新渲染。
    • 用法

      • 唯一性:使用稳定且唯一的标识,如 item.id,避免用索引 index

        html
        <!-- 错误:用索引作为 key -->
        <div v-for="(item, index) in list" :key="index">
          {{ item.name }}
        </div>
  • 结合v-if:v-for 的优先级高于 v-if(Vue2)。若需先判断条件再循环,可将 v-if 置于外层元素。

    html
    <!-- 正确写法 -->
    <template v-if="hasData">
      <div v-for="item in list" :key="item.id">
        {{ item.name }}
      </div>
    </template>
  • 结合计算属性:推荐使用计算属性过滤数据,而非在模板中直接操作。

    html
    <div v-for="item in filteredList" :key="item.id">{{ item.name }}</div>
    js
    computed: {
      filteredList() {
        return this.list.filter(item => item.isActive);
      }
    }
  • 性能优化

    • 避免超大列表:对超长列表(如1000+项)使用虚拟滚动技术,如 vue-virtual-scroller

    • 减少响应式依赖:对不需要响应式的静态数据,使用 Object.freeze 冻结。

      js
      data() {
        return {
          list: Object.freeze([...]) // 禁止 Vue 追踪其变化
        };
      }
    • 扁平化数据结构:避免嵌套过深的响应式对象,优先使用扁平化数据。

  • 在组件中使用

    • 传递 Props:在组件上使用 v-for 时,需显式传递数据。

      html
      <ChildComponent 
        v-for="item in list" 
        :key="item.id"
        :item="item"
        @custom-event="handleEvent"
      />
    • 组件状态保留:若组件需要保留内部状态(如输入框内容),需确保 :key 唯一且稳定。

      html
      <UserInput 
        v-for="user in users" 
        :key="user.id" 
        :user="user" 
      />

key@

key唯一标识,用于标识虚拟 DOM 节点身份的特殊属性,它在 Vue 的响应式更新机制中起到优化渲染性能维护组件状态的关键作用。

  • 语法:

    html
    <元素 :key="唯一标识符"></元素> <!-- 在元素上绑定 key -->
    <组件 :key="唯一标识符"></组件> <!-- 在组件上绑定 key -->
  • 唯一标识

    • 唯一性:同一父级下,每个 key 必须唯一,不可重复。
    • 稳定性:key 值应在数据变化时保持稳定,如id而非index。
  • 特性:

  • 核心作用

    • 优化虚拟DOM对比(Diff 算法):Vue 通过 key 识别节点身份,减少不必要的 DOM 操作。
      • 相同 key:复用现有节点,仅更新变化部分。
      • 不同 key:销毁旧节点,创建新节点。
    • 维护组件/元素状态:当元素或组件因为如列表重新排序而数据变化被复用时,key 可确保其内部状态如输入框内容、滚动位置不被意外保留。
  • 应用场景

    • v-for列表渲染:在 v-for 循环中,每个元素必须绑定唯一 key。

    • 强制替换元素/组件:当需要完全替换元素或组件而非复用,如切换表单类型,可通过改变key强制重新渲染。

      html
      <!-- 切换登录方式时,清空输入框 -->
      <div v-if="isEmailLogin">
        <input key="email" placeholder="邮箱">
      </div>
      <div v-else>
        <input key="phone" placeholder="手机号">
      </div>
    • 动态组件强制刷新:为 <component> 绑定key,避免组件复用导致的生命周期钩子未触发。

      html
      <component :is="currentComponent" :key="currentComponent"></component>
    • 过渡动画强制刷新:确保过渡动画在元素身份变化时正确触发。

      html
      <transition>
        <div :key="state">{{ state }}</div>
      </transition>
  • 底层原理

    • 虚拟DOM Diff过程
      • 无key:Vue 采用 “就地更新” 策略,按索引顺序对比节点,可能导致错误复用。
      • 有key:通过 key 精准匹配相同节点,最小化 DOM 操作。
    • 状态保留机制
      • 相同key:复用元素,保留表单值、滚动位置等状态。
      • 不同key:销毁旧元素,初始化新元素。

其他

v-once

v-once,用于标记元素或组件仅渲染一次,后续数据变化时不再更新。

  • 语法:

    html
    <元素 v-once>{{ 静态内容 }}</元素> <!-- 元素上使用 -->
    <组件 v-once></组件> <!-- 组件上使用 -->
  • 示例:

    html
    // 首次渲染后冻结组件
    <template>
      <UserProfile v-once :initialData="userData" /> <!-- 用户资料不再更新 -->
    </template>
  • 特性:

  • 核心作用

    • 单次渲染:初始渲染后,元素/组件及其子内容不再响应数据变化。
    • 性能优化:减少不必要的虚拟 DOM 比对,提升渲染效率。
  • 使用场景

    • 静态内容优化

      html
      <!-- 标题永不更新 -->
      <h1 v-once>{{ initialTitle }}</h1>
    • 复杂计算的缓存

      html
      <!-- 计算密集型数据只计算一次 -->
      <div v-once>{{ heavyCompute(data) }}</div>
    • 避免子组件更新

      html
      <!-- 父组件数据变化时,子组件不更新 -->
      <ChildComponent v-once :prop="staticData" />
  • 作用范围:影响整个元素及其子树,包括子组件。若子组件依赖动态 props,v-once 会阻止其更新。

v-cloak

v-cloak,用于在 Vue 实例完成编译前隐藏未编译的模板内容,避免页面加载时出现原始模板符号({ { } })的短暂闪烁。必需配合 CSS 样式,CSS 必须全局生效

  • 语法:

    html
    <!-- 在根元素或需要隐藏的元素上添加 v-cloak -->
    <div v-cloak>{{ message }}</div>
    css
    /** CSS 必须全局生效 */
    [v-cloak] {
      display: none !important;
    }
  • 示例:

    html
    <!DOCTYPE html>
    <html>
    <head>
      <style>
        [v-cloak] {
          display: none;
        }
      </style>
    </head>
    <body>
      <div id="app" v-cloak>
        <p>{{ message }}</p>
      </div>
    
      <script src="https://unpkg.com/vue@3"></script>
      <script>
        Vue.createApp({
          data() {
            return { message: "Hello Vue!" };
          }
        }).mount('#app');
      </script>
    </body>
    </html>
  • 特性:

  • 实现原理

    • 初始阶段:元素带有 v-cloak 属性,CSS 将其隐藏。
    • 编译完成:Vue 移除所有 v-cloak 属性,元素显示渲染后的内容。
  • 配合异步加载:若 Vue 实例异步加载,如按需加载,v-cloak 仍可确保内容隐藏直至编译完成。

    html
    <div v-cloak>
      <p v-if="loaded">{{ data }}</p>
    </div>

全局API

应用实例

createApp()

createApp()(rootComponent, rootProps?),用于创建一个 Vue 应用实例,是 Vue 3 应用开发的入口函数。

  • rootComponentoptions | Component,根组件,Vue 应用的入口组件。

    • options{data,methods,...},选项式API中的组件选项。

    • Component:组合式API中包含 setup() 方法的组件对象。

  • rootProps?Record<string, any>,传递给根组件的 props 对象,用于父组件向根组件传递数据。

  • 返回:

  • appApp,返回的Vue应用实例,提供多个用于配置和控制应用的实例方法。

  • 示例:

    • script setup
    js
    import { createApp } from 'vue'
    
    import App from './App.vue'
    import store from './store'
    import router from './router'
    import icons from './utils/register-icons'
    
    const app = createApp(App)
    app.use(store)
    app.use(router)
    app.use(icons)
    app.mount('#app')
  • 特性:

  • TS

    ts
    function createApp(rootComponent: Component, rootProps?: object): App
  • TS:使用 defineComponent() 增强类型推断。

    ts
    import { defineComponent } from 'vue';
    const RootComponent = defineComponent({
      data() { return { count: 0 }; }
    });
    const app = createApp(RootComponent)
    app.mount('#app')

app.use()

app.use()(plugin,...options?),用于为 Vue 应用安装插件(如 Vue Router、Pinia等)或自定义功能扩展。

  • pluginObject | Function,插件对象或函数。

    • Object:必须包含 install() 方法。
    • Function:直接作为 install() 方法调用。
  • ...options?any[],传递给插件 install 方法的额外参数(如插件配置项)。

  • 返回:

  • appApp,返回应用实例本身,支持链式调用。

  • 示例:

    • 安装 Vue Router
    js
    import { createApp } from 'vue';
    import App from './App.vue';
    import VueRouter from 'vue-router';  // 假设已安装 vue-router@4
    
    const app = createApp(App);
    
    // 安装路由插件,并传递路由配置
    const router = VueRouter.createRouter({ /* ...路由配置 */ });
    app.use(router);  
    
    app.mount('#app');
  • 特性:

  • TS

    ts
    interface App {
      use(plugin: Plugin, ...options: any[]): this
    }
  • 自定义插件开发:插件必须提供 install() 方法(或自身为函数)。

    js
    // 1. 自定义插件(含 install 方法)
    const MyPlugin = {
    +  install(app, options) {
        // 添加全局组件
        app.component('CustomHeader', {
          template: `<header>{{ title }}</header>`,
          props: ['title']
        });
    
        // 添加全局属性
        app.config.globalProperties.$log = (msg) => console.log(msg);
      }
    };
    
    // 2. 使用插件并传递配置
    const app = Vue.createApp({});
    + app.use(MyPlugin, { debug: true });  // options 参数会传递给 install 方法
    
    // 3. 使用插件功能
    app.component('DemoComponent', {
      mounted() {
    +    this.$log('Component mounted!');  // 调用全局属性
      }
    });

app.mount()

app.mount()(containerSelector | element),将 Vue 应用实例挂载到 DOM 元素。

  • containerSelectorstring,CSS 选择器字符串(如 '#app'),指定挂载的容器元素。

  • elementElement,直接传入 DOM 元素对象(如 document.getElementById('app'))。

  • 返回:

  • instanceComponentPublicInstance,返回根组件实例,可通过该实例访问组件属性/方法。

  • 示例:

    • 挂载 app
    js
    import { createApp } from 'vue';
    import App from './App.vue';
    
    // 创建应用实例
    const app = createApp(App);
    
    // 挂载到 #app 元素
    const rootInstance = app.mount('#app');
    
    // 访问根组件数据
    console.log(rootInstance.someData);
  • 特性:

  • TS

    ts
    interface App {
      mount(rootContainer: Element | string): ComponentPublicInstance
    }
  • TS类型注解:可以给instance添加TS类型注解。

    ts
    interface RootComponentInstance {
        someMethod: () => void
    }
    const instance = app.mount('#app') as unknown as RootComponentInstance;
  • 选项式:

    js
    // 1. Vue2选项式写法:自动挂载
    new Vue({
        el: '#app', // 自动挂载
        render: h => h(App)
    });
    
    // 2.Vue2选项式写法:手动挂载
    const vm = new Vue({
        render: h => h(App)
    }).$mount('#app'); // 等价于 app.mount()
  • 挂载时机:需确保 DOM 已加载完成(通常在 DOMContentLoaded 事件后调用)。

  • 单次挂载限制:每个应用实例只能挂载一次,重复调用 .mount() 会抛出警告。

  • SSR差异:服务端渲染时应使用 createSSRApp + hydrate 而非直接 mount

  • 访问实例不推荐,通过返回的实例可直接操作组件的属性和方法。推荐优先使用 props/emits 通信。

app.component()

app.component()(name,component?),用于全局注册或获取组件。

  • namestring,组件的名称。命名遵循camelCase或kebab-case。

  • component?Component,包含组件配置的对象。

  • 返回:

    • appApp,当传递 component 参数时,表示注册全局组件。返回app,支持链式调用。
    • componentComponent|undefined,当省略 component 参数时,表示获取已注册的组件。返回已注册的组件,未找到则返回 undefined
  • 示例:

    • 注册/获取全局组件
    js
    import { createApp } from 'vue'
    const app = createApp({})
    
    // 1. 全局注册一个按钮组件
    app.component('custom-button', {
      template: `
        <button class="btn">
          <slot></slot>
        </button>
      `
    })
    
    // 2. 获取已注册的组件
    const cpn = app.component('custom-button')
  • 特性:

  • TS

    ts
    interface App {
      component(name: string): Component | undefined
      component(name: string, component: Component): this
    }

app.directive()

app.directive()(name, directive?),用于注册或获取全局自定义指令。

  • namestring,指令的名称。命名通常遵循kebab-case。

  • directive?{mounted,...},包含指令定义的对象。指令定义在不同的生命周期钩子中,控制指令在不同阶段的行为。

  • 返回:

  • appApp,返回app,支持链式调用。

  • 示例:

    • 自定义指令 v-focus
    js
    const app = createApp(App);
    
    // 1. 自定义指令:`v-focus`,使元素获得焦点
    const focusDirective = {
        mounted(el) {
            el.focus(); 
        }
    };
    
    // 2. 注册全局指令
    app.directive('focus', focusDirective);
    
    app.mount('#app');
  • 特性:

  • TS

    ts
    interface App {
      directive(name: string): Directive | undefined
      directive(name: string, directive: Directive): this
    }
  • 选项式

    js
    import { createApp } from 'vue';
    
    const App = {
      // 1. 注册局部指令
      directives: {
        focus: {
          mounted(el) {
            el.focus(); // 元素插入时获得焦点
          }
        }
      },
      template: `
        <div>
          <input v-focus />
        </div>
      `
    };
    
    createApp(App).mount('#app');

app.mixin()

app.mixin()(mixin),用来全局注册一个混入对象。会将该对象的所有组件选项(data,methods,...)合并到每个组件中。

  • mixin{data,methods,...},该对象的组件选项会被合并到每个组件的选项中。

  • 返回:

  • appApp,返回app,支持链式调用。

  • 示例:

    • 全局混入对象
    js
    const globalMixin = {
      data() {
        return {
          mixinData: 'This is data from mixin',
        };
      },
      created() {
        console.log('Mixin created hook');
      },
      methods: {
        greet() {
          console.log('Hello from mixin method');
        }
      }
    };
    
    const app = Vue.createApp(App);
    
    // 2. 注册全局混入对象
    app.mixin(globalMixin);
    app.mount('#app');
    • 局部混入对象
    js
    const localMixin = {
      data() {
        return {
          localMessage: 'This is local mixin data',
        };
      }
    };
    
    const app = Vue.createApp({
      // 2. 注册局部混入对象
      mixins: [localMixin],
      template: '<p>{{ localMessage }}</p>'
    });
    
    app.mount('#app');
  • 特性:

  • TS

    ts
    interface App {
      mixin(mixin: ComponentOptions): this
    }
  • 生命周期钩子的合并:如果定义了多个相同的生命周期钩子,这些钩子会被合并,并按顺序依次调用。

  • 数据合并

    • 如果data选项是一个函数,组件和混入中的data的返回值会被合并(推荐)。
    • 如果data选项是一个普通对象,会出现合并冲突。
  • 方法覆盖:组件的方法会覆盖混入中的方法。

  • 不推荐:Mixins 在 Vue 3 支持主要是为了向后兼容,因为生态中有许多库使用到。在新的应用中应尽量避免使用 mixin,特别是全局 mixin。若要进行逻辑复用,推荐用组合式函数来替代。

通用

nextTick()

nextTick()(callback?),通常用来在下一次 DOM 更新刷新后执行某些操作。

  • callback?() => void,在 DOM 更新后执行的回调函数。

  • 返回:

  • resultPromise<void>,在DOM更新完成后解析。

  • 示例:

    • 在watch或onMounted中使用
    js
    import { ref, watch, nextTick } from 'vue';
    
    const count = ref(0);
    
    // 在watch或onMounted中使用
    watch(count, async () => {
      await nextTick();
      console.log('count 更新后,DOM 渲染完成');
    });
    • 与异步操作结合
    js
    import { ref, nextTick } from 'vue';
    
    const count = ref(0);
    
    const updateAndWait = async () => {
      count.value++;
      // 与异步操作结合
      await nextTick();
      console.log('DOM 更新后进行的操作');
    };
  • 特性:

  • TS

    ts
    function nextTick(callback?: () => void): Promise<void>
  • 性能nextTick() 可能会影响性能,特别是在高频更新的情况下会造成任务队列拥挤。

  • 用途nextTick() 主要是为了解决 Vue 的异步渲染机制。在Vue内部,数据更新是异步的,DOM更新会在下一个“tick”内完成。

defineAsyncComponent()【

组合式API

setup

setup()

setup()(props?, context?),是组件逻辑的入口点,用于替代 Vue2 的 data、methods、computed 等选项。

  • props?Object,接收父组件传递的 props 值。需通过 props 选项预先声明。

  • context?{attrs?,slots?,emit?},setup上下文对象,暴露了其他一些在 setup 中可能会用到的值。

    • attrs?any,未在 props 中声明的属性,相当于 Vue2 的 this.$attrs
    • slots?,插槽内容,相当于 Vue2 的 this.$slots
    • emit?(eventName,data)=>void,触发自定义事件,相当于 Vue2 的 this.$emit
  • 返回:

  • objObject,必须返回一个对象,供模板使用。

  • 示例:

    html
    <template>
      <div>
        <h1>{{ title }}</h1>
        <p>Count: {{ count }}</p>
        <p>Double: {{ double }}</p>
        <button @click="increment">增加</button>
      </div>
    </template>
    
    <script>
    import { ref, computed, onMounted } from 'vue';
    
    export default {
      // 1. props
      props: ['title'],
      setup(props, { emit }) {
        // 2. 定义响应式数据
        const count = ref(0);
          
        // 3. 计算属性
        const double = computed(() => count.value * 2);
    
        // 4. 方法
        const increment = () => {
          count.value++;
          emit('count-updated', count.value);
        };
    
        // 5. 声明周期钩子
        onMounted(() => {
          console.log('组件已挂载');
        });
    
        // 6. 必须返回对象,供模版使用
        return { count, double, increment };
      }
    };
    </script>
  • 特性:

  • 避免使用this:setup() 中无法访问 this,所有数据通过参数和响应式 API 获取。

响应式

ref()

ref()<T>(initialValue:T),用来定义一个响应式的、可更改的 ref 对象,该对象可以通过.value访问其内部值。

  • initialValueT,初始值。

  • 返回:

  • refValueRef<UnwrapRef<T>>,返回响应式对象。可以通过 .value 访问或修改该值。

  • 示例:

    js
    // 1. 参数类型
    const count = ref(0);  // 基本数据类型
    const name = ref('Vue');  // 字符串类型
    const person = ref({ name: 'John', age: 25 });  // 对象类型
    
    // 2. 在JS中访问/修改ref()返回的值,需要通过.value
    console.log(count.value);  // 访问值
    count.value = 10; // 修改值
    html
    <template>
      <!-- 3. 在模板中会自动解包ref对象,不需要通过.value访问 -->
      <div>{{ count }}</div>
    </template>
  • 特性:

  • TS

    ts
    function ref<T>(value: T): Ref<UnwrapRef<T>>
    
    interface Ref<T> {
      value: T
    }
  • TS:通过泛型指定元素类型注解

    ts
    const inputRef = ref<HTMLInputElement | null>(null)
    onMounted(() => inputRef.value?.select())
  • 响应式:ref()返回的值是一个响应式的值。.value改变时,依赖该值的视图会自动更新。底层通过Proxy实现。

  • 解包

    • 在模板中,Vue 会自动解包 ref 对象,可以直接访问和显示其值。
    • 在 JS 中,需要通过 .value 来访问和修改 ref 的值。
  • 对比reactive

    • ref() 适用于单一的值,通常是基本数据类型,或你希望包装的对象。不能用于定义响应式数组元素
    • reactive() 用来创建一个深度响应式的对象,适用于更复杂的数据结构(如对象或数组),它会对对象的所有属性进行响应式处理。
  • 异步操作:可以和异步网络请求操作结合使用,保存请求到的数据。

    js
    import { ref } from 'vue';
    
    const data = ref(null);
    
    async function fetchData() {
      const response = await fetch('https://api.example.com');
      data.value = await response.json();
    }
  • 引用DOM元素:可以和ref属性结合创建一个引用,以便直接访问 DOM 元素。

    html
    <template>
      <!-- 2. 将创建的ref变量绑定到ref属性上 -->
      <div ref="myDiv">Hello, world!</div>
    </template>
    
    <script>
    import { ref, onMounted } from 'vue';
    
    export default {
      setup() {
        const myDiv = ref(null); // 1. 创建ref变量
        
        onMounted(() => {
          console.log(myDiv.value);  // 3. 访问DOM元素,需要在可以DOM操作的钩子函数中使用
        });
    
        return { myDiv };
      }
    };
    </script>

reactive()

reactive()<T extends object>(initialObject:T),用于创建深度响应式的对象或数组。底层通过Proxy实现。

  • initialObjectObject|Array|Class Instance,初始的对象或数组。不能是基本数据类型。

  • 返回:

  • objUnwrapNestedRefs<T>,返回一个响应式对象。

  • 示例:

    js
    import { reactive } from 'vue';
    
    // 1. 对象类型
    const person = reactive({
      name: 'Alice',
      age: 30
    });
    
    // 2. 数组类型
    const numbers = reactive([1, 2, 3, 4]);
    
    // 3. 访问和修改值
    console.log(person.age);
    person.age = 18;
  • 特性:

  • TS

    ts
    function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
  • 深度响应式:reactive() 会递归地将对象的每个属性都变成响应式的。

    js
    const user = reactive({
      name: 'Bob',
      address: {
        city: 'Paris',
        postalCode: '75000'
      }
    });
    
    user.address.city = 'London';  // 这个操作是响应式的
  • 响应式丢失:直接赋值新的对象,会造成响应式丢失。

    js
    const state = reactive({
      user: { name: 'John' }
    });
    
    // 直接赋值新的对象,会造成响应式丢失,不会触发视图更新
    state.user = { name: 'Jane' };
  • 不适用于基本类型:只能用于对象类型,不能直接处理基本数据类型(stringnumberboolean等),基本类型数据推荐使用ref()。

  • 结合computed或watch使用:可以和computed()或watch()结合使用,它们都是基于响应式数据进行的计算。

    js
    import { computed, reactive } from 'vue';
    
    // 1. 通过reactive创建响应式数据
    const state = reactive({
      count: 0
    });
    
    // 2. 结合computed返回计算属性
    const doubleCount = computed(() => state.count * 2);
    
    // 3. 结合watch监听数据的变化
    watch(() => state.count, (newValue, oldValue) => {
      console.log(`count changed from ${oldValue} to ${newValue}`);
    });
    state.count = 1;  // 触发 watcher
  • 应用场景

    • 管理复杂状态,特别是包含多个属性或深层嵌套属性的对象。
    • 管理组件状态,可以确保状态的变更会自动触发视图的更新。

computed()

computed()<T>(getter | options),用于声明基于响应式依赖的计算属性,适合处理需要动态计算的逻辑,且结果会被缓存。

  • getter()=>T,纯函数,返回只读的计算后的值。

  • options{get:()=>T,set:(value:T)=>void},包含 getter/setter 的对象的可写计算属性。

  • 返回:

  • ComputedRef{value:T},响应式引用对象,模板中自动解包,脚本中需用 .value 访问。

  • 示例:

    • 只读计算属性
    js
    // 常用用法:基础计算属性
    
    import { ref, computed } from 'vue';
    
    const firstName = ref('John');
    const lastName = ref('Doe');
    
    // 只读计算属性
    const fullName = computed(() => {
        return `${firstName.value} ${lastName.value}`;
    });
    • 可写计算属性
    js
    // 进阶用法:可写计算属性
    
    // 可写计算属性
    const fullName = computed({
        get() {
            return `${firstName.value} ${lastName.value}`;
        },
        set(newValue) {
            [firstName.value, lastName.value] = newValue.split(' ');
        }
    });
    
    // 修改会触发 setter
    fullName.value = 'Jane Smith';
  • 特性:

  • TS

    ts
    // 只读
    function computed<T>(
      getter: (oldValue: T | undefined) => T,
      // 查看下方的 "计算属性调试" 链接
      debuggerOptions?: DebuggerOptions
    ): Readonly<Ref<Readonly<T>>>
    
    // 可写的
    function computed<T>(
      options: {
        get: (oldValue: T | undefined) => T
        set: (value: T) => void
      },
      debuggerOptions?: DebuggerOptions
    ): Ref<T>
  • 选项式

    js
    export default {
        data() {
            return {
                firstName: 'John',
                lastName: 'Doe'
            }
        },
        computed: {
            // 1. 只读
            fullName() {
                return `${this.firstName} ${this.lastName}`;
            },
            // 2. 可写
            fullNameWithSetter: {
                get() {
                    return `${this.firstName} ${this.lastName}`;
                },
                set(newValue) {
                    [this.firstName, this.lastName] = newValue.split(' ');
                }
            }
        }
    }
  • 依赖收集:只有被 getter 实际使用的响应式依赖才会触发更新。

  • 避免副作用:不要在计算属性中执行异步操作或修改 DOM,这属于副作用,应使用 methods 或生命周期钩子。

  • 缓存机制:计算属性基于依赖缓存,适合处理高开销逻辑(如遍历大数组)。若无需缓存,改用methods

  • Setter 使用场景:需要双向绑定计算属性(如v-model)。Setter 应仅修改其依赖的原始数据。

  • 解包规则:组合式 API 中需用 .value 访问值,但在模板中自动解包。

  • 对比methods

    js
    methods: {
      getFullName() { return this.firstName + this.lastName; } // 每次调用都执行
    },
    computed: {
      fullName() { return this.firstName + this.lastName; }    // 依赖变化才执行
    }

watch()

watch()<T>(source,callback,options?),用于监听响应式数据变化并执行回调函数,适合处理异步操作、复杂逻辑或需要观察特定数据变动的场景。

  • sourceWatchSource<T> | WatchSource<T>[],监听的数据源。类型可以是 refreactivegetter函数 或包含这些类型的数组。

  • callback(newValue,oldValue?,onCleanup?)=>void,数据变化时的回调函数。

    • newValueT|T[],变化后的新值。
    • oldValue?T|T[],变化前的旧值。
    • onCleanup?(cleanupFn:()=>void)=>void,注册清理副作用的函数,如取消未完成的异步请求。
  • options?{immediate?,deep?,flush?,once?},配置对象。

    • immediate?boolean默认:false,是否在侦听器创建时立即触发回调。
    • deep?boolean默认:false,是否深度监听对象/数组内部变化。
    • flush?'pre' | 'post' | 'sync'默认:'pre',控制回调触发时机。DOM更新前、更新后、更新同步。
  • 返回:

  • stopHandle{():void,stop,pause,resume},返回一个stop函数,可以调用它来停止侦听器的执行。

    • stop()()=>void,和stop一样,停止侦听器的执行。
    • pause()()=>void@3.5,暂停侦听器。
    • resume()()=>void@3.5,恢复侦听器。
  • 示例:

    • 常用用法
    js
    // 常用用法
    // 1. 监听单个 ref
    const count = ref(0);
    watch(count, (newVal, oldVal) => {
      console.log(`计数从 ${oldVal} 变为 ${newVal}`);
    });
    
    // 2. 监听 reactive 对象的属性
    const user = reactive({ name: 'Alice', age: 25 });
    watch(
      () => user.age,
      (newAge) => {
        console.log('年龄更新:', newAge);
      }
    );
    
    // 3. 监听多个数据源
    watch([count, () => user.name], ([newCount, newName]) => {
      console.log(`计数: ${newCount}, 用户名: ${newName}`);
    });
    • 进阶用法
    js
    // 进阶用法
    // 1. 深度监听对象并立即触发
    const config = reactive({ theme: 'dark', layout: 'grid' });
    watch(
      () => ({ ...config }), // 返回新对象触发深度监听
      (newConfig) => {
        localStorage.setItem('config', JSON.stringify(newConfig));
      },
      { deep: true, immediate: true }
    );
    
    // 2. 动态停止监听
    let stopHandle;
    const setupWatcher = () => {
      stopHandle = watch(count, (newVal) => {
        if (newVal > 10) stopHandle();
      });
    };
  • 特性:

  • TS

    ts
    // 侦听单个来源
    function watch<T>(
      source: WatchSource<T>,
      callback: WatchCallback<T>,
      options?: WatchOptions
    ): WatchHandle
    
    // 侦听多个来源
    function watch<T>(
      sources: WatchSource<T>[],
      callback: WatchCallback<T[]>,
      options?: WatchOptions
    ): WatchHandle
    
    type WatchCallback<T> = (
      value: T,
      oldValue: T,
      onCleanup: (cleanupFn: () => void) => void
    ) => void
    
    type WatchSource<T> =
      | Ref<T> // ref
      | (() => T) // getter
      | (T extends object ? T : never) // 响应式对象
    
    interface WatchOptions extends WatchEffectOptions {
      immediate?: boolean // 默认:false
      deep?: boolean | number // 默认:false
      flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
      onTrack?: (event: DebuggerEvent) => void
      onTrigger?: (event: DebuggerEvent) => void
      once?: boolean // 默认:false (3.4+)
    }
    
    interface WatchHandle {
      (): void // 可调用,与 `stop` 相同
      pause: () => void
      resume: () => void
      stop: () => void
    }
  • 性能

    • 避免滥用深度监听(deep: true),尤其是大型对象/数组,可能导致性能问题。

    • 优先监听具体属性而非整个对象,如 'user.name' 而非 user

  • 初始触发:配置 immediate: true 会在组件创建时立即执行回调,newVal 为初始值,oldValundefined

  • 异步操作:在回调中进行异步操作时,需考虑组件可能已卸载的情况。可用 cleanupAbortController 终止未完成操作。

  • 对比computed:需要执行副作用(如 API 请求、DOM 操作)时用 watch,仅依赖计算值时用 computed

  • 数组监听:直接修改数组元素(如 arr[0] = 1)不会触发监听,需使用变更方法(push/splice)或替换整个数组。

watchEffect()

watchEffect()(effect,options?),会立即执行并跟踪函数内部访问的所有响应式数据。任何响应式数据发生变化时,回调函数会重新执行。

  • effect(onCleanup)=>void,它在注册时会立即执行一次,执行时会访问响应式数据。每当其中访问的数据变化时,watchEffect()会重新执行该函数。

  • options?{flush?,...},配置项对象,用于控制watchEffect()的行为。

    • flush?'pre' | 'post' | 'sync'默认:'pre',控制回调触发时机。DOM更新前、更新后、更新同步。
  • 返回:

  • stop{():void,stop,pause,resume},返回一个stop函数,可以调用它来停止侦听器的执行。

    • stop()()=>void,和stop一样,停止侦听器的执行。
    • pause()()=>void@3.5,暂停侦听器。
    • resume()()=>void@3.5,恢复侦听器。
  • 示例:

    • 渲染实时同步数据
    js
    import { ref, watchEffect } from 'vue';
    
    const price = ref(100);
    const quantity = ref(2);
    
    // 渲染实时同步数据
    watchEffect(() => {
      document.title = `Total price: ${price.value * quantity.value}`;
    });
  • 特性:

  • TS

    ts
    function watchEffect(
      effect: (onCleanup: OnCleanup) => void,
      options?: WatchEffectOptions
    ): WatchHandle
    
    type OnCleanup = (cleanupFn: () => void) => void
    
    interface WatchEffectOptions {
      flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
      onTrack?: (event: DebuggerEvent) => void
      onTrigger?: (event: DebuggerEvent) => void
    }
    
    interface WatchHandle {
      (): void // 可调用,与 `stop` 相同
      pause: () => void
      resume: () => void
      stop: () => void
    }
  • 立即执行副作用:会在注册时立即执行一次effect函数,而不像watch()那样需要指定初始值。

  • 自动追踪响应式数据:会自动追踪在effect函数内部访问的响应式数据。

  • 不复杂条件或多个数据源:如果需要根据特定条件进行监控,或有多个数据源需要分开监听,推荐使用watch()。

  • 不适合深度监听:如果你需要监听对象的深层属性,推荐使用watch()配合deep:true

readonly()

readonly()<T extends object>(object:T),用于将一个响应式对象或普通对象变为只读对象,防止其被修改。

  • objectT,需要转换为只读的对象。

  • 返回:

  • readonlyObjectDeepReadonly,返回一个新的只读对象。如果尝试修改,会在开发环境下触发警告,生产环境下不会有任何反应。

  • 示例:

    • 将reactive对象变为只读
    js
    import { reactive, readonly } from 'vue';
    
    // 将reactive对象变为只读
    const state = reactive({
      count: 0
    });
    const readonlyState = readonly(state);
    
    // readonlyState 是只读的,无法修改
    readonlyState.count = 1;  // 会在开发环境下触发警告
    • 将ref对象变为只读
    js
    import { ref, readonly } from 'vue';
    
    // 将ref对象变为只读
    const count = ref(0);
    const readonlyCount = readonly(count);
    
    // readonlyCount 是只读的,无法修改
    readonlyCount.value = 1;  // 会在开发环境下触发警告
  • 特性:

  • TS

    ts
    function readonly<T extends object>(
      target: T
    ): DeepReadonly<UnwrapNestedRefs<T>>
  • 深层只读:对任何嵌套属性的访问都是只读的。要避免深层只读,推荐使用shallowReadonly()。

shallowReadonly()

shallowReadonly()(target),用于创建一个浅层只读代理对象,其顶层属性为只读,但嵌套对象仍保持可变。

  • targetObject,需要被代理的原始对象,可以是普通对象或响应式对象。

  • 返回:

  • proxyReadonly,一个代理对象,顶层属性为只读,嵌套属性保持原始可操作性。

  • 特性:

  • TS

    ts
    function shallowReadonly<T extends object>(target: T): Readonly<T>
    ts
    interface State {
      id: number;
      data: { content: string };
    }
    // TS类型约束
    const state = shallowReadonly<State>({
      id: 1,
      data: { content: 'Hello' }
    });
    
    state.id = 2; // ❌ TS 报错(顶层只读)
    state.data.content = 'Hi'; // ✅ 允许
  • 浅层只读

    • 顶层属性:只读,无法直接修改。
    • 嵌套属性:保留可变性,允许修改。
    js
    const state = shallowReadonly({
      count: 1,
      nested: { value: '可变' }
    });
    
    state.count = 2; // ❌ 失败(顶层只读)
    state.nested.value = '已修改'; // ✅ 成功(嵌套可变)
  • 响应式:若原始对象是响应式数据,代理对象会保持响应性,但顶层属性仍不可修改。

    js
    import { reactive, shallowReadonly } from 'vue';
    
    const state = reactive({
      user: { name: 'Alice' },
      settings: { theme: 'dark' }
    });
    
    const readonlyState = shallowReadonly(state);
    
    // 修改响应式源数据
    state.user.name = 'Bob'; // ✅ 生效
    readonlyState.user.name = 'Charlie'; // ✅ 生效(嵌套属性未受保护)
    
    // 尝试修改代理对象顶层属性
    readonlyState.settings = {}; // ❌ 报错
  • 使用场景

    • 安全传递Props:父组件传递数据给子组件时,确保子组件无法修改顶层属性,但允许修改深层数据。

      js
      // 父组件
      const parentState = reactive({ config: { theme: 'dark' } });
      const readonlyConfig = shallowReadonly(parentState);
      
      // 子组件接收后
      readonlyConfig.config.theme = 'light'; // ✅ 允许修改嵌套属性
      readonlyConfig.config = {};            // ❌ 禁止修改顶层属性
    • 部分状态保护:需要保护部分数据结构的顶层不可变,同时允许操作内部状态。

      js
      const state = shallowReadonly({
        id: 123, // 只读
        metadata: { tags: ['vue', 'js'] } // 可变
      });
      state.metadata.tags.push('react'); // ✅ 修改嵌套属性
    • 性能优化:对大型对象仅保护顶层属性,避免深度只读代理的性能开销。

  • 对比readonly:仅顶层只读,性能开销低。

  • ****:

生命周期钩子

onBeforeMount()

onBeforeMount()(callback),注册一个钩子,在组件被挂载之前被调用。

  • callback() => void,常用于设置或准备在组件挂载之前需要执行的逻辑。

  • 示例:

    js
    import { onBeforeMount } from 'vue';
    
    onBeforeMount(() => {
      console.log('组件即将挂载到 DOM 上');
    });
  • 特性:

  • TS

    ts
    function onBeforeMount(callback: () => void): void
  • 选项式beforeMount

  • SSR:不适用于SSR,仅在客户端执行。

  • 执行时机:组件已经完成响应式状态的设置,但还没有创建DOM节点。它即将首次执行DOM渲染过程。

  • 不适合修改DOM:此时DOM尚未准备好,应尽量避免在此钩子中直接操作DOM元素。

  • 常见用途

    • 初始化操作:用来做一些初始化工作,比如设置默认数据、获取外部数据等。
    • 异步数据加载:用来进行异步请求,确保数据加载完成后再开始挂载。
    • 插件或库的初始化:如果使用第三方库或插件,可以在这里进行初始化操作,准备好外部依赖。

onMounted()

onMounted()(callback),注册一个钩子,在组件挂载完成后执行。

  • callback() => void,无参数的回调函数,在组件挂载到 DOM 后执行。

  • 示例:

  • js
    import { onMounted, ref } from 'vue';
    
    const elementRef = ref(null);
    
    onMounted(() => {
        // 此时 DOM 已渲染完成
        console.log('Mounted! 元素宽度:', elementRef.value.offsetWidth);
        
        // 执行初始化操作
        fetchData();
    });
    
    // 模板中需绑定 ref
    // <div ref="elementRef"></div>
  • 特性:

  • TS

    ts
    function onMounted(callback: () => void): void
  • 选项式mounted

  • SSR:服务端渲染时不会执行,若需要兼容 SSR 应使用 onServerPrefetch

  • 执行时机:在组件挂载到DOM之后触发,适用于需要访问DOM元素的操作。

  • 不可在异步代码中调用:必须在 setup()<script setup> 中同步调用。

    js
    // 错误示范!
    setTimeout(() => {
        onMounted(() => console.log('这会抛出警告')); 
    }, 100);
  • 多次调用:可多次调用以添加多个回调,按注册顺序依次执行。

  • 异步操作:支持在其中执行异步函数,但需自行处理错误。

  • 依赖注入:可安全访问通过provide/inject传递的上下文。

  • DOM操作:推荐使用template ref代替document.querySelector获取元素。

    html
    <button ref="buttonRef">自动聚焦按钮</button>
    
    <script>
    const buttonRef = ref(null);
    onMounted(() => buttonRef.value.focus());
    </script>
  • 清理操作:始终在 onUnmounted() 中清理副作用。

    js
    onMounted(() => {
        const resizeObserver = new ResizeObserver(callback);
        resizeObserver.observe(elementRef.value);
    });
    
    // 清理副作用
    onUnmounted(() => resizeObserver.disconnect());

onBeforeUpdate()

onBeforeUpdate()(callback),注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。

  • callback() => void,无参数的回调函数,在组件即将重新渲染前触发。

  • 示例:

  • js
    import { onBeforeUpdate, ref } from 'vue'
    
    const count = ref(0)
    
    onBeforeUpdate(() => {
      console.log('DOM 即将更新,当前 count 值:', count.value)
    })
  • 特性:

  • TS

    ts
    function onBeforeUpdate(callback: () => void): void
  • 选项式beforeUpdate

  • SSR:不适用于SSR,仅在客户端执行。

  • 执行时机:在响应式数据变化后、DOM重新渲染前触发。在此钩子中修改响应式数据,会再次触发更新流程。

  • DOM访问:此时访问的DOM元素仍是更新前的状态。

onUpdated()

onUpdated()(callback),注册一个钩子,在组件因为响应式状态变更而更新其 DOM 树之后调用。

  • callback() => void,无参数的回调函数,在组件完成 DOM 重新渲染后触发。

  • 示例:

    js
    import { onUpdated, ref } from 'vue'
    
    const count = ref(0)
    
    onUpdated(() => {
      console.log('DOM 已更新,当前 count 值:', count.value)
      // 可安全操作更新后的 DOM
      const button = document.querySelector('button')
      if (button) {
        button.style.backgroundColor = '#f0f0f0'
      }
    })
  • 特性:

  • TS

    ts
    function onUpdated(callback: () => void): void
  • 选项式updated

  • SSR:不适用于SSR,仅在客户端执行。

  • 执行时机:在响应式数据变化导致DOM完成重新渲染后触发。在此钩子中修改响应式数据,会再次触发更新流程。

  • DOM操作:此时可以安全访问和操作更新后的DOM元素。

  • 性能影响:避免在此处执行高消耗操作,如频繁 DOM 查询,可能导致性能问题。

onBeforeUnmount()

onBeforeUnmount()(callback),注册一个钩子,在组件实例被卸载之前调用。

  • callback() => void,无参数的回调函数,在组件实例被卸载之前触发。

  • 示例:

    js
    import { onBeforeUnmount, onMounted, ref } from 'vue'
    
    const timerId = ref(null)
    
    onMounted(() => {
      timerId.value = setInterval(() => {
        console.log('定时器运行中...')
      }, 1000)
    })
    
    onBeforeUnmount(() => {
      clearInterval(timerId.value) // 清理定时器
      console.log('组件即将卸载,已清除资源')
    })
  • 特性:

  • TS

    ts
    function onBeforeUnmount(callback: () => void): void
  • 选项式beforeUnmount

  • SSR:不适用于SSR,仅在客户端执行。

  • 执行时机:在组件实例被卸载前,父组件销毁或条件渲染导致组件移除时触发。在此处修改响应式数据,不会触发更新,组件已进入卸载流程。

  • 使用场景:主要用途是清理副作用(如定时器、事件监听、网络请求取消、关闭 WebSocket 连接或第三方库实例等),避免内存泄漏。

onUnmounted()

onUnmounted()(callback),注册一个钩子,在组件实例被卸载之后调用。

  • callback() => void,无参数的回调函数,在组件实例及其 DOM 元素被完全卸载后触发。

  • 示例:

    js
    import { onUnmounted, onMounted } from 'vue'
    
    const eventListener = (e) => console.log('点击事件:', e)
    
    onMounted(() => {
      window.addEventListener('click', eventListener)
    })
    
    onUnmounted(() => {
      window.removeEventListener('click', eventListener) // 移除事件监听
      console.log('组件已卸载')
    })
  • 特性:

  • TS

    ts
    function onUnmounted(callback: () => void): void
  • 选项式unmounted

  • SSR:不适用于SSR,仅在客户端执行。

  • 执行时机:在组件实例及其 DOM 元素完全销毁后触发,此时无法访问组件实例的属性和 DOM 元素。在此处修改响应式数据,不会产生任何效果。

  • 使用场景

    • 主要用于清理与 DOM 无关的残留资源,如全局事件监听、手动创建的第三方库实例等。
    • 取消 Vuex 通过store.subscribe的订阅。

onActivated()@

onActivated()(callback),注册一个钩子,若组件实例是<KeepAlive>缓存树的一部分,当组件被插入到DOM中时调用。

  • callback() => void,无参数的回调函数,在组件被重新插入 DOM 时触发(从缓存恢复后)。

  • 示例:

    js
    import { onActivated, onDeactivated } from 'vue'
    
    let websocket = null
    
    onActivated(() => {
      // 重新连接 WebSocket(若缓存期间断开)
      websocket = new WebSocket('wss://api.example.com/ws')
      console.log('组件激活,WebSocket 已连接')
    })
    
    onDeactivated(() => {
      websocket?.close() // 组件切至后台时关闭连接
    })
  • 特性:

  • TS

    ts
    function onActivated(callback: () => void): void
  • 选项式activated

  • SSR:不适用于SSR,仅在客户端执行。

  • 执行时机

    • 仅当组件被<KeepAlive>包裹时生效。
    • 首次挂载时会同时触发 onMountedonActivated
    • 后续从缓存恢复时只触发onActivated,不触发 onMounted
  • DOM操作:此时组件已重新插入DOM树,可以安全访问DOM元素。

  • 配合onDeactivated:通常成对使用:在 onActivated 中初始化资源,在 onDeactivated 中清理资源。

  • 响应式:在此处修改响应式数据会触发组件更新,需避免不必要的重复操作。

  • 使用场景

    • 恢复被 <KeepAlive> 缓存时暂停的轮询请求。
    • 重新激活第三方库,如地图、图表控件的重新渲染。
    • 重置滚动位置到缓存前的状态。
    • 记录组件活跃状态,用于统计用户停留时长。

onDeactivated()@

onDeactivated()(callback),注册一个钩子,若组件实例是<KeepAlive>缓存树的一部分,当组件从DOM中被移除时调用。

  • callback() => void,无参数的回调函数,在组件被移出DOM但未销毁时触发(进入缓存状态)。

  • 示例:

    js
    import { onDeactivated, onActivated } from 'vue'
    
    let animationFrame = null
    
    const startAnimation = () => {
      // 模拟动画循环
      const animate = () => {
        animationFrame = requestAnimationFrame(animate)
        console.log('动画运行中...')
      }
      animate()
    }
    
    onActivated(() => {
      startAnimation() // 激活时启动动画
    })
    
    onDeactivated(() => {
      cancelAnimationFrame(animationFrame) // 切至后台时暂停动画
      console.log('组件已切至后台,动画暂停')
    })
  • 特性:

  • TS

    ts
    function onDeactivated(callback: () => void): void
  • 选项式deactivated

  • SSR:不适用于SSR,仅在客户端执行。

  • 执行条件:必须在组件被<KeepAlive>包裹时生效,普通组件的卸载会触发onUnmounted而非此钩子。

  • 对比onUnmounted

    • onDeactivated:组件仍保留在内存中,未被销毁,可再次通过激活恢复状态。
    • onUnmounted:组件被完全销毁。
  • 资源管理:主要用于释放非持久性资源,如暂停动画、断开实时连接,避免后台运行消耗性能。

  • 响应式:在此处修改响应式数据会保留修改结果,组件状态被缓存,但不会触发界面更新。

  • 使用场景

    • 暂停 requestAnimationFrame 动画循环。
    • 断开 WebSocket 或 SSE(Server-Sent Events)连接。
    • 停止数据轮询,如定时调用API。
    • 保存当前滚动位置,用于恢复时跳转。
    • 释放占用的浏览器资源,如摄像头/麦克风访问权限。

依赖注入

provide()

provide()<T>(key,value),提供一个值,可以被后代组件注入。

  • keyInjectionKey<T> | string,依赖的标识符,推荐使用 Symbol 避免命名冲突。

  • valueT,要传递的值,可以是响应式数据或普通值。

  • 示例:

    • 传递普通值
    js
    // 传递普通值
    // 1. 父组件 Parent.vue
    provide('apiUrl', 'https://api.example.com') // 提供静态字符串
    
    // 2. 子组件 Child.vue
    const apiUrl = inject('apiUrl') // 获取注入的值
    • 传递响应式数据
    js
    // 传递响应式数据
    // 1. 父组件 Parent.vue
    const count = ref(0) // 响应式数据
    provide('count', count) // 提供ref
    
    // 2. 子组件 Child.vue
    const count = inject('count') // 获取响应式ref
  • 特性:

  • TS

    ts
    function provide<T>(key: InjectionKey<T> | string, value: T): void
  • 选项式provide()

  • SSR:provide的响应式数据需确保在客户端激活后保持同步。

  • 响应式

    • 若需传递的值为响应式,必须显式使用ref/reactive。直接传递普通对象不会自动追踪变化。
    • 修改父组件中的响应式数据时,所有注入该数据的子组件会自动更新。
  • 注入默认值:使用inject(key, defaultValue)可指定默认值,避免未提供时返回 undefined

  • 作用域限制:提供的数据仅对后代组件可见(子组件、孙子组件等),同级或父组件无法访问。

  • 避免滥用:仅在跨多层级传递或共享全局配置时使用,父子组件通信推荐使用props

inject()

inject()<T>(key,defaultValue?),注入一个由祖先组件或整个应用通过app.provide()提供的值。

  • keyInjectionKey<T> | string,需与祖先组件provide()中定义的标识符一致。

  • defaultValue?T,当未找到对应的provide值时使用的默认值,可以是普通值或返回默认值的函数。

  • 返回:

  • resT|undefined,返回与key关联的提供值。若未找到返回undefined。

  • 示例:

    • 传递响应式数据
    js
    // 传递响应式数据
    // 1. 父组件 Parent.vue
    const count = ref(0) // 响应式数据
    provide('count', count) // 提供ref
    
    // 2. 子组件 Child.vue
    const count = inject('count') // 获取响应式ref
  • 特性:provide()

  • TS

    ts
    // 没有默认值
    function inject<T>(key: InjectionKey<T> | string): T | undefined
    
    // 带有默认值
    function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
    
    // 使用工厂函数
    function inject<T>(
      key: InjectionKey<T> | string,
      defaultValue: () => T,
      treatDefaultAsFactory: true
    ): T
  • 选项式inject()

script setup

<script setup>

<script setup>,是组合式 API 的编译时语法糖,旨在简化组件逻辑的编写,提高代码的可读性和开发效率。

  • 语法:在单文件组件(SFC)中,使用 setup 属性标记 <script> 标签。

    html
    <script setup>
    // 组合式 API 逻辑(自动暴露顶层变量)
    </script>
  • 示例:

    html
    <script setup>
    import { ref, defineProps, defineEmits, onMounted } from 'vue';
    
    // Props
    const props = defineProps({
      initialCount: { type: Number, default: 0 }
    });
    
    // Emits
    const emit = defineEmits(['countChange']);
    
    // 响应式数据
    const count = ref(props.initialCount);
    
    // 方法
    const increment = () => {
      count.value++;
      emit('countChange', count.value);
    };
    
    // 生命周期
    onMounted(() => {
      console.log('组件已挂载');
    });
    </script>
    
    <template>
      <button @click="increment">Count: {{ count }}</button>
    </template>
  • 特性:

  • TS

    • 类型化Props

      html
      <script setup lang="ts">
      interface Props {
        title: string;
        count?: number;
      }
      
      const props = defineProps<Props>();
      </script>
    • 类型化Emits

      html
      <script setup lang="ts">
      interface Emits {
        (e: 'updateCount', value: number): void;
      }
      
      const emit = defineEmits<Emits>();
      </script>
  • 基本用法

    • 自动暴露顶层变量:所有顶层声明的变量、函数、响应式数据均自动暴露给模板,无需手动 return。

      html
      <script setup>	
      import { ref } from 'vue';	
      
      const count = ref(0); // 自动在模板中可用
      </script>
      
      <template>
        <button @click="count++">{{ count }}</button>
      </template>
    • 声明Props和Emits

      • defineProps():声明组件接收的 props。
      • defineEmits():声明组件触发的事件。
      html
      <script setup>
      // 1. 声明 Props
      const props = defineProps({
        title: String,
        count: { type: Number, default: 0 }
      });
      
      // 2. 声明 Emits
      const emit = defineEmits(['updateCount']);
      const increment = () => {
        emit('updateCount', props.count + 1);
      };
      </script>
    • 响应式:使用 ref()reactive() 定义响应式数据。

      html
      <script setup>
      import { ref, reactive } from 'vue';
      
      const count = ref(0); // 基本类型
      const state = reactive({ name: 'Vue' }); // 对象类型
      </script>
    • 生命周期钩子:直接导入并调用生命周期钩子。

      html
      <script setup>
      import { onMounted } from 'vue';
      
      onMounted(() => {
        console.log('组件已挂载');
      });
      </script>
    • 计算属性

      html
      <script setup>
      import { computed } from 'vue';
      
      const doubleCount = computed(() => count.value * 2);
      </script>
    • 侦听器

      html
      <script setup>
      import { watch } from 'vue';
      
      watch(count, (newVal) => {
        console.log('count 变化:', newVal);
      });
      </script>
  • 进阶用法

    • 暴露组件方法/属性:使用defineExpose()暴露内容供父组件通过 ref 调用

      html
      <script setup>
      import { defineExpose, ref } from 'vue';
      
      const internalData = ref('内部数据');
      
      defineExpose({
        internalData
      });
      </script>
    • 模板引用ref:直接声明 ref 绑定 DOM 或子组件。

      html
      <script setup>
      import { ref } from 'vue';
      
      const inputRef = ref(null); // 绑定到模板中的 ref="inputRef"
      </script>
      
      <template>
        <input ref="inputRef">
      </template>
    • 动态组件与异步组件

      html
      <script setup>
      import { defineAsyncComponent } from 'vue';
      
      const AsyncComponent = defineAsyncComponent(() =>
        import('./AsyncComponent.vue')
      );
      </script>
  • 对比setup()

    • 代码结构

      • script setup:更简洁,自动暴露顶层变量。
      • setup():需手动 return 暴露数据。
    • Props/Emits声明

      • script setup:使用 defineProps()、defineEmits()。
      • setup():在 setup() 参数中解构 props 和 context。
    • TS支持

      • script setup:更好的类型推断。
      • setup():需要额外类型注解。
    • 代码复用

      • script setup:支持逻辑抽离为 Composables。
      • setup():支持逻辑抽离为 Composables。

defineProps()

defineProps()(propsDefinition),用于在script setup中声明组件的props。

  • propsDefinitionObject|Array<string>,定义 props 的格式

    • Object{Record<string,{type,required?,default?,validator>},定义每个prop的类型、默认值、验证器等。

      • type:数据类型:StringNumberBooleanArrayObjectDateFunctionSymbol、任何自定义构造函数、或上述内容组成的数组。

      • required?boolean,否是必填项。

      • default?any,默认值。

      • validator(prop)=>boolean,自定义验证函数。

    • Array<string>:仅声明prop名称,类型推断为any。

  • 返回:

  • props{ [key: string]: any },返回一个包含所有props的对象,可直接在模板或逻辑中使用。

  • 示例:

    • 对象形式定义 props
    js
    // 对象形式定义 props
    const props = defineProps({
      title: {
        type: String,
        required: true
      },
      count: {
        type: Number,
        default: 0
      }
    })
    • 数组形式定义 props
    js
    // 数组形式定义 props
    const props = defineProps(['title', 'count'])
  • 特性:

  • TS

    ts
    const props = defineProps<{
      foo: string
      bar?: number
    }>()
  • TS:script setup中的TS写法:

    • 泛型语法defineProps<{...}>()需确保 TypeScript 版本 ≥ 4.6。
    • 对于复杂的数据类型如数组、对象或联合类型,需使用 PropType 包裹。
    js
    // 使用 TypeScript 类型标注
    const props = defineProps<{
      user: { id: number name: string }
      disabled?: boolean
    }>()
    js
    // 带默认值的 TS 写法
    import { withDefaults } from 'vue'
    withDefaults(
      defineProps<{ size?: 'small' | 'medium' | 'large' }>(),
      { size: 'medium' }
    )
    ts
    // 联合类型需使用PropType包裹
    import type { PropType } from 'vue'
    defineProps({
      id: {
        type: [String, Number] as PropType<string | number>,
        required: true
      }
    })
  • 选项式props

  • 使用限制

    • 仅适用于script setup,不能在普通script或非setup上下文中使用。
    • 必须直接作为顶层语句,不可嵌套在条件/循环中。
  • 响应式:props自动为只读响应式对象,直接修改会触发警告,需通过emit通知父组件更新。

  • 验证函数

    • 可自定义validator函数进行复杂校验。

    • 如果TS版本低于4.7,validator函数要使用箭头函数。

      js
      defineProps({
        status: {
          type: String,
          validator: (value) => ['success', 'error'].includes(value)
        }
      })
  • 性能优化

    • 避免在 props 定义中使用复杂对象默认值,每次实例化会创建新对象,应改用工厂函数。
    • 如果TS版本低于4.7,default函数要使用箭头函数。
    js
    defineProps({
      config: {
        type: Object,
        default: () => ({ retry: 3, timeout: 5000 })
      }
    })

defineEmits()

defineEmits()(emitsDefinition),在script setup中声明组件可触发的事件。

  • emitsDefinitionObject|Array<string>,定义事件名称及其验证规则。

    • Object:为每个事件定义参数验证函数。

    • Array<string>:仅声明事件名称,无参数验证。

  • 返回:

  • emit(eventName: string,...args: any[])=>void,用于触发事件的函数。

  • 示例:

    • 基础事件触发
    js
    // 基础事件触发
    const emit = defineEmits(['submit', 'update:modelValue']) // 声明事件
    
    function handleClick() {
      emit('submit', { id: 1, status: 'done' }) // 触发带参数的事件
    }
    • 带参数验证
    js
    // 带参数验证
    const emit = defineEmits({
      // 无验证
      'update:title': null,
    
      // 带参数验证函数
      'submit'(payload) {
        // 必须返回 boolean 表示验证结果
        return !!payload?.id && typeof payload.status === 'string'
      }
    })
  • 特性:

  • TS

    ts
    const emit = defineEmits<{
      (e: 'change', id: number): void
      (e: 'update', value: string): void
    }>()
    
    // 3.3+:另一种更简洁的语法
    const emit = defineEmits<{
      change: [id: number] // 具名元组语法
      update: [value: string]
    }>()
  • TS:script setup中的TS写法:

    html
    <script setup lang="ts">
    // 使用 TS 类型定义事件参数
    const emit = defineEmits<{
      (e: 'update:modelValue', value: string): void
      (e: 'submit', payload: { id: number }): void
    }>()
    
    // 调用示例
    emit('submit', { id: 123 }) // ✅ 类型安全
    emit('submit', { id: 'abc' }) // ❌ TS 报错
    </script>
  • 选项式emits

  • 使用限制

    • 仅限script setup使用,且必须作为顶层语句,不可嵌套在条件/循环中。
    • 事件名推荐使用kebab-case,如 update:model-value
  • 参数验证

    • 验证函数返回true表示参数有效;返回false会在开发模式下触发控制台警告。
    • 验证失败不会阻止事件触发,仅用于调试提示。
  • 配合v-model:使用 update:modelValue 事件可实现自定义组件的双向绑定。

  • 响应式解构:若需解构 emit,需使用 toRefs 保持响应性。

    js
    const { emit } = toRefs(defineEmits(['submit']))

defineExpose()

defineExpose()(exposed),在script setup中显式暴露组件实例的公共属性。

  • exposedObject,包含需要暴露给父组件的属性和方法。可以是普通值、响应式数据、方法等。

  • 示例:

    • 暴露基础属性和方法
    html
    <!-- 1. 子组件 Child.vue -->
    <script setup>
    import { ref } from 'vue'
    
    const count = ref(0)
    const reset = () => { count.value = 0 }
    
    // 暴露 count 和 reset 给父组件
    defineExpose({ count, reset })
    </script>
    
    <!-- 2. 父组件 Parent.vue -->
    <template>
      <Child ref="childRef" />
    </template>
    
    <script setup>
    import { ref } from 'vue'
    const childRef = ref(null)
    
    // 访问子组件暴露的内容
    console.log(childRef.value?.count) // 0
    childRef.value?.reset() // 调用子组件方法
    </script>
    • 暴露响应式对象
    html
    <!-- 子组件 FormInput.vue -->
    <script setup>
    import { reactive } from 'vue'
    
    const state = reactive({ value: '', isValid: false })
    
    // 暴露整个响应式对象
    defineExpose({ state })
    </script>
  • 特性:

  • 使用限制:仅限script setup使用,且必须作为顶层语句,不可嵌套在条件/循环中。

  • 响应式:暴露的ref或reactive对象保持响应性,父组件修改会影响子组件内部状态。

  • TS类型标注:需为暴露的对象定义类型,避免父组件访问时类型推断错误。

    ts
    const count = ref(0)
    defineExpose({ count } as { count: number }) // 显式类型标注
  • 与expose冲突:若同时使用 defineExpose() 和选项式 expose,后者会覆盖前者。

  • 避免过度暴露:仅暴露必要的接口,维持组件封装性。暴露过多内部细节可能导致代码耦合。

defineSlots()@vue3.3

defineSlots()()@vue3.3,是 script setup 中引入的类型声明函数,用于显式定义组件的插槽类型,为 TypeScript 提供类型检查和智能提示支持。它仅在类型推导阶段生效,不会产生运行时逻辑。

  • 返回:

  • slotsSlots,返回一个 Slots 对象,这个对象可以访问组件的所有插槽。

  • 语法:

    html
    <script setup lang="ts">
    // 定义插槽类型
    const slots = defineSlots<{
      default?: (props: { item: string }) => VNode[]; // 默认插槽,接受作用域参数
      header?: () => VNode[]; // 具名插槽,无作用域参数
    }>();
    </script>
  • 特性:

    • 类型校验
      • 校验父组件/模板中插槽的名称
      • 校验父组件/模板中作用域参数及返回值类型
  • 常见用途:

    • 声明具名插槽和作用域插槽

      • 子组件:声明具名插槽和作用域插槽
      html
      <script setup lang="ts">
      interface Item {
        id: number;
        name: string;
      }
      
      // 定义插槽类型
      defineSlots<{
        // 默认插槽,接收作用域参数 item
        default?: (props: { item: Item }) => VNode[];
        // 具名插槽 header,无参数
        header?: () => VNode[];
        // 具名插槽 footer,接收参数
        footer?: (props: { total: number }) => VNode[];
      }>();
      </script>
      • 父组件:类型校验
      html
      <!-- 父组件使用子组件 -->
      <template>
        <ChildComponent :items="items">
          <template #header>标题</template>
            
          <template #default="{ item }">
            <!-- TypeScript 校验 item 的类型为 Item -->
            <span>{{ item.name }}</span>
          </template>
            
          <template #footer="{ total }">
            <p>总数: {{ total }}</p>
          </template>
        </ChildComponent>
      </template>
    • 动态插槽名类型声明:通过 TS 的模板字面量类型支持动态插槽名。

      js
      defineSlots<{
        // 动态插槽名:item-1, item-2, ...
        [key: `item-${number}`]: (props: { data: string }) => VNode[];
        default?: () => VNode[];
      }>();
  • 注意事项:

    • 仅限TS:主要用于 TypeScript 类型声明,在纯 JavaScript 中无实际作用。
    • 使用范围:只能在 script setup 或 setup() 中使用,不能在普通 script 或选项式 API 中使用。

useSlots()

useSlots()(),用于在 setup() 函数中访问组件的 插槽内容

  • 返回:

  • slotProxyProxy,返回一个包含所有插槽的代理对象,每个插槽对应一个返回虚拟节点数组VNodes的函数。

  • 语法:

    • 默认插槽slots.default?.()

    • 具名插槽slots.<name>?.(),如 slots.header?.()

    • 作用域插槽:通过插槽函数参数传递作用域数据。

    • 动态插槽名:使用动态插槽名时,需通过 slots[slotName]?.() 访问。

      js
      const slotName = 'dynamicSlot';
      const content = slots[slotName]?.();
    • 结合渲染函数:在 JSX/渲染函数中,插槽内容需手动调用插槽函数,如 slots.default?.()

  • 特性:

    • 响应式检测:useSlots() 返回的对象是响应式的,插槽内容变化会触发组件更新。

    • 对比$slots

      特性组合式 API (useSlots())选项式 API (this.$slots)
      访问方式通过函数调用获取响应式对象直接通过 this.$slots 访问
      作用域插槽统一通过函数参数传递作用域数据Vue 2 中需用 $scopedSlots
      响应式自动追踪变化非响应式(Vue 2)
      使用位置仅在 setup()<script setup>所有选项式生命周期和方法中
    • script setup:在SFC的 script setup 语法中,可直接使用 $slots。

      html
      <script setup>
      import { useSlots } from 'vue';
      const slots = useSlots();
      
      // 或直接使用编译宏 $slots(隐式注入)
      const hasHeader = !!$slots.header;
      </script>
      
      <template>
        <div>
          <slot name="header"></slot>
        </div>
      </template>
  • 使用场景:

    • 手动渲染插槽内容

      js
      import { h, useSlots } from 'vue';
      
      export default {
        setup() {
          const slots = useSlots();
          return () => h('div', [
            slots.header?.() || h('h1', '默认标题'),
            slots.default?.()
          ]);
        }
      }
    • 条件渲染插槽

      js
      import { useSlots } from 'vue';
      
      export default {
        setup() {
          const slots = useSlots();
          const hasFooter = !!slots.footer; // 检查是否存在 footer 插槽
          return { hasFooter };
        }
      }
    • 传递作用域数据

      html
      <!-- 父组件 -->
      <Child>
        <template #item="{ data }">
          {{ data.name }}
        </template>
      </Child>
      
      <!-- 子组件(Child.vue) -->
      <script>
      import { useSlots } from 'vue';
      
      export default {
        setup() {
          const slots = useSlots();
          const items = [{ name: 'Alice' }, { name: 'Bob' }];
          
          return () => (
            <ul>
              {items.map(item => slots.item?.({ data: item }))}
            </ul>
          );
        }
      }
      </script>
    • 动态渲染具名插槽

      js
      import { useSlots } from 'vue';
      
      export default {
        setup() {
          const slots = useSlots();
          return () => (
            <div>
              {['header', 'body', 'footer'].map(name => (
                slots[name]?.()
              ))}
            </div>
          );
        }
      }
  • 注意事项:

    • 存在性判断:若父组件未传递插槽,对应的插槽函数为 undefined。
    • 仅限setup中使用:useSlots() 必须在 setup() 函数或 script setup 中调用。
    • 避免直接操作VNodes:插槽内容是只读的,直接修改可能导致渲染错误。

useAttrs()

useAttrs()(),用于在 setup() 或 script setup 中访问组件接收的未声明为 props 的属性,如 class、style、自定义属性和事件监听器。这些属性默认会被自动绑定到组件的根元素,除非设置 inheritAttrs: false,通过 useAttrs() 可以手动控制它们的传递。

  • 返回:

  • attrsObjectRef,返回一个响应式对象。

  • 语法:导入与使用

    js
    import { useAttrs } from 'vue';
    
    export default {
      setup() {
        const attrs = useAttrs(); // 返回一个响应式对象
        return { attrs };
      }
    }
  • 特性:

    • 包含内容:attrs 对象包含以下未声明为 props 的属性:

      • HTML属性:如 idclassstyle
      • 自定义属性:如 data-*custom-prop
      • 事件监听器:如 @click@input(除非通过 emits 声明)。
    • 响应式对象:attrs 是响应式的,父组件传递的属性变化时会自动更新。

    • props的隔离:若属性在 props 中显式声明,则不会出现在 attrs 中。

    • 选项式APIthis.$attrs

    • script setup:在SFC的 script setup 语法中,可直接使用 $attrs。

      html
      <script setup>
      import { useAttrs } from 'vue';
      const attrs = useAttrs();
      
      // 或直接使用编译宏 $attrs(隐式注入)
      const hasClick = !!$attrs.onClick;
      </script>
      
      <template>
        <div v-bind="$attrs"></div>
      </template>
  • 常见用途:

    • 手动传递属性到子元素

      js
      <!-- 父组件 -->
      <MyComponent class="custom-class" data-id="123" @custom-event="handler" />
      
      <!-- 子组件(透传属性到内部元素) -->
      <script setup>
      import { useAttrs } from 'vue';
      const attrs = useAttrs();
      </script>
      
      <template>
        <div>
          <input v-bind="attrs" /> <!-- 将属性绑定到 input 元素 -->
        </div>
      </template>
    • 动态处理属性

      js
      import { useAttrs } from 'vue';
      
      export default {
        setup() {
          const attrs = useAttrs();
          // 根据属性动态生成样式
          const dynamicStyle = computed(() => ({
            ...attrs.style,
            color: attrs.disabled ? 'gray' : 'black'
          }));
          return { dynamicStyle };
        }
      }
    • 过滤特定属性

      js
      import { useAttrs } from 'vue';
      
      export default {
        setup() {
          const attrs = useAttrs();
          // 排除 class 属性,透传其他属性
          const filteredAttrs = computed(() => {
            const { class: _, ...rest } = attrs;
            return rest;
          });
          return { filteredAttrs };
        }
      }
    • 结合inheritAttrs: false

      js
      export default {
        inheritAttrs: false, // 禁止自动绑定到根元素
        setup() {
          const attrs = useAttrs();
          // 手动将 class 绑定到指定元素
          return () => (
            <div>
              <span :class="attrs.class">标题</span>
              <input v-bind="attrs" />
            </div>
          );
        }
      }
  • 注意事项:

    • 属性覆盖:手动绑定 v-bind="attrs" 时,若同时声明相同属性如 class,后者会覆盖前者。

      html
      <input :class="myClass" v-bind="attrs" />
    • 事件监听器的处理:未在 emits 中声明的事件监听器会以 onXxx 格式存在于 attrs 中,如 @clickonClick

    • 非响应式操作:直接解构 attrs 会丢失响应性,需使用 toRefs 或保持对象引用。

      js
      // 错误:解构后失去响应性
      const { class, style } = useAttrs();
      
      // 正确:保持响应式引用
      const attrs = useAttrs();
      const class = computed(() => attrs.class);

组件

基本组件

<Transition>

<Transition>mode? type? duration? appear?,用于在元素或组件的挂载卸载DOM 时添加动画效果。它通过自动应用 CSS 类名或 JavaScript 钩子函数实现平滑过渡。

  • 属性

  • mode?in-out | out-in默认:in-out,控制进入和离开动画的顺序。

    • in-out:新元素先进入,旧元素再离开(默认)。
    • out-in:旧元素先离开,新元素再进入。
  • type?animation | transition,指定动画类型,用于确定过渡时长。

    • animation:以 CSS 动画 animationend 事件确定时长。
    • transition:以 CSS 过渡 transitionend 事件确定时长。
  • duration?ms,手动指定动画时长。

  • appear?boolean,添加初始渲染动画。

  • 特性:

  • 过渡阶段类名:Transition会根据过渡阶段自动添加以下 CSS 类名,默认前缀为 v-

    • v-enter-fromclassName,进入动画的起始状态,元素插入前。
    • v-enter-activeclassName,进入动画的激活状态,整个进入过程。
    • v-enter-toclassName,进入动画的结束状态,元素插入后。
    • v-leave-fromclassName,离开动画的起始状态,元素移除前。
    • v-leave-activeclassName,离开动画的激活状态,整个离开过程。
    • v-leave-toclassName,离开动画的结束状态,元素移除后。
    html
    <template>
      <button @click="show = !show">切换</button>
      
      <Transition>
        <div v-if="show" class="box">内容</div>
      </Transition>
    </template>
    
    <script setup>
    import { ref } from 'vue';
    
    const show = ref(true);
    </script>
    
    <style>
    /* 定义过渡的 CSS 类 */
    .v-enter-active, .v-leave-active {
      transition: opacity 0.5s ease;
    }
    
    .v-enter-from, .v-leave-to {
      opacity: 0;
    }
    </style>
  • 钩子函数:通过事件监听实现更复杂的动画逻辑,如结合GSAP。

    • @before-enteronBeforeEnter: (el, done)=>void
    • @enteronEnter: (el, done)=>void
    • @after-enteronAfterEnter: (el, done)=>void
    • @enter-cancelledonEnterCancelled: (el, done)=>void
    • @before-leaveonBeforeLeave: (el, done)=>void
    • @leaveonLeave: (el, done)=>void
    • @after-leaveonAfterLeave: (el, done)=>void
    • @leave-cancelledonLeaveCancelled: (el, done)=>void
      • el:``,过渡元素。
      • done:``,回调函数,用于通知动画结束。
    html
    <Transition
      @before-enter="onBeforeEnter"
      @enter="onEnter"
      @after-enter="onAfterEnter"
      @enter-cancelled="onEnterCancelled"
      @before-leave="onBeforeLeave"
      @leave="onLeave"
      @after-leave="onAfterLeave"
      @leave-cancelled="onLeaveCancelled"
    >
      <!-- 元素 -->
    </Transition>
    
    <script setup>
    // el: 过渡元素
    // done: 回调函数,用于通知动画结束
    const onEnter = (el, done) => {
      gsap.from(el, {
        opacity: 0,
        duration: 0.5,
        onComplete: done
      });
    };
    </script>
  • 自定义类名前缀:通过 name 属性修改类名前缀。

    html
    <Transition name="fade">
      <!-- 元素 -->
    </Transition>
    
    <style>
    .fade-enter-active, .fade-leave-active {
      transition: opacity 0.5s;
    }
    .fade-enter-from, .fade-leave-to {
      opacity: 0;
    }
    </style>
  • 集成Animate.css库

    html
    <Transition
      name="custom-classes"
      enter-active-class="animate__animated animate__fadeIn"
      leave-active-class="animate__animated animate__fadeOut"
    >
      <div v-if="show">内容</div>
    </Transition>
  • 注意事项

    • 必须包裹单个根元素:Transition内部只能有一个根元素或组件。
    • 触发条件:元素必须通过 v-if、v-show 或动态组件<component :is="">触发。
    • 性能优化:优先使用 CSS 过渡,利用transform、opacity开启GPU硬件加速。
    • 首次渲染动画:通过 appear 属性添加初始渲染动画。
  • 示例:

    • 淡入淡出
    css
    /** 进入、离开过程 */
    .fade-enter-active,
    .fade-leave-active {
      transition: opacity 0.3s;
    }
    
    /** 初始、结束状态 */
    .fade-enter-from,
    .fade-leave-to {
      opacity: 0;
    }
    • 滑动效果
    css
    /** 进入、离开过程 */
    .slide-enter-active {
      transition: all 0.3s ease-out;
    }
    .slide-leave-active {
      transition: all 0.3s ease-in;
    }
    
    /** 初始、结束状态 */
    .slide-enter-from {
      transform: translateX(100px);
      opacity: 0;
    }
    .slide-leave-to {
      transform: translateX(-100px);
      opacity: 0;
    }
    • 缩放动画
    css
    /** 进入、离开过程 */
    .scale-enter-active,
    .scale-leave-active {
      transition: all 0.3s ease;
    }
    
    /** 初始、结束状态 */
    .scale-enter-from,
    .scale-leave-to {
      transform: scale(0);
      opacity: 0;
    }
    • 完整示例:动态标签切换 + 动画。
    html
    <template>
      <button @click="toggleView">切换视图</button>
      
      <Transition name="slide-fade" mode="out-in">
        <div v-if="isCardView" key="card" class="card">卡片视图</div>
        <div v-else key="list" class="list">列表视图</div>
      </Transition>
    </template>
    
    <script setup>
    import { ref } from 'vue';
    
    const isCardView = ref(true);
    const toggleView = () => (isCardView.value = !isCardView.value);
    </script>
    
    <style>
    .slide-fade-enter-active {
      transition: all 0.3s ease-out;
    }
    .slide-fade-leave-active {
      transition: all 0.3s ease-in;
    }
        
    .slide-fade-enter-from,
    .slide-fade-leave-to {
      transform: translateX(20px);
      opacity: 0;
    }
    </style>

<TransitionGroup>

<TransitionGroup>tag? move-class? css? ,用于在列表元素的进入、离开和位置变化时应用动画效果,特别适用于动态列表的平滑过渡。

  • 属性

  • tag?HTMLTagString默认:<span>,指定包裹列表的根元素标签。

  • move-class?className,手动指定位置变化时的 CSS 类名,覆盖默认的 name-move。

  • css?boolean默认:true,显式控制是否应用 CSS 过渡类。

  • 钩子函数

    • Transition的钩子函数:``,
    • @moveonMove: (el)=>void,元素位置变化时的钩子函数。
  • 特性:

  • 注意事项

    • 必须设置key:列表中的每个元素必须绑定唯一的 key,否则动画无法正确触发。
    • 定位处理:在离开动画中,为离开的元素设置 position: absolute,避免布局抖动。
  • 集成Animate.css库

    html
    <TransitionGroup
      name="list"
      tag="ul"
      enter-active-class="animate__animated animate__fadeInUp"
      leave-active-class="animate__animated animate__fadeOutDown"
    >
      <li v-for="item in items" :key="item.id">{{ item.text }}</li>
    </TransitionGroup>
  • 对比Transition

    • 目标元素:TransitionGroup包裹多个元素的列表;Transition包裹单个元素/组件。
    • DOM结构:TransitionGroup默认包裹一个span;Transition不渲染额外元素。
    • key:TransitionGroup每个子元素必须设置唯一的 key;Transition不需要 key。
    • 位置变化动画:TransitionGroup自动检测元素位置变化,应用移动动画;Transition不支持。
  • 示例:

    • 动态任务列表
    css
    <template>
      <div>
        <input v-model="newTask" @keyup.enter="addTask" placeholder="添加任务">
        <TransitionGroup name="task-list" tag="ul">
          <li v-for="task in tasks" :key="task.id" class="task-item">
            {{ task.text }}
            <button @click="removeTask(task.id)">×</button>
          </li>
        </TransitionGroup>
      </div>
    </template>
    
    <script setup>
    import { ref } from 'vue';
    
    const tasks = ref([
      { id: 1, text: '学习 Vue 3' },
      { id: 2, text: '编写组件' }
    ]);
    const newTask = ref('');
    
    const addTask = () => {
      if (newTask.value.trim()) {
        tasks.value.push({
          id: Date.now(),
          text: newTask.value.trim()
        });
        newTask.value = '';
      }
    };
    
    const removeTask = (id) => {
      tasks.value = tasks.value.filter(task => task.id !== id);
    };
    </script>
    
    <style>
    .task-list-enter-active,
    .task-list-leave-active,
    .task-list-move {
      transition: all 0.5s ease;
    }
    
    .task-list-enter-from,
    .task-list-leave-to {
      opacity: 0;
      transform: translateX(30px);
    }
    
    .task-list-leave-active {
      position: absolute;
      width: 100%;
    }
    
    .task-item {
      padding: 8px;
      margin: 4px 0;
      background: #f0f0f0;
      display: flex;
      justify-content: space-between;
    }
    </style>

<KeepAlive>

<KeepAlive>include? exclude? max?,用于缓存动态组件的实例,避免重复渲染,从而优化性能。

  • include?string | [] | RegExp,指定哪些组件需要缓存。

  • exclude?string | [] | RegExp,指定哪些组件不需要缓存。

  • max?number,限制最大缓存组件实例数,超出时销毁最久未访问的实例。

  • 特性:

  • 生命周期钩子:被缓存的组件会触发两个额外的生命周期钩子。

    • onActivated()(callback),组件被激活时调用(首次加载或从缓存中恢复)。
    • onDeactivated()(callback),组件被停用时调用(切换离开或被销毁前)。
  • 动态控制缓存

    • 通过 v-if 强制销毁:使用 v-if 手动控制是否缓存组件。

      html
      <KeepAlive>
        <component :is="currentComponent" v-if="shouldCache" />
      </KeepAlive>
    • 通过 key 强制刷新:改变 key 值强制销毁并重新创建组件。

      html
      <KeepAlive>
        <component :is="currentComponent" :key="componentKey" />
      </KeepAlive>
      
      <script setup>
      const componentKey = ref(0);
      const refreshComponent = () => componentKey.value++;
      </script>
  • 结合<router-view>:缓存路由页面组件,提升SPA体验。

    html
    <template>
      <router-view v-slot="{ Component }">
        <KeepAlive :include="['Home', 'Profile']">
          <component :is="Component" />
        </KeepAlive>
      </router-view>
    </template>
  • 注意事项

    • 组件命名:确保组件具有 name 选项,否则 include/exclude 无法匹配。

      js
      // TabA.vue
      export default {
        name: 'TabA',
        setup() { /* ... */ }
      }
    • 状态保留:被缓存的组件会保留所有状态,如数据、DOM滚动位置等,需通过 onActivated 重置必要状态。

    • 性能优化:合理使用 max,避免缓存过多实例,防止内存泄漏。

    • 异步组件:支持缓存异步组件,但需确保组件已解析。

  • 示例:缓存标签页

    html
    <template>
      <div>
        <button 
          v-for="tab in tabs" 
          :key="tab.name" 
          @click="currentTab = tab.component"
        >
          {{ tab.name }}
        </button>
    
        <KeepAlive :include="['TabA', 'TabB']">
          <component :is="currentTab" :key="currentTab.name" />
        </KeepAlive>
      </div>
    </template>
    
    <script setup>
    import { shallowRef } from 'vue';
    import TabA from './TabA.vue';
    import TabB from './TabB.vue';
    
    const tabs = [
      { name: 'TabA', component: TabA },
      { name: 'TabB', component: TabB }
    ];
    const currentTab = shallowRef(TabA);
    </script>

<Teleport>

<Teleport>to disabled?,用于将子组件或 DOM 元素 “传送”到 DOM 中的其他位置,常用于处理模态框Modal、通知Toast、弹出菜单等需要脱离父容器布局的场景。

  • toCSS选择器 | DOM,指定传送的目标容器,可以是 CSS 选择器或 DOM 元素。

  • disabled?boolean,禁用传送,内容保留在原位置。

  • 语法:

    html
    <!-- 传送到 body 末尾 -->
    <Teleport to="body">
      <div>内容</div>
    </Teleport>
    
    <!-- 传送到 ID 为 modal-root 的元素 -->
    <Teleport to="#modal-root">
      <Modal />
    </Teleport>
    
    <!-- 动态目标(通过 ref 获取的 DOM 元素) -->
    <Teleport :to="targetElement">
      <Popup />
    </Teleport>
    html
    <!-- public/index.html 或其他根 HTML 文件中 -->
    <body>
      <div id="app"></div>
      <div id="modal-root"></div> <!-- 目标容器 -->
    </body>
  • 特性:

  • 使用场景

    • 全局模态框:将模态框内容传送到<body>下,避免父组件 CSS 布局的影响。

      html
      <template>
        <button @click="showModal = true">打开模态框</button>
      
        <Teleport to="body">
          <div v-if="showModal" class="modal">
            <p>模态框内容</p>
            <button @click="showModal = false">关闭</button>
          </div>
        </Teleport>
      </template>
      
      <script setup>
      import { ref } from 'vue';
      const showModal = ref(false);
      </script>
      
      <style>
      .modal {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background: white;
        padding: 20px;
        z-index: 1000;
      }
      </style>
    • 通知消息:将多个通知传送到统一的容器中,便于管理和样式控制。

      html
      <template>
        <button @click="addNotification">添加通知</button>
      
        <Teleport to="#notification-container">
          <div v-for="msg in notifications" :key="msg.id" class="notification">
            {{ msg.text }}
          </div>
        </Teleport>
      </template>
      
      <script setup>
      import { ref } from 'vue';
      const notifications = ref([]);
      
      const addNotification = () => {
        notifications.value.push({
          id: Date.now(),
          text: `新通知 ${notifications.value.length + 1}`
        });
      };
      </script>
  • 注意事项

    • 目标容器必须存在:确保 to 指定的目标元素在组件挂载前已存在于 DOM 中。通常在public/index.html中预先定义。

      html
      <body>
        <div id="app"></div>
        <div id="modal-root"></div>
        <div id="notification-container"></div>
      </body>
    • 组件上下文保留:被传送的内容仍属于原组件的逻辑上下文,可以访问父组件的 props、data 和事件。

    • 样式隔离:传送后的内容可能受目标容器所在位置的 CSS 影响,需注意样式作用域,如使用 scoped 样式或 BEM 命名。

    • 多个Teleport到同一目标:多个Teleport可以传送到同一容器,内容按代码顺序依次追加。

  • 示例:动态传送的弹出菜单

    html
    <template>
      <div class="parent">
        <button @click="showMenu = true">显示菜单</button>
    
        <!-- 菜单内容传送到 body -->
        <Teleport to="body">
          <div v-if="showMenu" class="menu">
            <ul>
              <li @click="selectItem('选项1')">选项1</li>
              <li @click="selectItem('选项2')">选项2</li>
            </ul>
            <button @click="showMenu = false">关闭</button>
          </div>
        </Teleport>
      </div>
    </template>
    
    <script setup>
    import { ref } from 'vue';
    
    const showMenu = ref(false);
    const selectItem = (item) => {
      alert(`选择了:${item}`);
      showMenu.value = false;
    };
    </script>
    
    <style>
    .parent {
      position: relative;
      border: 1px solid #ccc;
      padding: 20px;
    }
    
    .menu {
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      background: white;
      border: 1px solid #666;
      padding: 20px;
      z-index: 1000;
    }
    </style>

<Suspense>

<Suspense>(),用于管理异步依赖如异步组件或异步数据加载的组件,允许在等待异步操作完成时展示备用内容如加载状态。

  • #defaultSlot,展示异步操作完成后的内容的插槽。

  • #fallbackSlot,在异步加载期间展示的插槽。

  • @error(error)=>void,捕获异步错误的事件。

  • timeoutnumber,设置最长等待时间,超时后强制显示默认内容,即使异步未完成。

  • 语法:

    html
    <template>
      <Suspense>
        <!-- 默认内容:展示异步操作完成后的内容 -->
        <template #default>
          <AsyncComponent />
        </template>
    
        <!-- 备用内容:在异步加载期间展示 -->
        <template #fallback>
          <div class="loading">加载中...</div>
        </template>
      </Suspense>
    </template>
    
    <script setup>
    // 异步组件(通过 defineAsyncComponent 定义)
    import { defineAsyncComponent } from 'vue';
    const AsyncComponent = defineAsyncComponent(() =>
      import('./AsyncComponent.vue')
    );
    </script>
  • 特性:

  • 核心功能

    • 处理异步组件:当组件通过 setup 函数返回 Promise 或使用 defineAsyncComponent 定义时,Suspense 会等待其解析。

    • 捕获异步错误

      • 通过 onErrorCaptured 钩子处理错误。
      html
      <script setup>
      import { onErrorCaptured } from 'vue';
      
      onErrorCaptured((error) => {
        console.error('捕获到异步错误:', error);
        return false; // 阻止错误继续冒泡
      });
      </script>
      • 通过 Suspense 的 @error 事件处理错误。
      html
      <template>
        <Suspense @error="handleError">
          <!-- ... -->
        </Suspense>
      </template>
      
      <script setup>
      const handleError = (error) => {
        console.error('异步加载失败:', error);
      };
      </script>
    • 嵌套异步依赖:Suspense 会等待所有嵌套的异步操作完成。

      html
      <template>
        <Suspense>
          <template #default>
            <!-- 包含多个异步子组件 -->
            <AsyncComponentA />
            <AsyncComponentB />
          </template>
            
          <template #fallback>
            <div>加载所有组件中...</div>
          </template>
        </Suspense>
      </template>
    • 手动控制加载状态:通过 v-if 或响应式状态控制 Suspense 的显示。

      html
      <template>
        <button @click="loadComponent">加载组件</button>
        <Suspense v-if="isLoading">
          <!-- ... -->
        </Suspense>
      </template>
      
      <script setup>
      const isLoading = ref(false);
      const loadComponent = () => {
        isLoading.value = true;
      };
      </script>
  • 注意事项

    • 单根节点限制:Suspense 的 #default#fallback 插槽必须包含单个根元素。
    • 避免过度嵌套:深层嵌套的 Suspense 可能导致状态管理复杂化。
  • 示例:数据加载 + 骨架屏

    html
    <template>
      <Suspense>
        <template #default>
          <ArticleList :articles="articles" />
        </template>
          
        <template #fallback>
          <SkeletonLoader /> <!-- 2. 骨架屏占位 -->
        </template>
      </Suspense>
    </template>
    
    <script setup>
    import { ref } from 'vue';
    import ArticleList from './ArticleList.vue';
    import SkeletonLoader from './SkeletonLoader.vue';
    
    // 1. 异步获取数据
    const articles = ref([]);
    const fetchArticles = async () => {
      const response = await fetch('/api/articles');
      articles.value = await response.json();
    };
    fetchArticles(); // 触发数据加载
    </script>

特殊元素

<component>

<component>:is key?,用于动态渲染组件或 HTML元素,通过 :is 属性绑定目标类型。常用于实现标签页切换、动态组件加载等场景。

  • :isstring | Component,指定要渲染的组件或 HTML 标签。

  • key?唯一标识,强制重新渲染组件,避免复用导致的副作用。

  • 特性:

  • 核心功能

    • 动态绑定组件:通过 :is 绑定组件名字符串或组件对象。

      html
      <!-- 绑定组件对象 -->
      <component :is="currentComponent" />
      
      <!-- 绑定组件名(需注册) -->
      <component :is="'ComponentA'" /> <!-- 注意:组件名要用引号包裹 -->
    • 动态渲染HTML元素:直接渲染原生标签。

      html
      <component :is="tagName" class="box">动态元素</component>
      
      <script setup>
      const tagName = ref('div'); // 可切换为 'h1', 'span' 等
      </script>
  • Props和事件传递:动态组件可以像普通组件一样传递属性和监听事件。

    html
    <component 
      :is="currentComponent" 
      :title="pageTitle"
      @submit="handleSubmit"
    />
  • 使用场景

    • 标签页切换

      html
      <template>
        <button @click="currentTab = TabA">TabA</button>
        <button @click="currentTab = TabB">TabB</button>
      
        <component :is="currentTab" :key="currentTab.name" />
      </template>
      
      <script setup>
      import { shallowRef } from 'vue';
      import TabA from './TabA.vue';
      import TabB from './TabB.vue';
      
      const currentTab = shallowRef(TabA);
      </script>
    • 动态表单控件:根据数据类型渲染不同输入组件。

      html
      <template>
        <component 
          :is="field.type + '-input'" 
          v-for="field in formFields" 
          :key="field.id"
          :field="field"
        />
      </template>
      
      <script setup>
      import TextInput from './TextInput.vue';
      import NumberInput from './NumberInput.vue';
      
      const formFields = ref([
        { id: 1, type: 'text', label: '姓名' },
        { id: 2, type: 'number', label: '年龄' }
      ]);
      </script>
    • 路由视图容器:模拟 <router-view> 的简易实现。

      html
      <component :is="routes[currentRoute]" />
      
      <script setup>
      import HomePage from './HomePage.vue';
      import AboutPage from './AboutPage.vue';
      
      const routes = {
        '/': HomePage,
        '/about': AboutPage
      };
      const currentRoute = ref('/');
      </script>
  • 结合KeepAlive:缓存动态组件状态,避免重复渲染。

    html
    <KeepAlive>
      <component :is="currentComponent" />
    </KeepAlive>
  • 注意事项

    • 组件注册:使用组件名时:is="'ComponentA'",需确保组件已全局或局部注册。

    • 性能优化

      • 使用 shallowRef 存储组件对象,避免深层次响应式转换。
      • 对高频切换的组件,结合 <KeepAlive> 减少渲染开销。
    • 错误处理:绑定不存在的组件时,会渲染为空节点。可通过 v-if 增加容错。

      html
      <component :is="currentComponent" v-if="currentComponent" />
  • 示例:动态组件加载器

    html
    <template>
      <div>
        <select v-model="componentName">
          <option value="TextBlock">文本块</option>
          <option value="ImageBlock">图片块</option>
        </select>
    
        <component 
          :is="components[componentName]" 
          :key="componentName"
          :content="blockData"
        />
      </div>
    </template>
    
    <script setup>
    import { ref, shallowRef } from 'vue';
    import TextBlock from './TextBlock.vue';
    import ImageBlock from './ImageBlock.vue';
    
    const components = {
      TextBlock,
      ImageBlock
    };
    const componentName = ref('TextBlock');
    const blockData = ref({ text: '默认内容' });
    </script>

is

isstring | Component,用于 动态组件解决 HTML 原生元素的解析限制

  • 特性:

  • 用途

    • 动态组件切换:通过 :is 属性绑定组件名或组件对象,实现动态切换组件。

    • 解决原生HTML解析限制:在原生 HTML 元素 <table><ul>中使用自定义组件时,浏览器会强制校验子元素类型。通过 is 属性绕过限制,使自定义组件能被正确解析。

      html
      <template>
        <table>
          <!-- 浏览器要求 <tbody> 内只能是 <tr>,通过 is 属性注入组件 -->
          <tbody>
            <tr is="vue:CustomRowComponent"></tr>
          </tbody>
        </table>
      </template>
      
      <script setup>
      import CustomRowComponent from './CustomRowComponent.vue';
      </script>
    • vue:前缀

      • 在 Vue3 中,如果直接在原生元素上使用 is<tr is="MyComponent">,浏览器会将其视为原生属性,可能导致解析错误。添加 vue: 前缀is="vue:MyComponent"可明确告诉 Vue 这是动态组件。
      • Vue2:不需要 vue: 前缀,直接 <tr is="MyComponent"> 即可。
  • 注意事项

    • 大小写敏感
      • 在HTML DOM模板中,组件名需使用 kebab-case,如 is="my-component"
      • 在单文件组件SFC或字符串模板中,可使用 PascalCase,如 is="MyComponent"

<slot>

<slot>name?,用于实现组件内容分发(插槽),在组合式API中,它允许父组件向子组件传递模板片段,并支持动态内容和作用域数据传递。

  • name?string,通过 name 属性指定多个具名插槽位置。

  • 语法:

    • 默认插槽:子组件定义插槽位置,父组件传递内容。

      • 子组件
      html
      <template>
        <div class="child">
          <!-- 默认插槽占位 -->
          <slot></slot>
        </div>
      </template>
      • 父组件
      html
      <template>
        <ChildComponent>
          <!-- 传递到默认插槽的内容 -->
          <p>这是父组件传递的内容</p>
        </ChildComponent>
      </template>
    • 具名插槽

      • 子组件:通过 name 属性指定多个插槽位置。
      html
      <template>
        <div>
          <header>
            <slot name="header"></slot>
          </header>
          <main>
            <slot></slot> <!-- 默认插槽 -->
          </main>
          <footer>
            <slot name="footer"></slot>
          </footer>
        </div>
      </template>
      • 父组件:使用 <template v-slot:name>#name 缩写传递内容。
      html
      <template>
        <ChildComponent>
          <!-- 具名插槽 -->
          <template #header>
            <h1>页头标题</h1>
          </template>
      
          <!-- 默认插槽 -->
          <p>主体内容</p>
      
          <!-- 具名插槽 -->
          <template #footer>
            <p>页脚信息</p>
          </template>
        </ChildComponent>
      </template>
    • 作用域插槽:子组件向父组件传递数据,父组件通过插槽访问。

      • 子组件:通过 <slot>属性 传递数据:
      html
      <template>
        <ul>
          <li v-for="(item, index) in items" :key="item.id">
            <!-- 向父组件传递 item 数据 -->
            <slot :item="item" :index="index"></slot>
          </li>
        </ul>
      </template>
      
      <script setup>
      const items = ref([{ id: 1, text: 'Item 1' }, { id: 2, text: 'Item 2' }]);
      </script>
      • 父组件:通过 v-slot:default="slotProps"#default="slotProps" 接收数据。
      html
      <template>
        <ChildComponent>
          <!-- 解构 slotProps -->
          <template #default="{ item, index }">
            <span>{{ index + 1 }}. {{ item.text }}</span>
          </template>
        </ChildComponent>
      </template>
    • 动态插槽名:通过动态指令参数绑定插槽名。

      • 父组件
      html
      <template>
        <ChildComponent>
          <template #[dynamicSlotName]>
            <p>动态插槽内容</p>
          </template>
        </ChildComponent>
      </template>
      
      <script setup>
      const dynamicSlotName = ref('header');
      </script>
    • useSlots():在script setup中,通过 useSlots() 访问插槽内容。

      • 子组件
      html
      <script setup>
      import { useSlots } from 'vue';
      
      const slots = useSlots();
      
      // 检查是否存在某个插槽
      if (slots.header) {
        console.log('header 插槽存在');
      }
      </script>
    • 渲染函数中的插槽:在 setup() 中使用渲染函数时,通过 this.$slotsthis.$scopedSlots 处理插槽。

      • 子组件
      html
      <script>
      import { h } from 'vue';
      
      export default {
        setup(props, { slots }) {
          return () => h('div', [
            slots.header ? slots.header() : h('div', '默认页头'),
            slots.default ? slots.default() : h('div', '默认内容'),
            slots.footer?.()
          ]);
        }
      };
      </script>
  • 特性:

  • 注意事项

    • 默认插槽的隐式传递:父组件中未包裹在template中的内容会自动传递到默认插槽。

    • 插槽内容渲染时机:插槽内容在父组件中编译,因此无法直接访问子组件的局部状态。

    • TS支持:在 script setup 中,使用 defineSlots() 声明插槽类型。

      html
      <script setup lang="ts">
      defineSlots<{
        default: (props: { msg: string }) => any;
        header?: () => any;
      }>();
      </script>
  • 示例:表格组件

    • 子组件
    html
    <template>
      <table>
        <thead>
          <tr>
            <!-- 具名插槽:表头 -->
            <slot name="header"></slot>
          </tr>
        </thead>
        <tbody>
          <!-- 作用域插槽:行数据 -->
          <tr v-for="(item, index) in data" :key="item.id">
            <slot :item="item" :index="index"></slot>
          </tr>
        </tbody>
      </table>
    </template>
    
    <script setup>
    defineProps({
      data: Array
    });
    </script>
    • 父组件
    html
    <template>
      <DataTable :data="users">
        <!-- 具名插槽:表头 -->
        <template #header>
          <th>ID</th>
          <th>姓名</th>
        </template>
    
        <!-- 作用域插槽:行内容 -->
        <template #default="{ item, index }">
          <td>{{ index + 1 }}</td>
          <td>{{ item.name }}</td>
        </template>
      </DataTable>
    </template>
    
    <script setup>
    const users = ref([
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' }
    ]);
    </script>

<template>

<template>,用于定义组件模板的核心标签。

  • 语法:

    html
    <template>
      <!-- 组件模板内容 -->
      <div>{{ message }}</div>
    </template>

ref

refref变量,用于获取 DOM 元素或子组件实例引用的特殊属性。

  • 语法:

    html
    <template>
      <!-- 1. 绑定ref属性 -->
      <div ref="myDiv">Hello</div> <!-- 绑定到 DOM 元素 -->
      <ChildComponent ref="child" /> <!-- 绑定到子组件 -->
    </template>
    
    <script>
    export default {
      mounted() {
        // 2. 访问 DOM 元素
        console.log(this.$refs.myDiv); // 输出: <div>Hello</div>
    
        // 2. 访问子组件实例
        this.$refs.child.someMethod(); // 调用子组件的方法
      }
    }
    </script>
  • 特性:

  • 组合式

    html
    <template>
      <!-- 2. 绑定声明的ref到ref属性上 -->
      <div ref="myDiv">Hello</div>
      <ChildComponent ref="child" />
    </template>
    
    <script setup>
    import { ref, onMounted } from 'vue';
    import ChildComponent from './ChildComponent.vue';
    
    // 1. 声明一个同名的 ref(变量名需与模板中的 ref 属性值一致)
    const myDiv = ref(null);
    const child = ref(null);
    
    onMounted(() => {
      // 3. 访问 DOM 元素
      console.log(myDiv.value); // <div>Hello</div>
    
      // 3. 访问子组件实例
      child.value.someMethod();
    });
    </script>
  • 动态ref

    • 动态名称

      html
      <template>
        <div v-for="i in 5" :key="i" :ref="'item' + i"></div>
      </template>
      
      <script>
      export default {
        mounted() {
          console.log(this.$refs.item3); // 第3个 div 元素
        }
      }
      </script>
    • 动态绑定函数

      html
      <template>
        <div :ref="(el) => { if (el) dynamicRefs.push(el) }"></div>
      </template>
      
      <script setup>
      import { ref } from 'vue';
      
      const dynamicRefs = ref([]);
      </script>
  • 访问组件实例:当 ref 绑定到子组件时,可访问其公开的属性和方法。

    • 父组件
    html
    <template>
      <!-- 2. 绑定声明的ref到ref属性上 -->
      <ChildComponent ref="child" />
    </template>
    
    <script setup>
    import { ref, onMounted } from 'vue';
    import ChildComponent from './ChildComponent.vue';
    
    // 1. 声明一个同名的 ref(变量名需与模板中的 ref 属性值一致)
    const child = ref(null);
    
    onMounted(() => {
      // 3. 访问子组件实例
      child.value.someMethod();
    });
    </script>
    • 子组件
    html
    <script setup>
    const someMethod = () => console.log('Method called!');
    
    // 明确暴露内容(组合式 API 需要)
    defineExpose({ someMethod });
    </script>
  • 生命周期时机:ref 在组件挂载后 mounted 中才可用,在 created 或 setup 的顶层作用域中值为 null。

  • 响应式更新:当 ref 绑定的元素被 v-if 或 v-for 动态渲染时,需检查引用是否存在。

    js
    if (myDiv.value) { // 安全操作 }
  • 避免过度使用:优先使用 props 和 emit 进行父子通信,仅在需要直接操作 DOM 或调用组件方法时使用 ref。

  • TS类型标注:组合式 API 中可为 ref 指定类型

    js
    // DOM 元素类型
    const myDiv = ref<HTMLDivElement | null>(null);
    
    // 组件实例类型
    const child = ref<InstanceType<typeof ChildComponent> | null>(null);
  • 示例:

    • 聚焦输入框:点击按钮,输入框聚焦
    html
    <template>
      <input ref="inputRef" type="text">
      <button @click="focusInput">Focus</button>
    </template>
    
    <script setup>
    import { ref } from 'vue';
    
    const inputRef = ref(null);
    
    const focusInput = () => {
      if (inputRef.value) {
        inputRef.value.focus();
      }
    };
    </script>

TypeScript

defineComponent()

defineComponent()(options),在定义Vue组件时提供类型推导的辅助函数

  • 语法:

    js
    import { defineComponent } from 'vue';
    
    const MyComponent = defineComponent({
      // 组件选项
      name: 'MyComponent',
      props: { /* 属性定义 */ },
      emits: { /* 自定义事件定义 */ },
      setup(props, context) {
        // 组合式API逻辑
        return { /* 暴露给模板的属性和方法 */ };
      }
    });
  • options{setup,data,methods,...},组件选项,包括选项式和组合式API。

  • 返回:

  • cpnComponentConstructor,返回创建的组件实例。

  • 示例:

    js
    import { defineComponent, ref, computed, onMounted } from 'vue';
    
    export default defineComponent({
      name: 'Counter',
      props: {
        initialCount: {
          type: Number,
          default: 0,
        },
      },
      emits: ['count-change'],
      setup(props, { emit }) {
        const count = ref(props.initialCount);
        const doubled = computed(() => count.value * 2);
    
        const increment = () => {
          count.value++;
          emit('count-change', count.value);
        };
    
        onMounted(() => {
          console.log('计数器已初始化');
        });
    
        return { count, doubled, increment };
      },
    });
  • 特性:

  • TS

    ts
    // 选项语法
    function defineComponent(
      component: ComponentOptions
    ): ComponentConstructor
    
    // 函数语法 (需要 3.3+)
    function defineComponent(
      setup: ComponentOptions['setup'],
      extraOptions?: ComponentOptions
    ): () => any

PropType<T>

PropType<T><TS类型>,用于 定义复杂props类型 的类型工具,尤其在结合TS时,它可以明确指定 props 的具体结构或构造函数类型。

  • 语法:

    ts
    import { defineComponent, PropType } from 'vue';
    
    interface User { id: number; name: string; }
    
    export default defineComponent({
      props: {
        // 使用 PropType 包裹类型
        user: {
          type: Object as PropType<User>, // 指定对象结构
          required: true
        },
        callback: {
          type: Function as PropType<(data: string) => void>, // 函数类型
          required: true
        }
      }
    });
  • 特性:

  • 核心用途:结合 as 类型断言声明复杂的props类型。

    • 声明对象类型

      ts
      props: {
        config: {
          type: Object as PropType<{ title: string; size: number }>,
          default: () => ({ title: '默认标题', size: 10 })
        }
      }
    • 声明数组类型

      ts
      props: {
        items: {
          type: Array as PropType<{ id: number; value: string }[]>,
          default: () => []
        }
      }
    • 声明联合类型

      ts
      type Status = 'loading' | 'success' | 'error';
      
      props: {
        status: {
          type: String as PropType<Status>,
          default: 'loading'
        }
      }
    • 声明函数类型

      ts
      props: {
        onSubmit: {
          type: Function as PropType<(payload: { data: string }) => boolean>,
          required: true
        }
      }
    • 声明类实例

      ts
      class DateRange {
        start: Date;
        end: Date;
      }
      
      props: {
        range: {
          type: Object as PropType<DateRange>,
          required: true
        }
      }
  • 结合defineProps():在组合式API的 <script setup> 中,直接通过泛型或类型断言定义 props。

    • 方法1:使用泛型参数推荐Vue@3.3+支持

      html
      <script setup lang="ts">
      interface Product {
        id: number;
        price: number;
      }
      
      const props = defineProps<{
        product: Product;
        tags: string[];
      }>();
      </script>
    • 方法2:使用类型断言

      html
      <script setup lang="ts">
      import { PropType } from 'vue';
      
      interface Product {
        id: number;
        price: number;
      }
      
      const props = defineProps({
        product: {
          type: Object as PropType<Product>,
          required: true
        },
        tags: {
          type: Array as PropType<string[]>,
          default: () => []
        }
      });
      </script>
  • 默认值:使用defineProps<...>()泛型语法时,默认值需通过 withDefaults 定义。

    js
    const props = withDefaults(
        defineProps<{ size?: number; }>(), 
        {size: 10}
    );
  • 运行时类型校验PropType<T> 仅提供 TS 类型检查,若需运行时校验,需额外使用 validator

    js
    props: {
      status: {
        type: String as PropType<Status>,
        validator: (value: string) => ['loading', 'success', 'error'].includes(value)
      }
    }

SSR

createSSRApp()

createSSRApp()(rootComponent,rootProps?),用于 服务端渲染 (SSR) 的应用程序创建函数,它在组合式API中的使用与客户端渲染CSR类似,但需要遵循特定的 SSR 配置和生命周期规则。

  • rootComponentoptions | Component,根组件,Vue 应用的入口组件。

    • options{data,methods,...},选项式API中的组件选项。

    • Component:组合式API中包含 setup() 方法的组件对象。

  • rootProps?Record<string, any>,传递给根组件的 props 对象,用于父组件向根组件传递数据。

  • 返回:

  • appApp,返回的Vue应用实例,提供多个用于配置和控制应用的实例方法。

  • 特性:

  • 服务端与客户端协作

    • 服务端:通过 renderToString() 生成 HTML 字符串。
    • 客户端:激活(hydrate)静态 HTML,使其成为交互式应用
  • 生命周期钩子

    • onServerPrefetch():在服务端渲染前预取数据。

      js
      import { onServerPrefetch } from 'vue'
      
      setup() {
        const data = ref(null)
        onServerPrefetch(async () => {
          data.value = await fetchData()
        })
        return { data }
      }
  • 服务端配置

    • 服务端入口server-entry.js

      js
      import { createSSRApp } from 'vue'
      import { renderToString } from 'vue/server-renderer'
      import App from './App.vue'
      import router from './router'
      
      export async function render(url, manifest) {
        const app = createSSRApp(App)
        app.use(router)
        
        // 设置路由位置
        router.push(url)
        await router.isReady()
      
        // 获取预取数据
        const ctx = {}
        const html = await renderToString(app, ctx)
      
        return { html }
      }
    • 客户端入口client-entry.js

      js
      import { createSSRApp } from 'vue'
      import App from './App.vue'
      import router from './router'
      
      const app = createSSRApp(App)
      app.use(router)
      
      // 等待路由就绪后挂载
      router.isReady().then(() => {
        app.mount('#app')
      })
  • 构建配置:Vite 配置示例。

    js
    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    
    export default defineConfig({
      plugins: [vue()],
      build: {
        ssr: true, // 启用 SSR 构建
        rollupOptions: {
          input: {
            server: './server-entry.js', // 服务端入口
            client: './client-entry.js' // 客户端入口
          }
        }
      }
    })
  • 注意事项

    • 共享状态:服务端预取的数据需传递到客户端。

      js
      // 服务端
      const pinia = createPinia()
      app.use(pinia)
      const data = await fetchData()
      pinia.state.value = data
      
      // 客户端
      const pinia = createPinia()
      if (window.__INITIAL_STATE__) {
        pinia.state.value = window.__INITIAL_STATE__
      }
    • 使用工厂函数避免状态污染:每次请求创建新的应用实例。

      js
      export function createApp() {
        const app = createSSRApp(App)
        const store = createStore()
        app.use(store)
        return { app, store }
      }
    • 浏览器API访问:避免在服务端访问 window 或 document。

      js
      if (import.meta.env.SSR) {
        // 服务端逻辑
      } else {
        // 客户端逻辑
      }
  • 示例:

    • 服务端渲染流程
    js
    // server.js (Node.js)
    import express from 'express'
    import { createServer } from 'http'
    import { render } from './server-entry.js'
    
    const server = express()
    
    server.get('*', async (req, res) => {
      const { html } = await render(req.url)
      res.send(`
        <!DOCTYPE html>
        <html>
          <head><title>Vue SSR</title></head>
          <body>
            <div id="app">${html}</div>
            <script src="/client.js"></script>
          </body>
        </html>
      `)
    })
    
    createServer(server).listen(3000)
    • 组件数据预取
    html
    <!-- App.vue -->
    <script setup>
    import { onServerPrefetch, ref } from 'vue'
    import { useRoute } from 'vue-router'
    import { fetchData } from './api'
    
    const data = ref(null)
    const route = useRoute()
    
    onServerPrefetch(async () => {
      data.value = await fetchData(route.params.id)
    })
    </script>
    
    <template>
      <div>{{ data }}</div>
    </template>

renderToString()

renderToString()(app),用于SSR的核心函数,它将 Vue 应用实例渲染为 HTML 字符串。在组合式API中,需结合 createSSRApp() 和 SSR 生命周期钩子使用。

  • appApp,创建的 SSR 应用实例。

  • 返回:

  • htmlHTML String,渲染为 HTML 字符串。

  • 示例:

    js
    import { createSSRApp } from 'vue'
    import { renderToString } from '@vue/server-renderer'
    import App from './App.vue'
    
    // 创建 SSR 应用实例
    const app = createSSRApp(App)
    
    // 渲染为 HTML 字符串
    const html = await renderToString(app)
    console.log(html) // 输出: <div>Hello SSR</div>

useId()@vue3.5

useId()(),用于为无障碍属性或表单元素生成每个应用内唯一的 ID。

  • 返回:

  • idstring

  • 特性:

  • 前缀支持:可以通过 app.config.idPrefix 为每个应用提供一个 ID 前缀,以避免 ID 冲突。

  • 使用场景

    • 表单元素关联:确保 <label> 的 for 属性和 <input> 的 id 一致。

      html
      <label :for="inputId">邮箱</label>
      <input :id="inputId" type="email">
    • 动态组件标识:为动态生成的组件提供唯一标识。

      html
      <div v-for="item in list" :key="useId()">
        {{ item.name }}
      </div>
    • 无障碍访问:为ARIA属性提供可靠的ID。

      html
      <div :aria-labelledby="labelId">内容</div>
      <div :id="labelId">标题</div>
  • 注意事项

    • SSR兼容性:服务端和客户端必须生成相同的ID,否则会导致Hydration错误。
    • 唯一性保证:避免在全局作用域直接使用计数器,可能因组件复用导致冲突,应通过函数闭包管理状态。
    • 性能优化:对高频更新的组件,使用 useId 生成的ID应保持稳定,避免不必要的重新渲染。
  • 示例:在组件中生成唯一ID,用于关联表单元素。

    html
    <script setup>
    import { useId } from './useId'
    
    const inputId = useId('input')
    </script>
    
    <template>
      <div>
        <label :for="inputId">用户名</label>
        <input :id="inputId" type="text" />
      </div>
    </template>