Vue3
[TOC]
索引
指令
插值语法:
- mustache:
{ {variable} }
,最基础的数据绑定方式,用于将数据动态渲染为纯文本内容。
文本渲染:
- v-text:
dataProperty
,用于将数据作为纯文本动态渲染到元素中。 - v-html:
dataProperty
,用于将数据作为原始 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
属性绑定目标类型。常用于实现标签页切换、动态组件加载等场景。 - is:
string | Component
,用于 动态组件 和 解决 HTML 原生元素的解析限制。 - <slot>:
name?
,用于实现组件内容分发(插槽),在组合式API中,它允许父组件向子组件传递模板片段,并支持动态内容和作用域数据传递。 - <template>:
,用于定义组件模板的核心标签。
- ref:
ref变量
,用于获取 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><span>危险内容</span></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 -->
jscomputed: { reversedMessage() { return this.message.split('').reverse().join(''); } }
结合过滤器:
- 在 Vue2 中,可通过过滤器格式化内容。
- 在 Vue3 中已经废弃了过滤器,推荐使用计算属性或方法。
html<div>{{ message | capitalize }}</div>
jsfilters: { 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-text:dataProperty
,用于将数据作为纯文本动态渲染到元素中。
dataProperty:
基本类型
,绑定的数据可以是字符串、数字或其他基本类型,非字符串类型会自动转换为字符串。示例:
html<div v-text="message"></div> <!-- 输出:Hello Vue! --> <div v-text="count"></div> <!-- 输出:42 -->
jsdata() { 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><script>alert(1)</script></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-html:dataProperty
,用于将数据作为原始 HTML 解析并渲染到元素中。
dataProperty:
HTMLString
,包含有效的 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><span style="color: red;">红色文字</span></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消毒。
jsimport 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> <div>{{ message }}</div> <button @click="show = true">显示</button> </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> -->
jsdata() { 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> -->
jsdata() { 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>
jsmethods: { handleClick() { console.log("按钮被点击"); } }
传递参数:
手动传递参数
html<button @click="handleDelete(item.id)">删除</button>
jsmethods: { handleDelete(id) { this.items = this.items.filter(item => item.id !== id); } }
获取原生事件对象
$event
:当需要同时传递参数和原生事件对象时,显式传递$event
。html<button @click="handleSubmit('参数', $event)">提交</button>
jsmethods: { 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>
jsdata() { 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
jsdata() { return { skills: [] }; }
单选按钮:
html<input type="radio" value="male" v-model="gender" /> 男 <input type="radio" value="female" v-model="gender" /> 女
jsdata() { 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>
jsdata() { return { isVisible: true } }
特性:
实现原理:
- 当表达式为 true 时:移除元素的
display: none
样式。 当表达式为 false 时:添加内联样式display: none
。
- 当表达式为 true 时:移除元素的
对比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-if
→v-else-if
→v-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 }} </元素>
item:
any
,当前遍历的元素值,数组项 / 对象属性值 / 数值。index?:
string | number
,当前项的索引或键名。data:
any
,支持数组、对象、数值范围。:key:
any
,用于标识元素唯一性,优化虚拟DOM的更新性能。特性:
遍历不同类型数据源:
遍历数组:
html<ul> <li v-for="(item, index) in items" :key="item.id"> {{ index + 1 }}. {{ item.name }} <!-- 输出:1. Apple--> </li> </ul>
jsdata() { 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>
jsdata() { 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>
jscomputed: { filteredList() { return this.list.filter(item => item.isActive); } }
性能优化:
避免超大列表:对超长列表(如1000+项)使用虚拟滚动技术,如 vue-virtual-scroller。
减少响应式依赖:对不需要响应式的静态数据,使用
Object.freeze
冻结。jsdata() { 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 可确保其内部状态如输入框内容、滚动位置不被意外保留。
- 优化虚拟DOM对比(Diff 算法):Vue 通过 key 识别节点身份,减少不必要的 DOM 操作。
应用场景:
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:销毁旧元素,初始化新元素。
- 虚拟DOM Diff过程:
其他
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 应用开发的入口函数。
rootComponent:
options | Component
,根组件,Vue 应用的入口组件。options
:{data,methods,...}
,选项式API中的组件选项。Component
:组合式API中包含setup()
方法的组件对象。
rootProps?:
Record<string, any>
,传递给根组件的props
对象,用于父组件向根组件传递数据。返回:
app:
App
,返回的Vue应用实例,提供多个用于配置和控制应用的实例方法。示例:
- script setup
jsimport { 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:
tsfunction createApp(rootComponent: Component, rootProps?: object): App
TS:使用
defineComponent()
增强类型推断。tsimport { 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等)或自定义功能扩展。
plugin:
Object | Function
,插件对象或函数。Object
:必须包含install()
方法。Function
:直接作为install()
方法调用。
...options?:
any[]
,传递给插件install
方法的额外参数(如插件配置项)。返回:
app:
App
,返回应用实例本身,支持链式调用。示例:
- 安装 Vue Router
jsimport { 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:
tsinterface 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 元素。
containerSelector:
string
,CSS 选择器字符串(如'#app'
),指定挂载的容器元素。element:
Element
,直接传入 DOM 元素对象(如document.getElementById('app')
)。返回:
instance:
ComponentPublicInstance
,返回根组件实例,可通过该实例访问组件属性/方法。示例:
- 挂载 app
jsimport { createApp } from 'vue'; import App from './App.vue'; // 创建应用实例 const app = createApp(App); // 挂载到 #app 元素 const rootInstance = app.mount('#app'); // 访问根组件数据 console.log(rootInstance.someData);
特性:
TS:
tsinterface App { mount(rootContainer: Element | string): ComponentPublicInstance }
TS类型注解:可以给instance添加TS类型注解。
tsinterface 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?)
,用于全局注册或获取组件。
name:
string
,组件的名称。命名遵循camelCase或kebab-case。component?:
Component
,包含组件配置的对象。返回:
- app:
App
,当传递component
参数时,表示注册全局组件。返回app,支持链式调用。 - component:
Component|undefined
,当省略component
参数时,表示获取已注册的组件。返回已注册的组件,未找到则返回undefined
。
- app:
示例:
- 注册/获取全局组件
jsimport { 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:
tsinterface App { component(name: string): Component | undefined component(name: string, component: Component): this }
app.directive()
app.directive():(name, directive?)
,用于注册或获取全局自定义指令。
name:
string
,指令的名称。命名通常遵循kebab-case。directive?:
{mounted,...}
,包含指令定义的对象。指令定义在不同的生命周期钩子中,控制指令在不同阶段的行为。返回:
app:
App
,返回app,支持链式调用。示例:
- 自定义指令 v-focus
jsconst app = createApp(App); // 1. 自定义指令:`v-focus`,使元素获得焦点 const focusDirective = { mounted(el) { el.focus(); } }; // 2. 注册全局指令 app.directive('focus', focusDirective); app.mount('#app');
特性:
TS:
tsinterface App { directive(name: string): Directive | undefined directive(name: string, directive: Directive): this }
选项式:
jsimport { 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,...}
,该对象的组件选项会被合并到每个组件的选项中。返回:
app:
App
,返回app,支持链式调用。示例:
- 全局混入对象
jsconst 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'); - 局部混入对象
jsconst localMixin = { data() { return { localMessage: 'This is local mixin data', }; } };
const app = Vue.createApp({ // 2. 注册局部混入对象 mixins: [localMixin], template: '<p>{{ localMessage }}</p>' }); app.mount('#app'); 特性:
TS:
tsinterface App { mixin(mixin: ComponentOptions): this }
生命周期钩子的合并:如果定义了多个相同的生命周期钩子,这些钩子会被合并,并按顺序依次调用。
数据合并:
- 如果data选项是一个函数,组件和混入中的data的返回值会被合并(推荐)。
- 如果data选项是一个普通对象,会出现合并冲突。
方法覆盖:组件的方法会覆盖混入中的方法。
不推荐:Mixins 在 Vue 3 支持主要是为了向后兼容,因为生态中有许多库使用到。在新的应用中应尽量避免使用 mixin,特别是全局 mixin。若要进行逻辑复用,推荐用组合式函数来替代。
通用
nextTick()
nextTick():(callback?)
,通常用来在下一次 DOM 更新刷新后执行某些操作。
callback?:
() => void
,在 DOM 更新后执行的回调函数。返回:
result:
Promise<void>
,在DOM更新完成后解析。示例:
- 在watch或onMounted中使用
jsimport { ref, watch, nextTick } from 'vue'; const count = ref(0); // 在watch或onMounted中使用 watch(count, async () => { await nextTick(); console.log('count 更新后,DOM 渲染完成'); });
- 与异步操作结合
jsimport { ref, nextTick } from 'vue'; const count = ref(0); const updateAndWait = async () => { count.value++; // 与异步操作结合 await nextTick(); console.log('DOM 更新后进行的操作'); };
特性:
TS:
tsfunction 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
。
- attrs?:
返回:
obj:
Object
,必须返回一个对象,供模板使用。示例:
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
访问其内部值。
initialValue:
T
,初始值。返回:
refValue:
Ref<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:
tsfunction ref<T>(value: T): Ref<UnwrapRef<T>> interface Ref<T> { value: T }
TS:通过泛型指定元素类型注解
tsconst inputRef = ref<HTMLInputElement | null>(null) onMounted(() => inputRef.value?.select())
响应式:ref()返回的值是一个响应式的值。
.value
改变时,依赖该值的视图会自动更新。底层通过Proxy实现。解包:
- 在模板中,Vue 会自动解包
ref
对象,可以直接访问和显示其值。 - 在 JS 中,需要通过
.value
来访问和修改ref
的值。
- 在模板中,Vue 会自动解包
对比reactive:
ref()
适用于单一的值,通常是基本数据类型,或你希望包装的对象。不能用于定义响应式数组元素。reactive()
用来创建一个深度响应式的对象,适用于更复杂的数据结构(如对象或数组),它会对对象的所有属性进行响应式处理。
异步操作:可以和异步网络请求操作结合使用,保存请求到的数据。
jsimport { 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实现。
initialObject:
Object|Array|Class Instance
,初始的对象或数组。不能是基本数据类型。返回:
obj:
UnwrapNestedRefs<T>
,返回一个响应式对象。示例:
jsimport { 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:
tsfunction reactive<T extends object>(target: T): UnwrapNestedRefs<T>
深度响应式:reactive() 会递归地将对象的每个属性都变成响应式的。
jsconst user = reactive({ name: 'Bob', address: { city: 'Paris', postalCode: '75000' } }); user.address.city = 'London'; // 这个操作是响应式的
响应式丢失:直接赋值新的对象,会造成响应式丢失。
jsconst state = reactive({ user: { name: 'John' } }); // 直接赋值新的对象,会造成响应式丢失,不会触发视图更新 state.user = { name: 'Jane' };
不适用于基本类型:只能用于对象类型,不能直接处理基本数据类型(
string
、number
、boolean
等),基本类型数据推荐使用ref()。结合computed或watch使用:可以和computed()或watch()结合使用,它们都是基于响应式数据进行的计算。
jsimport { 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>
选项式:
jsexport 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:
jsmethods: { getFullName() { return this.firstName + this.lastName; } // 每次调用都执行 }, computed: { fullName() { return this.firstName + this.lastName; } // 依赖变化才执行 }
watch()
watch():<T>(source,callback,options?)
,用于监听响应式数据变化并执行回调函数,适合处理异步操作、复杂逻辑或需要观察特定数据变动的场景。
source:
WatchSource<T> | WatchSource<T>[]
,监听的数据源。类型可以是ref
、reactive
、getter函数
或包含这些类型的数组。callback:
(newValue,oldValue?,onCleanup?)=>void
,数据变化时的回调函数。- newValue:
T|T[]
,变化后的新值。 - oldValue?:
T|T[]
,变化前的旧值。 - onCleanup?:
(cleanupFn:()=>void)=>void
,注册清理副作用的函数,如取消未完成的异步请求。
- newValue:
options?:
{immediate?,deep?,flush?,once?}
,配置对象。- immediate?:
boolean
,默认:false
,是否在侦听器创建时立即触发回调。 - deep?:
boolean
,默认:false
,是否深度监听对象/数组内部变化。 - flush?:
'pre' | 'post' | 'sync'
,默认:'pre'
,控制回调触发时机。DOM更新前、更新后、更新同步。
- immediate?:
返回:
stopHandle:
{():void,stop,pause,resume}
,返回一个stop函数,可以调用它来停止侦听器的执行。- stop():
()=>void
,和stop一样,停止侦听器的执行。 - pause():
()=>void
,@3.5
,暂停侦听器。 - resume():
()=>void
,@3.5
,恢复侦听器。
- stop():
示例:
- 常用用法
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
为初始值,oldVal
为undefined
。异步操作:在回调中进行异步操作时,需考虑组件可能已卸载的情况。可用
cleanup
或AbortController
终止未完成操作。对比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更新前、更新后、更新同步。
- flush?:
返回:
stop:
{():void,stop,pause,resume}
,返回一个stop函数,可以调用它来停止侦听器的执行。- stop():
()=>void
,和stop一样,停止侦听器的执行。 - pause():
()=>void
,@3.5
,暂停侦听器。 - resume():
()=>void
,@3.5
,恢复侦听器。
- stop():
示例:
- 渲染实时同步数据
jsimport { ref, watchEffect } from 'vue'; const price = ref(100); const quantity = ref(2); // 渲染实时同步数据 watchEffect(() => { document.title = `Total price: ${price.value * quantity.value}`; });
特性:
TS:
tsfunction 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)
,用于将一个响应式对象或普通对象变为只读对象,防止其被修改。
object:
T
,需要转换为只读的对象。返回:
readonlyObject:
DeepReadonly
,返回一个新的只读对象。如果尝试修改,会在开发环境下触发警告,生产环境下不会有任何反应。示例:
- 将reactive对象变为只读
jsimport { reactive, readonly } from 'vue'; // 将reactive对象变为只读 const state = reactive({ count: 0 }); const readonlyState = readonly(state); // readonlyState 是只读的,无法修改 readonlyState.count = 1; // 会在开发环境下触发警告
- 将ref对象变为只读
jsimport { ref, readonly } from 'vue'; // 将ref对象变为只读 const count = ref(0); const readonlyCount = readonly(count); // readonlyCount 是只读的,无法修改 readonlyCount.value = 1; // 会在开发环境下触发警告
特性:
TS:
tsfunction readonly<T extends object>( target: T ): DeepReadonly<UnwrapNestedRefs<T>>
深层只读:对任何嵌套属性的访问都是只读的。要避免深层只读,推荐使用shallowReadonly()。
shallowReadonly()
shallowReadonly():(target)
,用于创建一个浅层只读代理对象,其顶层属性为只读,但嵌套对象仍保持可变。
target:
Object
,需要被代理的原始对象,可以是普通对象或响应式对象。返回:
proxy:
Readonly
,一个代理对象,顶层属性为只读,嵌套属性保持原始可操作性。特性:
TS:
tsfunction shallowReadonly<T extends object>(target: T): Readonly<T>
tsinterface State { id: number; data: { content: string }; } // TS类型约束 const state = shallowReadonly<State>({ id: 1, data: { content: 'Hello' } }); state.id = 2; // ❌ TS 报错(顶层只读) state.data.content = 'Hi'; // ✅ 允许
浅层只读:
- 顶层属性:只读,无法直接修改。
- 嵌套属性:保留可变性,允许修改。
jsconst state = shallowReadonly({ count: 1, nested: { value: '可变' } }); state.count = 2; // ❌ 失败(顶层只读) state.nested.value = '已修改'; // ✅ 成功(嵌套可变)
响应式:若原始对象是响应式数据,代理对象会保持响应性,但顶层属性仍不可修改。
jsimport { 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 = {}; // ❌ 禁止修改顶层属性
部分状态保护:需要保护部分数据结构的顶层不可变,同时允许操作内部状态。
jsconst state = shallowReadonly({ id: 123, // 只读 metadata: { tags: ['vue', 'js'] } // 可变 }); state.metadata.tags.push('react'); // ✅ 修改嵌套属性
性能优化:对大型对象仅保护顶层属性,避免深度只读代理的性能开销。
对比readonly:仅顶层只读,性能开销低。
****:
生命周期钩子
onBeforeMount()
onBeforeMount():(callback)
,注册一个钩子,在组件被挂载之前被调用。
callback:
() => void
,常用于设置或准备在组件挂载之前需要执行的逻辑。示例:
jsimport { onBeforeMount } from 'vue'; onBeforeMount(() => { console.log('组件即将挂载到 DOM 上'); });
特性:
TS:
tsfunction 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:
tsfunction 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()
中清理副作用。jsonMounted(() => { 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:
tsfunction onBeforeUpdate(callback: () => void): void
选项式:beforeUpdate
SSR:不适用于SSR,仅在客户端执行。
执行时机:在响应式数据变化后、DOM重新渲染前触发。在此钩子中修改响应式数据,会再次触发更新流程。
DOM访问:此时访问的DOM元素仍是更新前的状态。
onUpdated()
onUpdated():(callback)
,注册一个钩子,在组件因为响应式状态变更而更新其 DOM 树之后调用。
callback:
() => void
,无参数的回调函数,在组件完成 DOM 重新渲染后触发。示例:
jsimport { 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:
tsfunction onUpdated(callback: () => void): void
选项式:updated
SSR:不适用于SSR,仅在客户端执行。
执行时机:在响应式数据变化导致DOM完成重新渲染后触发。在此钩子中修改响应式数据,会再次触发更新流程。
DOM操作:此时可以安全访问和操作更新后的DOM元素。
性能影响:避免在此处执行高消耗操作,如频繁 DOM 查询,可能导致性能问题。
onBeforeUnmount()
onBeforeUnmount():(callback)
,注册一个钩子,在组件实例被卸载之前调用。
callback:
() => void
,无参数的回调函数,在组件实例被卸载之前触发。示例:
jsimport { onBeforeUnmount, onMounted, ref } from 'vue' const timerId = ref(null) onMounted(() => { timerId.value = setInterval(() => { console.log('定时器运行中...') }, 1000) }) onBeforeUnmount(() => { clearInterval(timerId.value) // 清理定时器 console.log('组件即将卸载,已清除资源') })
特性:
TS:
tsfunction onBeforeUnmount(callback: () => void): void
选项式:beforeUnmount
SSR:不适用于SSR,仅在客户端执行。
执行时机:在组件实例被卸载前,父组件销毁或条件渲染导致组件移除时触发。在此处修改响应式数据,不会触发更新,组件已进入卸载流程。
使用场景:主要用途是清理副作用(如定时器、事件监听、网络请求取消、关闭 WebSocket 连接或第三方库实例等),避免内存泄漏。
onUnmounted()
onUnmounted():(callback)
,注册一个钩子,在组件实例被卸载之后调用。
callback:
() => void
,无参数的回调函数,在组件实例及其 DOM 元素被完全卸载后触发。示例:
jsimport { onUnmounted, onMounted } from 'vue' const eventListener = (e) => console.log('点击事件:', e) onMounted(() => { window.addEventListener('click', eventListener) }) onUnmounted(() => { window.removeEventListener('click', eventListener) // 移除事件监听 console.log('组件已卸载') })
特性:
TS:
tsfunction onUnmounted(callback: () => void): void
选项式:unmounted
SSR:不适用于SSR,仅在客户端执行。
执行时机:在组件实例及其 DOM 元素完全销毁后触发,此时无法访问组件实例的属性和 DOM 元素。在此处修改响应式数据,不会产生任何效果。
使用场景:
- 主要用于清理与 DOM 无关的残留资源,如全局事件监听、手动创建的第三方库实例等。
- 取消 Vuex 通过
store.subscribe
的订阅。
onActivated()@
onActivated():(callback)
,注册一个钩子,若组件实例是<KeepAlive>
缓存树的一部分,当组件被插入到DOM中时调用。
callback:
() => void
,无参数的回调函数,在组件被重新插入 DOM 时触发(从缓存恢复后)。示例:
jsimport { onActivated, onDeactivated } from 'vue' let websocket = null onActivated(() => { // 重新连接 WebSocket(若缓存期间断开) websocket = new WebSocket('wss://api.example.com/ws') console.log('组件激活,WebSocket 已连接') }) onDeactivated(() => { websocket?.close() // 组件切至后台时关闭连接 })
特性:
TS:
tsfunction onActivated(callback: () => void): void
选项式:activated
SSR:不适用于SSR,仅在客户端执行。
执行时机:
- 仅当组件被
<KeepAlive>
包裹时生效。 - 首次挂载时会同时触发
onMounted
和onActivated
。 - 后续从缓存恢复时只触发
onActivated
,不触发onMounted
。
- 仅当组件被
DOM操作:此时组件已重新插入DOM树,可以安全访问DOM元素。
配合onDeactivated:通常成对使用:在
onActivated
中初始化资源,在onDeactivated
中清理资源。响应式:在此处修改响应式数据会触发组件更新,需避免不必要的重复操作。
使用场景:
- 恢复被
<KeepAlive>
缓存时暂停的轮询请求。 - 重新激活第三方库,如地图、图表控件的重新渲染。
- 重置滚动位置到缓存前的状态。
- 记录组件活跃状态,用于统计用户停留时长。
- 恢复被
onDeactivated()@
onDeactivated():(callback)
,注册一个钩子,若组件实例是<KeepAlive>
缓存树的一部分,当组件从DOM中被移除时调用。
callback:
() => void
,无参数的回调函数,在组件被移出DOM但未销毁时触发(进入缓存状态)。示例:
jsimport { 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:
tsfunction onDeactivated(callback: () => void): void
选项式:deactivated
SSR:不适用于SSR,仅在客户端执行。
执行条件:必须在组件被
<KeepAlive>
包裹时生效,普通组件的卸载会触发onUnmounted
而非此钩子。对比onUnmounted:
onDeactivated
:组件仍保留在内存中,未被销毁,可再次通过激活恢复状态。onUnmounted
:组件被完全销毁。
资源管理:主要用于释放非持久性资源,如暂停动画、断开实时连接,避免后台运行消耗性能。
响应式:在此处修改响应式数据会保留修改结果,组件状态被缓存,但不会触发界面更新。
使用场景:
- 暂停
requestAnimationFrame
动画循环。 - 断开 WebSocket 或 SSE(Server-Sent Events)连接。
- 停止数据轮询,如定时调用API。
- 保存当前滚动位置,用于恢复时跳转。
- 释放占用的浏览器资源,如摄像头/麦克风访问权限。
- 暂停
依赖注入
provide()
provide():<T>(key,value)
,提供一个值,可以被后代组件注入。
key:
InjectionKey<T> | string
,依赖的标识符,推荐使用 Symbol 避免命名冲突。value:
T
,要传递的值,可以是响应式数据或普通值。示例:
- 传递普通值
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:
tsfunction 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()
提供的值。
key:
InjectionKey<T> | string
,需与祖先组件provide()
中定义的标识符一致。defaultValue?:
T
,当未找到对应的provide值时使用的默认值,可以是普通值或返回默认值的函数。返回:
res:
T|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。
propsDefinition:
Object|Array<string>
,定义 props 的格式Object
:{Record<string,{type,required?,default?,validator>}
,定义每个prop的类型、默认值、验证器等。type
:数据类型:String
、Number
、Boolean
、Array
、Object
、Date
、Function
、Symbol
、任何自定义构造函数、或上述内容组成的数组。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:
tsconst 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函数要使用箭头函数。jsdefineProps({ status: { type: String, validator: (value) => ['success', 'error'].includes(value) } })
性能优化:
- 避免在 props 定义中使用复杂对象默认值,每次实例化会创建新对象,应改用工厂函数。
- 如果TS版本低于
4.7
,default函数要使用箭头函数。
jsdefineProps({ config: { type: Object, default: () => ({ retry: 3, timeout: 5000 }) } })
defineEmits()
defineEmits():(emitsDefinition)
,在script setup中声明组件可触发的事件。
emitsDefinition:
Object|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:
tsconst 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
保持响应性。jsconst { emit } = toRefs(defineEmits(['submit']))
defineExpose()
defineExpose():(exposed)
,在script setup中显式暴露组件实例的公共属性。
exposed:
Object
,包含需要暴露给父组件的属性和方法。可以是普通值、响应式数据、方法等。示例:
- 暴露基础属性和方法
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类型标注:需为暴露的对象定义类型,避免父组件访问时类型推断错误。
tsconst count = ref(0) defineExpose({ count } as { count: number }) // 显式类型标注
与expose冲突:若同时使用 defineExpose() 和选项式 expose,后者会覆盖前者。
避免过度暴露:仅暴露必要的接口,维持组件封装性。暴露过多内部细节可能导致代码耦合。
defineSlots()@vue3.3
defineSlots():()
,@vue3.3
,是 script setup 中引入的类型声明函数,用于显式定义组件的插槽类型,为 TypeScript 提供类型检查和智能提示支持。它仅在类型推导阶段生效,不会产生运行时逻辑。
返回:
slots:
Slots
,返回一个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 的模板字面量类型支持动态插槽名。
jsdefineSlots<{ // 动态插槽名:item-1, item-2, ... [key: `item-${number}`]: (props: { data: string }) => VNode[]; default?: () => VNode[]; }>();
注意事项:
- 仅限TS:主要用于 TypeScript 类型声明,在纯 JavaScript 中无实际作用。
- 使用范围:只能在 script setup 或 setup() 中使用,不能在普通 script 或选项式 API 中使用。
useSlots()
useSlots():()
,用于在 setup() 函数中访问组件的 插槽内容。
返回:
slotProxy:
Proxy
,返回一个包含所有插槽的代理对象,每个插槽对应一个返回虚拟节点数组VNodes的函数。语法:
默认插槽:
slots.default?.()
。具名插槽:
slots.<name>?.()
,如slots.header?.()
。作用域插槽:通过插槽函数参数传递作用域数据。
动态插槽名:使用动态插槽名时,需通过
slots[slotName]?.()
访问。jsconst 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>
使用场景:
手动渲染插槽内容:
jsimport { h, useSlots } from 'vue'; export default { setup() { const slots = useSlots(); return () => h('div', [ slots.header?.() || h('h1', '默认标题'), slots.default?.() ]); } }
条件渲染插槽:
jsimport { 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>
动态渲染具名插槽:
jsimport { 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() 可以手动控制它们的传递。
返回:
attrs:
ObjectRef
,返回一个响应式对象。语法:导入与使用:
jsimport { useAttrs } from 'vue'; export default { setup() { const attrs = useAttrs(); // 返回一个响应式对象 return { attrs }; } }
特性:
包含内容:attrs 对象包含以下未声明为 props 的属性:
- HTML属性:如
id
、class
、style
。 - 自定义属性:如
data-*
、custom-prop
。 - 事件监听器:如
@click
、@input
(除非通过emits
声明)。
- HTML属性:如
响应式对象:attrs 是响应式的,父组件传递的属性变化时会自动更新。
props的隔离:若属性在 props 中显式声明,则不会出现在 attrs 中。
选项式API:
this.$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>
动态处理属性:
jsimport { useAttrs } from 'vue'; export default { setup() { const attrs = useAttrs(); // 根据属性动态生成样式 const dynamicStyle = computed(() => ({ ...attrs.style, color: attrs.disabled ? 'gray' : 'black' })); return { dynamicStyle }; } }
过滤特定属性:
jsimport { useAttrs } from 'vue'; export default { setup() { const attrs = useAttrs(); // 排除 class 属性,透传其他属性 const filteredAttrs = computed(() => { const { class: _, ...rest } = attrs; return rest; }); return { filteredAttrs }; } }
结合
inheritAttrs: false
:jsexport 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 中,如
@click
→onClick
。非响应式操作:直接解构 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-from:
className
,进入动画的起始状态,元素插入前。 - v-enter-active:
className
,进入动画的激活状态,整个进入过程。 - v-enter-to:
className
,进入动画的结束状态,元素插入后。 - v-leave-from:
className
,离开动画的起始状态,元素移除前。 - v-leave-active:
className
,离开动画的激活状态,整个离开过程。 - v-leave-to:
className
,离开动画的结束状态,元素移除后。
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> - v-enter-from:
钩子函数:通过事件监听实现更复杂的动画逻辑,如结合GSAP。
- @before-enter:
onBeforeEnter: (el, done)=>void
, - @enter:
onEnter: (el, done)=>void
, - @after-enter:
onAfterEnter: (el, done)=>void
, - @enter-cancelled:
onEnterCancelled: (el, done)=>void
, - @before-leave:
onBeforeLeave: (el, done)=>void
, - @leave:
onLeave: (el, done)=>void
, - @after-leave:
onAfterLeave: (el, done)=>void
, - @leave-cancelled:
onLeaveCancelled: (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>
- @before-enter:
自定义类名前缀:通过 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的钩子函数:``,
- @move:
onMove: (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)
,组件被停用时调用(切换离开或被销毁前)。
- onActivated():
动态控制缓存:
通过 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、弹出菜单等需要脱离父容器布局的场景。
to:
CSS选择器 | 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>:()
,用于管理异步依赖如异步组件或异步数据加载的组件,允许在等待异步操作完成时展示备用内容如加载状态。
#default:
Slot
,展示异步操作完成后的内容的插槽。#fallback:
Slot
,在异步加载期间展示的插槽。@error:
(error)=>void
,捕获异步错误的事件。timeout:
number
,设置最长等待时间,超时后强制显示默认内容,即使异步未完成。语法:
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 可能导致状态管理复杂化。
- 单根节点限制: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
属性绑定目标类型。常用于实现标签页切换、动态组件加载等场景。
:is:
string | 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
is:string | 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">
即可。
- 在 Vue3 中,如果直接在原生元素上使用 is
注意事项:
- 大小写敏感:
- 在HTML DOM模板中,组件名需使用 kebab-case,如
is="my-component"
。 - 在单文件组件SFC或字符串模板中,可使用 PascalCase,如
is="MyComponent"
。
- 在HTML DOM模板中,组件名需使用 kebab-case,如
- 大小写敏感:
<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.$slots
和this.$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
ref:ref变量
,用于获取 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 动态渲染时,需检查引用是否存在。
jsif (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组件时提供类型推导的辅助函数。
语法:
jsimport { defineComponent } from 'vue'; const MyComponent = defineComponent({ // 组件选项 name: 'MyComponent', props: { /* 属性定义 */ }, emits: { /* 自定义事件定义 */ }, setup(props, context) { // 组合式API逻辑 return { /* 暴露给模板的属性和方法 */ }; } });
options:
{setup,data,methods,...}
,组件选项,包括选项式和组合式API。返回:
cpn:
ComponentConstructor
,返回创建的组件实例。示例:
jsimport { 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 的具体结构或构造函数类型。
语法:
tsimport { 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类型。声明对象类型:
tsprops: { config: { type: Object as PropType<{ title: string; size: number }>, default: () => ({ title: '默认标题', size: 10 }) } }
声明数组类型:
tsprops: { items: { type: Array as PropType<{ id: number; value: string }[]>, default: () => [] } }
声明联合类型:
tstype Status = 'loading' | 'success' | 'error'; props: { status: { type: String as PropType<Status>, default: 'loading' } }
声明函数类型:
tsprops: { onSubmit: { type: Function as PropType<(payload: { data: string }) => boolean>, required: true } }
声明类实例:
tsclass 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
定义。jsconst props = withDefaults( defineProps<{ size?: number; }>(), {size: 10} );
运行时类型校验:
PropType<T>
仅提供 TS 类型检查,若需运行时校验,需额外使用validator
。jsprops: { status: { type: String as PropType<Status>, validator: (value: string) => ['loading', 'success', 'error'].includes(value) } }
SSR
createSSRApp()
createSSRApp():(rootComponent,rootProps?)
,用于 服务端渲染 (SSR) 的应用程序创建函数,它在组合式API中的使用与客户端渲染CSR类似,但需要遵循特定的 SSR 配置和生命周期规则。
rootComponent:
options | Component
,根组件,Vue 应用的入口组件。options
:{data,methods,...}
,选项式API中的组件选项。Component
:组合式API中包含setup()
方法的组件对象。
rootProps?:
Record<string, any>
,传递给根组件的props
对象,用于父组件向根组件传递数据。返回:
app:
App
,返回的Vue应用实例,提供多个用于配置和控制应用的实例方法。特性:
服务端与客户端协作:
- 服务端:通过 renderToString() 生成 HTML 字符串。
- 客户端:激活(hydrate)静态 HTML,使其成为交互式应用
生命周期钩子:
onServerPrefetch():在服务端渲染前预取数据。
jsimport { onServerPrefetch } from 'vue' setup() { const data = ref(null) onServerPrefetch(async () => { data.value = await fetchData() }) return { data } }
服务端配置:
服务端入口:
server-entry.js
jsimport { 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
jsimport { 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 配置示例。
jsimport { 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__ }
使用工厂函数避免状态污染:每次请求创建新的应用实例。
jsexport function createApp() { const app = createSSRApp(App) const store = createStore() app.use(store) return { app, store } }
浏览器API访问:避免在服务端访问 window 或 document。
jsif (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 生命周期钩子使用。
app:
App
,创建的 SSR 应用实例。返回:
html:
HTML String
,渲染为 HTML 字符串。示例:
jsimport { 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。
返回:
id:
string
,特性:
前缀支持:可以通过
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>