Vue2
[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
渲染选项:
- template:
HTML字符串 | ID选择器
,定义组件结构的地方,通常用于描述组件的 HTML 结构。使用 Vue 的模板语法,可以让我们通过声明式的方式构建 UI,结合数据、指令和事件来动态地渲染内容。 - render:
(h)
,用于直接控制虚拟DOM生成的核心方法,常用于替代模板语法来实现更灵活的渲染逻辑。 - h():
(tag, props?, children?)
,用于创建虚拟DOM节点VNode。
状态选项:
- data:
() => ({})
,返回组件响应式数据的对象。 - props:
string[] | Object
,声明从父组件接收的属性。可以是一个数组、一个对象,或者一个函数。 - computed:
{ Getter|{Getter,Setter?},...}
,用于声明依赖于其他数据的计算属性。 - methods:
{Record<string,Function>}
,用于定义组件的方法,通常用于事件处理、逻辑操作或触发副作用(如 API 请求)。所有方法会被混入组件实例,可通过this
直接调用。 - watch:
{attr,...}
,监听响应式数据的变化,并在变化时执行自定义逻辑,如异步操作、复杂逻辑、副作用。 - emits:
数组形式|对象形式
,显式声明组件可以触发的自定义事件,用于子组件向父组件通信。 - expose:
string[]
,用于显式声明组件对外暴露的公共属性或方法,通过模板引用访问时,只能访问暴露的内容。
生命周期钩子:
- beforeCreate:
()=>void
,注册一个钩子,在Vue实例初始化之后立即调用。 - created:
()=>void
,注册一个钩子,在Vue实例完成数据观测(data、props初始化)后,但尚未挂载DOM时调用。 - beforeMount:
()
,类似 onBeforeMount(),注册一个钩子,在组件被挂载之前被调用。 - mounted:
()
,类似 onMounted(),注册一个钩子,在组件挂载完成后执行。 - beforeUpdate:
()
,类似 onBeforeUpdate(),注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。 - updated:
()
,类似 onUpdated(),注册一个钩子,在组件因为响应式状态变更而更新其 DOM 树之后调用。 - beforeUnmount:
()
,类似 onBeforeUnmount(),注册一个钩子,在组件实例被卸载之前调用。 - unmounted:
()
,类似 onUnmounted(),注册一个钩子,在组件实例被卸载之后调用。 - activated:
()
,类似 onActivated(),注册一个钩子,若组件实例是<KeepAlive>
缓存树的一部分,当组件被插入到DOM中时调用。 - deactivated:
()
,类似 onDeactivated(),注册一个钩子,若组件实例是<KeepAlive>
缓存树的一部分,当组件从DOM中被移除时调用。
组合选项:
- provide:
()
,类似 provide(),提供一个值,可以被后代组件注入。 - inject:
()
,类似 inject(),注入一个由祖先组件或整个应用通过app.provide()
提供的值。 - mixins:
()
,类似 app.mixin(),用来全局注册一个混入对象。会将该对象的所有组件选项(data,methods,...
)合并到每个组件中。 - extends:
cpn
,用于组件继承的选项,允许一个组件基于另一个组件进行扩展,复用其选项并覆盖或增强特定逻辑。
其他:
- name:
string
,用于显式声明组件的名称。主要用于调试、递归组件、动态组件、缓存控制。 - components:
{name: cpn,...}
,用于在当前组件中局部注册子组件,使得这些子组件可以在当前组件的模板中使用。 - derectives:
()
,类似 app.directive(),用于注册或获取全局自定义指令。
组件实例:
- $data:
()
,类似 data,返回组件响应式数据的对象。返回组件响应式数据的对象。 - $props:
()
,类似 props,声明从父组件接收的属性。可以是一个数组、一个对象,或者一个函数。 - $el:
,Vue实例的内置属性,指向该实例关联的根DOM元素。在组件中对应组件模板的根元素。
- $options:
,Vue实例的内置属性,用于访问组件定义的 原始选项对象。
- $slots:
slot
,Vue实例的内置属性,用于访问组件接收的 插槽内容。 - $refs:
,Vue 实例的内置属性,用于直接访问模板中通过 ref 属性标记的 DOM元素 或 子组件实例。
- $parent:
,Vue实例的内置属性,用于直接访问当前组件的 父组件实例。
- $watch():
()
,类似 watch,监听响应式数据的变化,并在变化时执行自定义逻辑,如异步操作、复杂逻辑、副作用。 - $emit():
()
,类似 emits,显式声明组件可以触发的自定义事件,用于子组件向父组件通信。 - $forceUpdate():
()
,Vue实例的内置方法,用于 强制触发当前组件及其子组件的重新渲染的应急手段。 - $nextTick():
()
,类似 nextTick(),通常用来在下一次 DOM 更新刷新后执行某些操作。
指令
插值语法
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
渲染选项
template
template:HTML字符串 | ID选择器
,定义组件结构的地方,通常用于描述组件的 HTML 结构。使用 Vue 的模板语法,可以让我们通过声明式的方式构建 UI,结合数据、指令和事件来动态地渲染内容。
示例:
HTML字符串
html<div id="app"></div> <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.5.13/vue.global.js"></script> <script> const app = Vue.createApp({ template: '<h2>Vue组件</h2>' }) app.mount('#app') </script>
ID选择器-x-template
html<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.5.13/vue.global.js"></script> <script type="x-template" id="vue"> <h2>Vue组件-x-template</h2> </script> <script> const app = Vue.createApp({ template: '#vue' }) app.mount('#app') </script>
ID选择器-template
html<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.5.13/vue.global.js"></script> <template id="vue"> <h2>Vue组件-template</h2> </template> <script> const app = Vue.createApp({ template: '#vue' }) app.mount('#app') </script>
render
render:(h)
,用于直接控制虚拟DOM生成的核心方法,常用于替代模板语法来实现更灵活的渲染逻辑。
h:
(tag, props?, children?)=>VNode
,h函数,用于创建虚拟DOM节点VNode。返回:
node:
VNode
,返回虚拟DOM节点VNode。示例:
jsexport default { // 其他选项(data, props, methods等) render(h) { // h 是 h函数,用于创建VNode return h('div', { /* 属性 */ }, [ /* 子节点 */ ]); } }
h()
h():(tag, props?, children?)
,用于创建虚拟DOM节点VNode。
tag:
string | Component | AsyncComponent
,表示元素或组件类型。string
:HTML标签名,如'div'
。Component
:Vue 组件定义,如导入的.vue
文件。AsyncComponent
:通过defineAsyncComponent
定义的组件。
props?:
Object | null
,包含属性、事件监听器、DOM 属性等。class
:会自动合并的类名。style
:会自动合并的内联样式。onXXX
:事件监听器。其他
:自定义 props 或原生 DOM 属性。
children?:
string | Array<VNode> | VNode
,子节点内容,支持字符串、嵌套h()
调用、插槽函数。string
:文本节点。Array<VNode>
:多个子节点嵌套VNode。VNode
:单个子节点。
返回:
node:
VNode
,虚拟DOM节点对象,用于渲染真实DOM。示例:
jsimport { h } from 'vue' // 创建一个带点击事件的按钮 const button = h('button', { class: 'btn', onClick: () => console.log('Clicked!') }, '点击我')
js// 动态切换组件类型 const container = defineComponent({ setup() { const isVertical = ref(true); return () => h( isVertical.value ? 'div' : 'section', // 动态标签名 { class: 'container' }, [ h('h1', '标题'), h('p', { onClick: () => isVertical.value = !isVertical.value }, '切换布局') ] ); } });
js// 带具名插槽的组件 const layout = h( MyLayout, { title: '页面标题' }, { default: () => h('p', '主内容区'), footer: () => h('div', '底部信息') } );
特性:
TS:
ts// 完整参数签名 function h( type: string | Component, props?: object | null, children?: Children | Slot | Slots ): VNode // 省略 props function h(type: string | Component, children?: Children | Slot): VNode type Children = string | number | boolean | VNode | null | Children[] type Slot = () => Children type Slots = { [name: string]: Slot }
选项式:
js// Vue 2/3 选项式写法 export default { render() { return this.$createElement('div', { class: 'wrapper' }, [ this.$createElement('span', '选项式 API 渲染') ]); } } // Vue3 统一使用 h() export default { render() { return h('div', { class: 'wrapper' }, [ h('span', '统一渲染函数') ]); } }
SSR:SSR 中需使用
@vue/server-renderer
的renderToString
处理 VNode。Props特殊处理:class/style 支持对象/数组格式,事件监听器需以
on
前缀开头,如onClick
。jsh('button', { class: ['btn', { active: isActive }], onClick: handleClick }, '提交')
Key管理:列表渲染时必须提供唯一key。
jsh('ul', null, items.map(item => h('li', { key: item.id }, item.text)) )
插槽传递:通过函数形式传递作用域插槽。
- 默认插槽
jsh(MyComponent, null, { default: () => [h('p', '内容1'), h('p', '内容2')] })
- 具名插槽
jsh(MyComponent, null, { header: (props) => h('h1', props.title) })
性能优化:避免在频繁更新的组件中创建复杂嵌套结构,可缓存静态VNode。
jsconst staticNode = h('div', '静态内容'); // 提前创建 setup() { return () => h('div', [staticNode, h('p', dynamicContent)]); }
JSX集成:需配置 Babel 插件支持 JSX 语法,自动转换为
h()
调用。jsconst jsxNode = <div class="box"> { items.map(item => <span key={item.id}>{item.text}</span> ) } </div>
状态选项
data
data:() => ({})
,返回组件响应式数据的对象。
返回:
响应式对象:
Object
,返回的对象会被 Vue 转换为响应式代理,基于Proxy
(Vue 3)或Object.defineProperty
(Vue 2),属性变化会触发视图更新。- 直接修改属性值(如
this.key = newValue
)可触发响应式更新。 - 新增未在
data
中声明的属性默认非响应式(需通过Vue.set()
或重新赋值对象解决,Vue 2)。
- 直接修改属性值(如
示例:
jsconst app = Vue.createApp({ data() { return { message: 'Hello Vue!', counter: 0, user: { name: 'Alice', age: 28 } }; }, })
js// 动态数据初始化 export default { data() { // 根据外部条件或函数初始化数据 const initialData = this.fetchInitialData(); return { items: initialData.items, isLoading: false }; }, methods: { fetchInitialData() { // 模拟同步数据获取(实际中可能为异步) return { items: ['A', 'B', 'C'] }; } } };
特性:
必须为函数:若直接使用普通对象,多个组件实例会共享同一数据对象,导致状态污染。
用途:初始化数据,在函数中定义组件初始状态,所有属性会自动变为响应式。
props
props:string[] | Object
,声明从父组件接收的属性。可以是一个数组、一个对象,或者一个函数。
string[]
:数组中的每个元素表示一个需要传递的数据的prop
名称。Object
:{Record<string,{type,required?,default?,validator?}>}
,对象类型可以进行类型检查。类型检查包含以下配置项:type
:指定prop的数据类型:String
、Number
、Boolean
、Array
、Object
、Date
、Function
、Symbol
、任何自定义构造函数、或上述内容组成的数组。required?
:boolean
,默认:false
,指定prop是否必须传递。default?
:string | ()=>[] | ()=>({})
,指定prop的默认值。对于对象、数组类型的prop,default是一个返回数组或对象的函数。validator?
:(prop)=>Boolean|Error
,通过函数进行更复杂的验证。接收一个props对象参数,并返回一个布尔值或错误信息。
语法:
- 数组类型:
jsprops: ['title', 'content']
- 对象类型:
tsprops: { title: { type: String, required: true }, age: { type: Number, default: 18 } }
- 验证函数:
tsprops: { customProp: { validator(value) { // 自定义校验逻辑,返回 true 或 error 信息 if (value < 0) { return "Value must be greater than 0"; } return true; } } }
特性:
响应式:props是响应式的。如果父组件传递的prop的值发生变化,子组件中的prop值会自动更新。
不可修改:子组件不能直接修改props。
命名约定:props的命名在JS中通常遵循 camelCase,但在HTML/模板中props通常会转换为 kebab-case。
v-model:v-model本质上是对 props 的一种特殊用法。默认绑定的是
value
,父组件通过v-model
向子组件传递数据,子组件通过this.$emit('input', newValue)
向父组件同步更新数据。模版使用:在子组件的模板中,可以直接通过
{ { propName } }
来访问传入的 props。
computed
computed:{ Getter|{Getter,Setter?},...}
,用于声明依赖于其他数据的计算属性。
Getter:
()=>ComputedValue
,默认
,函数形式,当计算属性只需读取值时,可直接使用函数。Setter?:
(newValue)=>void
,对象形式,当需要设置计算属性时,需提供 set 方法。返回:
value:
ComputedValue
,返回计算后的属性值。语法:
- 函数形式:
jscomputed: { fullName() { return `${this.firstName} ${this.lastName}`; } }
- 对象形式:
jscomputed: { fullName: { get() { return `${this.firstName} ${this.lastName}`; }, set(newValue) { const [firstName, lastName] = newValue.split(' '); this.firstName = firstName; this.lastName = lastName || ''; } } }
特性:
- 缓存机制:计算属性基于依赖的响应式数据缓存结果。依赖不变时直接返回缓存值。
- 性能优化:适合将耗时计算移至 computed 中,利用缓存优化性能。
- 依赖追踪:自动追踪data、props或其他computed中的响应式依赖。避免在计算属性中操作非响应式数据。
- 无副作用:计算属性应是纯函数,不要在内部执行异步操作或修改外部状态。
注意事项:
避免箭头函数:确保 this 指向组件实例。
js// 错误写法(箭头函数无正确 this) fullName: () => this.firstName + this.lastName
methods
methods:{Record<string,Function>}
,用于定义组件的方法,通常用于事件处理、逻辑操作或触发副作用(如 API 请求)。所有方法会被混入组件实例,可通过 this
直接调用。
返回:
灵活返回:
any
,方法可返回任意值(或不返回),返回值不会被缓存(与computed
不同)。示例:
html<!-- 常用用法:事件处理 --> <template> <button @click="increment">点击增加</button> <p>计数:{{ count }}</p> </template> <script> export default { data() { return { count: 0 }; }, methods: { increment() { this.count++; } } }; </script>
html<!-- 进阶用法:方法参数与事件修饰符 --> <template> <!-- 1. 传递参数 --> + <button @click="logInfo('按钮A', $event)">按钮A</button> <!-- 2. 事件修饰符 --> + <form @submit.prevent="handleSubmit"> <input type="text" v-model="inputText"> <button type="submit">提交</button> </form> </template> <script> export default { data() { return { inputText: '' }; }, methods: { logInfo(buttonName, event) { console.log(`点击了${buttonName}`, event.target); }, handleSubmit() { if (this.inputText.trim()) { + this.submitData(this.inputText); } }, submitData(text) { console.log('提交内容:', text); // 模拟 API 请求 } } }; </script>
特性:
TS:使用
defineComponent()
进行类型推断。tsimport { defineComponent } from 'vue'; export default defineComponent({ methods: { // 明确参数和返回值类型 add(num1: number, num2: number): number { return num1 + num2; } } });
组合式 :
export default:
jsimport { ref } from 'vue'; export default { setup() { const count = ref(0); // 1. 定义方法 const increment = () => count.value++; // 2. 直接暴露为普通函数 return { count, increment }; } };
setup:
html<script setup> const count = ref(0); // 1. 定义方法 const increment = () => count.value++; // 2. 不需要暴露,直接在模版中使用 </scripts>
避免箭头函数:会导致
this
指向错误。js// ❌ 错误(this 指向错误) methods: { increment: () => this.count++ } // ✅ 正确 methods: { increment() { this.count++ } }
模板中的方法调用:
- 无参:直接绑定方法名:
@click="handleClick"
。 - 传递参数:
@click="handleClick(arg1, $event)"
。 - 避免在模板中直接执行复杂逻辑(如
{ { formatDate() } }
),应优先使用computed
。
- 无参:直接绑定方法名:
watch
watch:{attr,...}
,监听响应式数据的变化,并在变化时执行自定义逻辑,如异步操作、复杂逻辑、副作用。
attr:
函数形式|对象形式
,监听的响应式属性。函数形式
:(newValue, oldValue)=>void
,直接监听一个属性attr,参数为 newVal 和 oldVal。对象形式
:{handler,deep,immediate}
,通过对象配置更复杂的监听行为- handler:
(newValue)=>void
,变化时执行的函数。 - deep:
boolean
,默认:false
,是否深度监听对象/数组内部值的变动。 - immediate:
boolean
,默认:false
,是否立即触发一次 handler,初始值时执行。
- handler:
语法:
函数形式:
jswatch: { count(newVal, oldVal) { console.log('计数变化:', newVal); } }
对象形式:
jswatch: { user: { handler(newVal) { console.log('用户信息变化:', newVal); }, deep: true, // 监听 user 对象的所有嵌套属性 immediate: true // 立即触发一次 handler,初始值时执行 } }
监听嵌套属性:使用字符串路径监听对象内部的属性。
jswatch: { 'user.name'(newName) { console.log('新用户名:', newName); } }
异步操作:在 handler 中执行异步任务。
jswatch: { searchQuery(newQuery) { // 异步任务:防抖处理 setTimeout(() => { this.fetchResults(newQuery); }, 300); } }
手动停止监听器:在组件内创建的监听器通常会自动销毁。若需手动停止监听,如动态添加的监听器:
jsconst unwatch = this.$watch('count', (newVal) => { console.log('count changed:', newVal); }); // 停止监听 unwatch();
注意事项:
避免无限循环:如果在 handler 中修改监听的数据,需添加条件判断防止递归触发。
jswatch: { count(newVal) { if (newVal < 10) { this.count = newVal + 1; // 可能导致循环 } } }
明确监听目标:deep: true 或监听大型对象时可能影响性能,尽量明确监听具体属性。
对比computed:
- watch:监听变化,执行副作用,如请求、DOM 操作。
- computed:衍生数据,依赖其他值计算得到,适合模板渲染。
emits
emits:数组形式|对象形式
,显式声明组件可以触发的自定义事件,用于子组件向父组件通信。
数组形式:
string[]
,仅列出事件名称,无参数验证。对象形式:
{eventName,...}
,- eventName:
(payload)=>boolean | null
,事件名。
- eventName:
语法:
声明事件:
- 数组形式:仅列出事件名称,无参数验证。
js// 1. 数组形式(仅声明事件名称,无验证) emits: ['submit', 'cancel']
- 对象形式:为事件添加验证函数,验证事件参数的合法性。
js// 2. 对象形式(带参数验证) emits: { // 事件名: 验证函数 submit: (payload) => { // 返回 true 表示验证通过,false 会触发警告(开发环境) return payload.email && payload.password; }, // 无参数的简单事件 cancel: null // 直接设置为 null 表示无需验证参数 }
触发事件:在组件方法中通过
this.$emit(eventName, ...args)
触发事件。jsmethods: { sendData() { this.$emit('submit', { data: 'test' }); } }
父组件监听事件:父组件通过 v-on/@ 监听子组件触发的事件。
html<ChildComponent @submit="handleSubmit" />
注意事项:
验证函数的限制:
- 验证函数中无法访问组件实例,this 为 undefined。
- 仅用于开发阶段的参数校验,生产环境不执行。
对比props:
- emits:子组件向父组件传递事件。
- props:父组件向子组件传递数据。
统一命名规范:统一使用 kebab-case 命名事件。
示例:子组件向父组件传递事件
- 子组件:
jsexport default { // 1. 声明自定义事件 emits: { // 验证提交事件参数 submit: (payload) => payload.name && payload.age > 0 }, methods: { triggerSubmit() { // 2. 触发事件 this.$emit('submit', { name: 'John', age: 30 }); } } }
- 父组件:
html<template> <ChildComponent @submit="handleSubmit" /> <!-- 3. 父组件监听子组件触发的事件 --> </template> <script> export default { methods: { handleSubmit(payload) { console.log('收到数据:', payload); } } } </script>
expose@vue3
expose:string[]
,用于显式声明组件对外暴露的公共属性或方法,通过模板引用访问时,只能访问暴露的内容。
语法:
暴露属性和方法:在数组中列出需要暴露的属性和方法名。
js// 声明要暴露的属性或方法名(数组形式) expose: ['getData', 'resetForm'],
父组件访问暴露的内容:父组件通过模板引用ref访问子组件暴露的公共接口。
html<!-- 父组件模板 --> <template> <ChildComponent ref="childRef" /> <button @click="callChildMethod">调用子组件方法</button> </template> <script> export default { methods: { callChildMethod() { // 仅能访问 expose 中声明的属性和方法 this.$refs.childRef.publicMethod(); console.log(this.$refs.childRef.publicProperty); // 正常访问 // 以下代码会报错(未暴露的属性和方法不可访问) // this.$refs.childRef.privateMethod(); // console.log(this.$refs.childRef.privateData); } } } </script>
组合式API:
jsimport { defineExpose } from 'vue'; export default { setup() { const internalState = ref('隐藏数据'); const publicMethod = () => { /* ... */ }; // 组合式 API 的暴露方式 defineExpose({ publicMethod }); return { internalState, publicMethod }; } };
注意事项:
- Vue版本要求:expose 是 Vue 3+ 新增的功能,Vue 2 不支持。
- 作用范围:expose 不会影响通过
$emit
或$props
的通信,仅控制模板引用ref的访问。 - 默认行为:
未配置expose:父组件通过 ref 可以访问子组件实例的所有属性和方法。
配置expose:仅暴露明确声明的属性和方法,其他内容被屏蔽。
示例:父组件通过ref访问子组件暴露的方法/属性
- 子组件:
jsexport default { expose: ['getMessage'], // 1. 只暴露 getMessage 方法 data() { return { message: 'Hello from child!', secret: 'Confidential' }; }, methods: { getMessage() { return this.message; }, internalLog() { console.log('内部方法'); } } };
- 父组件:
html<template> <ChildComponent ref="child" /> <!-- 2. 绑定ref --> </template> <script> export default { mounted() { // 可以访问暴露的 getMessage console.log(this.$refs.child.getMessage()); // 输出: "Hello from child!" // 以下访问会报错(未暴露的属性和方法) // console.log(this.$refs.child.secret); // this.$refs.child.internalLog(); } } </script>
生命周期钩子
beforeCreate
beforeCreate:()=>void
,注册一个钩子,在Vue实例初始化之后立即调用。
特性:
- 此时实例的 data、methods、computed 等选项尚未初始化。
- 无法通过 this 访问组件的数据和方法,会返回undefined。
常见用途:
- 初始化非响应式变量:直接赋值给 this,但不会触发视图更新。
- 执行全局配置:如设置请求拦截器、第三方库初始化。
- 在插件开发中注入早期逻辑:需早于组件状态初始化。
注意事项:
- 避免操作响应式数据:修改 data 中的属性可能无效或被后续初始化覆盖。
- 慎用异步操作:若需异步获取数据,建议在 created 或 mounted 中进行。
示例:
jsexport default { beforeCreate() { console.log('beforeCreate 钩子被调用'); console.log(this.message); // 1. 输出:undefined(data 未初始化) console.log(this.sayHello); // 2. 输出:undefined(methods 未初始化) this.nonReactiveData = '初始值'; // 3. 可执行非响应式初始化(例如非 data 属性) }, data() { return { message: 'Hello Vue!' }; }, methods: { sayHello() { console.log(this.message); } } }
created
created:()=>void
,注册一个钩子,在Vue实例完成数据观测(data、props初始化)后,但尚未挂载DOM时调用。
特性:
初始化完成:data、methods、computed 等选项已初始化完成。
this访问:可以通过 this 访问组件的数据和方法。
无法操作DOM:此时模板尚未渲染成 HTML,this.$el 为 undefined。
SSR:在服务端渲染中,created 是唯一会被调用的生命周期钩子,因为无 DOM 环境。
常见用途:
数据初始化:调用 API 获取初始数据并赋值给 data。
事件监听:添加全局事件,如
window.addEventListener
。依赖数据的逻辑:根据
props
或data
执行计算或配置。第三方库初始化:初始化不依赖 DOM 的库,如图表库、状态管理。
注意事项:
避免操作DOM:需操作 DOM 时,应使用 mounted 钩子。
异步操作处理:若需等待异步数据,可结合 v-if 或加载状态控制渲染。
响应式数据安全:可直接修改 data 中的数据,Vue 会自动触发更新。
示例:
jsexport default { data() { return { message: 'Hello Vue!', userData: null }; }, created() { console.log('created 钩子被调用'); console.log(this.message); // 1. 输出:'Hello Vue!' console.log(this.$el); // 2. 输出:undefined(DOM 未挂载) this.fetchUserData(); // 3. 发起异步请求(如初始化数据) }, methods: { async fetchUserData() { const response = await fetch('https://api.example.com/user'); this.userData = await response.json(); } } }
beforeMount
beforeMount:()=>void
,类似 onBeforeMount(),注册一个钩子,在组件被挂载之前被调用。
mounted@
mounted:()=>void
,类似 onMounted(),注册一个钩子,在组件挂载完成后执行。
beforeUpdate
beforeUpdate:()=>void
,类似 onBeforeUpdate(),注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。
updated
updated:()=>void
,类似 onUpdated(),注册一个钩子,在组件因为响应式状态变更而更新其 DOM 树之后调用。
beforeUnmount
beforeUnmount:()=>void
,类似 onBeforeUnmount(),注册一个钩子,在组件实例被卸载之前调用。
unmounted
unmounted:()=>void
,类似 onUnmounted(),注册一个钩子,在组件实例被卸载之后调用。
activated
activated:()=>void
,类似 onActivated(),注册一个钩子,若组件实例是<KeepAlive>
缓存树的一部分,当组件被插入到DOM中时调用。
deactivated
deactivated:()=>void
,类似 onDeactivated(),注册一个钩子,若组件实例是<KeepAlive>
缓存树的一部分,当组件从DOM中被移除时调用。
组合选项
provide
provide:()
,类似 provide(),提供一个值,可以被后代组件注入。
inject
inject:()
,类似 inject(),注入一个由祖先组件或整个应用通过app.provide()
提供的值。
mixins
mixins:()
,类似 app.mixin(),用来全局注册一个混入对象。会将该对象的所有组件选项(data,methods,...
)合并到每个组件中。
extends
extends:cpn
,用于组件继承的选项,允许一个组件基于另一个组件进行扩展,复用其选项并覆盖或增强特定逻辑。
cpn:
Component
,接受一个组件选项对象或组件构造函数,作为当前组件的基类。示例:
- 基组件:BaseComponent.js
jsexport default { data() { return { baseData: '基类数据' }; }, methods: { baseMethod() { console.log('基类方法'); } }, mounted() { console.log('基类 mounted'); } };
- 派生组件:
js// 派生组件 DerivedComponent.js import BaseComponent from './BaseComponent'; export default { extends: BaseComponent, // 1. 继承基组件 data() { return { derivedData: '派生类数据' }; }, methods: { derivedMethod() { console.log('派生类方法'); }, // 2. 覆盖基类方法 baseMethod() { console.log('派生类重写的基类方法'); // 调用基类方法(类似 super.baseMethod()) this.$options.extends.methods.baseMethod.call(this); } }, mounted() { console.log('派生类 mounted'); // 3. 调用基类生命周期钩子 this.$options.extends.mounted.call(this); } };
特性:
选项合并规则:Vue 会按特定策略合并基组件和派生组件的选项。
data:属性浅合并,属性相同时,派生组件覆盖基组件。
js// 基组件 data data() { return { a: 1, b: 2 }; } // 派生组件 data data() { return { b: 3, c: 4 }; } // 合并后 data { a: 1, b: 3, c: 4 }
生命周期钩子:将基类和派生类的钩子函数合并为数组,依次执行,基类钩子先执行。
jsmounted() { // 基类钩子先执行 // 派生类钩子后执行 }
其他选项:同名属性覆盖,基类选项被派生类取代。
js// 基组件 methods methods: { a() {} } // 派生组件 methods methods: { a() {}, b() {} } // 合并后 methods { a() { /* 派生类方法 */ }, b() {} }
对比mixins:extends仅继承单个组件,而mixins可以混入多个组件。
访问基类选项:通过
this.$options.extends
访问基类选项,但需注意上下文绑定。js// 调用基类方法 this.$options.extends.methods.baseMethod.call(this);
其他
name
name:string
,用于显式声明组件的名称。主要用于调试、递归组件、动态组件、缓存控制。
语法:
声明组件名称:推荐使用 PascalCase,与文件名一致。
jsexport default { name: 'TodoItem', // 推荐使用 PascalCase(与文件名一致) // 其他选项... }
递归组件:组件通过 name 在自身模板中调用自己。
html<!-- TodoList.vue --> <template> <div> <h3>{{ title }}</h3> <!-- 2. 递归调用自身 --> <TodoList v-if="hasChildren" :items="children" /> </div> </template> <script> export default { name: 'TodoList', // 1. 必须声明 name 才能递归 props: ['items'], computed: { hasChildren() { return this.items && this.items.length > 0; } } } </script>
动态组件中使用:通过 name 切换组件。
html<template> <component :is="currentComponent"></component> <!-- 2. 动态组件中使用 --> </template> <script> import ComponentA from './ComponentA.vue'; import ComponentB from './ComponentB.vue'; export default { data() { return { currentComponent: 'ComponentA' // 1. 根据 name 匹配组件 }; }, components: { ComponentA, ComponentB } } </script>
KeepAlive缓存控制:通过 name 指定缓存目标。
html<keep-alive :include="['UserProfile', 'Settings']"> <!-- 指定包括的组件名 --> <component :is="currentTab"></component> </keep-alive>
调试标识:在Vue DevTools组件树中显示 name 值,便于调试。
自动推断规则:若未显式声明 name,Vue 会尝试从以下位置获取名称:
- 组件的文件名。
- 全局或局部注册时的名称。
注意事项:
- 唯一性建议:确保组件名称唯一,避免命名冲突。
示例:在父组件中局部注册定义的组件
- 组件定义:
jsexport default { name: 'TodoItem', // 1. 显式声明名称 props: ['text'], template: ` <li>{{ text }}</li> ` }
- 父组件使用:
html<template> <ul> <!-- 3. 模版中使用局部注册过的组件 --> <todo-item v-for="item in list" :text="item.text" :key="item.id" /> </ul> </template> <script> import TodoItem from './TodoItem.vue'; export default { // 2. 局部注册(模板中使用 kebab-case) components: { TodoItem }, data() { return { list: [{ id: 1, text: 'Learn Vue' }] }; } } </script>
components
components:{name: cpn,...}
,用于在当前组件中局部注册子组件,使得这些子组件可以在当前组件的模板中使用。
name:
string
,组件名称,模板中使用时的标签名。cpn:
Component | {data,...}
,组件对象,导入的组件或直接定义。语法:
注册/使用组件:
- 注册组件:在
components
对象中注册组件。
jsimport ChildComponent from './ChildComponent.vue'; import ButtonCounter from './ButtonCounter.vue'; export default { components: { 'CustomButton': ButtonCounter, // 1. 完整写法 ChildComponent // 2. ES6简写(等价于ComponentB: ComponentB) } };
- 模板中使用:在模板中直接使用注册的组件标签。
html<template> <div> <!-- 使用 kebab-case 或 PascalCase --> <child-component /> <custom-button /> </div> </template>
- 注册组件:在
动态组件:结合
<component :is="...">
动态切换组件。html<template> <component :is="currentComponent"></component> </template> <script> import CompA from './CompA.vue'; import CompB from './CompB.vue'; export default { data() { return { currentComponent: 'CompA' }; }, components: { CompA, CompB } }; </script>
递归组件:在组件内部注册自身实现递归,需配合 name 属性。
js// TreeItem.vue export default { name: 'TreeItem', // 1. 必须声明 name components: { TreeItem: () => import('./TreeItem.vue') // 2. 异步递归 }, props: ['item'] };
异步组件:通过动态导入实现按需加载,需配合打包工具如 Webpack。
jsexport default { components: { AsyncComponent: () => import('./AsyncComponent.vue') } };
注意事项:
注册名规则:推荐PascalCase,注册时使用
ComponentName
,模板中可用<component-name>
或<ComponentName>
。对比全局组件:
- 优先级:局部组件 > 全局组件。
- 作用域:
- 局部注册的组件仅在当前组件及其子组件中可用。
- 全局组件可在任何地方使用。
- 优势:局部注册可以减少全局命名污染,按需加载组件,优化性能。
命名一致性:保持文件名、组件注册名和模板标签名一致,如
UserList.vue
→ 注册为UserList
→ 模板用<user-list>
。按需加载:对非首屏必需的组件使用异步注册
() => import(...)
。
示例:局部注册并使用组件
- 子组件
html<template> <button @click="count++">点击次数: {{ count }}</button> </template> <script> export default { data() { return { count: 0 }; } }; </script>
- 父组件
html1<template> <div> <h1>组件示例</h1> <button-counter /> <!-- 3. 使用子组件 --> </div> </template> <script> // 1. 导入子组件 import ButtonCounter from './ButtonCounter.vue'; export default { // 2. 局部注册组件 components: { ButtonCounter } }; </script>
derectives
derectives:()
,类似 app.directive(),用于注册或获取全局自定义指令。
组件实例
$data
$data:()
,类似 data,返回组件响应式数据的对象。返回组件响应式数据的对象。
$props
$props:()
,类似 props,声明从父组件接收的属性。可以是一个数组、一个对象,或者一个函数。
$el
$el:,Vue实例的内置属性,指向该实例关联的根DOM元素。在组件中对应组件模板的根元素。
特性:
执行时机:
- mounted 阶段$el变为可用的DOM元素。
- beforeCreate 和 created 阶段$el 为 undefined,因为DOM 尚未挂载。
SSR:在 SSR 中,$el 在服务端为 undefined,仅在客户端 mounted 后可用。
对比ref:
特性 this.$el
this.$refs.xxx
指向目标 组件根元素 模板中通过 ref="xxx"
标记的元素访问时机 仅在 mounted
后可用在 mounted
后可用多根元素组件 指向第一个根元素,Vue3 支持 Fragments 可精确访问特定元素
主要用途:
直接操作DOM:在需要手动修改元素样式、属性或调用 DOM API 时使用。
jsmounted() { this.$el.style.backgroundColor = "#f0f0f0"; }
集成第三方库:初始化依赖 DOM 的库,如图表库、地图库。
jsmounted() { this.chart = new Chart(this.$el, { /* 配置 */ }); }
获取组件根元素:用于计算尺寸、位置或与其他 DOM 元素交互。
jsmounted() { const rect = this.$el.getBoundingClientRect(); console.log("组件宽度:", rect.width); }
注意事项:
- 动态模板的限制:如果组件没有模板,如函数式组件或 render 函数生成内容,开发环境下$el可能指向注释节点。
- 不要滥用:优先使用 Vue 的响应式数据或 ref 属性操作 DOM,直接操作 $el 可能导致状态与数据不同步。
示例:$el的访问时机
jsexport default { template: ` <div id="app"> <h1>{{ message }}</h1> </div> `, data() { return { message: "Hello Vue!" }; }, created() { console.log("created:", this.$el); // 1. 输出:undefined }, mounted() { console.log("mounted:", this.$el); // 2. 输出:<div id="app">...</div> console.log(this.$el.querySelector("h1")); // 3. 输出:<h1>Hello Vue!</h1> } }
$options
$options:,Vue实例的内置属性,用于访问组件定义的 原始选项对象。
返回:
options:
Object
,返回组件定义的原始选项对象。语法:
包含:它包含组件初始化时传入的所有配置选项 data、methods、created 等,以及通过 mixin 或 extends 合并的选项。
访问方式:
jsexport default { data() { return { message: "Hello" }; }, created() { console.log(this.$options); // 输出完整的组件选项对象 console.log(this.$options.data()); // 输出 { message: "Hello" } } }
特性:
静态数据存储:在 $options 中定义的属性不会成为响应式数据,适合存储常量或配置。
静态配置访问:通过 $options 可以获取组件定义的原始配置,未经过 Vue 内部处理。
jsexport default { apiBaseUrl: "https://api.example.com", // 静态配置 methods: { fetchData() { const url = this.$options.apiBaseUrl + "/users"; // 发起请求... } } }
生命周期钩子:生命周期函数在 $options 中以数组形式存储,支持通过遍历调用。
jscreated() { this.$options.created.forEach(hook => hook.call(this)); }
常见用途:
调试组件选项:
jsmounted() { // 打印所有选项(包括 mixin 合并后的结果) console.log("组件选项:", this.$options); }
动态调用生命周期钩子:
js// 在插件中扩展组件逻辑 const plugin = { install(Vue) { Vue.mixin({ created() { if (this.$options.customCreated) { this.$options.customCreated.call(this); } } }); } };
注意事项:
非响应式属性:$options 中的属性不会触发视图更新,修改它们通常无意义,Vue 内部已处理过选项。
避免滥用:多数情况下应通过 props、data 或 computed 管理数据,而非依赖 $options。
自定义选项规范:自定义选项如apiBaseUrl建议通过
Vue.config.optionMergeStrategies
定义合并策略。js// 定义合并策略:将 customOption 连接为数组 Vue.config.optionMergeStrategies.customOption = (parentVal, childVal) => { return parentVal ? parentVal.concat(childVal) : [childVal]; }; // 使用 const mixin = { customOption: "mixin-value" }; const Child = { mixins: [mixin], customOption: "child-value", created() { console.log(this.$options.customOption); // 输出 ["mixin-value", "child-value"] } };
示例:
- 结合自定义选项实现功能开关
jsexport default { featureA: true, // 启用功能 A featureB: false, // 禁用功能 B created() { if (this.$options.featureA) { this.initFeatureA(); } }, methods: { initFeatureA() { /* ... */ } } }
- 访问原始 data 函数
jsexport default { data() { return { count: 0 }; }, created() { const rawData = this.$options.data(); // { count: 0 } console.log("原始 data 返回值:", rawData); } }
$slots
$slots:slot
,Vue实例的内置属性,用于访问组件接收的 插槽内容。
返回:
slot:
{name: vnode}
,返回一个对象。- name:
string
,键为插槽名称。 - vnode:
VNode
,值为对应的 VNode 数组,虚拟节点。
- name:
语法:
模板中定义插槽:
html<!-- 父组件使用子组件 --> <ChildComponent> <template #default>默认内容</template> <template #header>标题</template> </ChildComponent>
访问插槽内容:
jsexport default { mounted() { // 访问默认插槽 const defaultSlot = this.$slots.default; // 访问具名插槽 "header" const headerSlot = this.$slots.header; } }
特性:
- 插槽类型:
- 默认插槽:
this.$slots.default
。 - 具名插槽:
this.$slots.<name>
,如this.$slots.header
。 - 作用域插槽:
- Vue2:通过
$scopedSlots
访问。 - Vue3:已合并到
$slots
。
- Vue2:通过
- 默认插槽:
- VNode结构:插槽内容是虚拟节点数组,可用于自定义渲染逻辑,如手动控制布局或条件渲染。
- 插槽类型:
常见用途:
条件渲染插槽内容:
jsexport default { render(h) { return h('div', [ this.$slots.header || '默认标题', // 存在 header 插槽则渲染,否则显示默认 this.$slots.default ]); } }
在渲染函数中手动分发插槽:
jsexport default { render(h) { return h('div', [ h('div', { class: 'header' }, this.$slots.header), h('div', { class: 'content' }, this.$slots.default) ]); } }
动态包装插槽内容:
jsexport default { render(h) { // 为每个插槽内容包裹一个 div const wrappedSlots = Object.keys(this.$slots).map(name => { return h('div', { class: 'wrapper' }, this.$slots[name]); }); return h('div', wrappedSlots); } }
注意事项:
- 仅用于渲染函数:
- 在模板中使用
<slot>
标签即可自动分发内容,无需直接操作 $slots。 - $slots 主要用于 自定义渲染函数 或复杂逻辑处理。
- 在模板中使用
- 避免直接修改:插槽内容是只读的,直接修改可能导致渲染错误。
- 异步组件限制:若父组件异步加载,子组件的 $slots 可能在初始渲染时为空。
- 仅用于渲染函数:
示例:
- 检查插槽是否存在
jsexport default { render(h) { return h('div', [ this.$slots.header ? h('h1', this.$slots.header) : h('h1', '默认标题'), this.$slots.default ]); } }
- 传递插槽内容到子组件@
js// 高阶组件(WrapperComponent.vue) export default { render(h) { return h('ChildComponent', { scopedSlots: { // 将父级插槽传递给子组件 default: props => this.$slots.default(props) } }); } }
$refs
$refs:,Vue 实例的内置属性,用于直接访问模板中通过 ref 属性标记的 DOM元素 或 子组件实例。
返回:
refs:
Object
,返回一个包含所有注册 ref 的对象。语法:
模板中定义ref:
html<template> <!-- 标记 DOM 元素 --> <input ref="inputRef" type="text"> <!-- 标记子组件 --> <ChildComponent ref="childComp" /> </template>
访问 $refs:
jsexport default { methods: { focusInput() { this.$refs.inputRef.focus(); // 操作 DOM 元素 this.$refs.childComp.someMethod(); // 调用子组件方法 } } }
特性:
动态绑定:ref 属性可以结合 v-bind动态赋值。
html<div :ref="dynamicRefName"></div>
响应式限制:$refs 不是响应式的,避免在模板或计算属性中依赖它。
生命周期可用性:$refs 在组件 mounted 之后才被填充,在 created 或 beforeMount 中访问为 undefined。
对比Vue3组合式API:
特性 选项式API ( this.$refs
)组合式API ( ref()
)定义方式 模板中静态或动态绑定 ref
使用 ref()
函数声明变量访问时机 仅在 mounted
后可用在 onMounted
生命周期后可用模板绑定 <div ref="divRef"></div>
<div ref="divRef"></div>
访问方式 this.$refs.divRef
divRef.value
常见用途:
操作DOM元素:
html<template> <input ref="emailInput" type="email"> <button @click="focusEmailInput">聚焦输入框</button> </template> <script> export default { methods: { focusEmailInput() { this.$refs.emailInput.focus(); } } } </script>
调用子组件方法或访问数据:
html<template> <Counter ref="counterRef" /> </template> <script> export default { mounted() { this.$refs.counterRef.increment(); // 调用子组件方法 console.log(this.$refs.counterRef.count); // 访问子组件数据 } } </script>
注意事项:
- 唯一性:同一模板中重复的 ref 名称会覆盖,后者生效。
- 动态组件与条件渲染:若元素/组件被 v-if 隐藏或动态切换,对应的 $refs 可能变为 null。
- 避免滥用:优先通过 Props/Events 实现父子通信,直接操作子组件可能破坏封装性。
- 异步更新:在 Vue 更新 DOM 后(如 nextTick)再访问 $refs,确保引用最新。
- 命名规范:使用清晰的 ref 名称,如 formRef、chartInstanceRef。
- 防御性检查:访问前检查是否存在,如
if (this.$refs.xxx)
。
示例:
- 访问动态生成的组件
html<template> <component :is="currentComponent" ref="dynamicComp"></component> </template> <script> export default { data() { return { currentComponent: 'LoginForm' }; }, mounted() { if (this.$refs.dynamicComp) { this.$refs.dynamicComp.validate(); } } } </script>
- 结合 nextTick 确保 DOM 更新
jsexport default { methods: { updateAndFocus() { this.someData = '新值'; this.$nextTick(() => { this.$refs.inputRef.focus(); }); } } }
$parent
$parent:,Vue实例的内置属性,用于直接访问当前组件的 父组件实例。
返回:
parent:
Component
,返回父组件实例。语法:
访问方式:通过
this.$parent
访问,可以操作父组件的数据、方法或生命周期钩子。jsexport default { methods: { callParentMethod() { this.$parent.someParentMethod(); // 调用父组件方法 console.log(this.$parent.parentData); // 访问父组件数据 } } }
特性:
层级关系:
- $parent 指向当前组件的直接父组件实例。
- 若组件被嵌套多层,需通过链式访问
this.$parent.$parent
访问更上层组件,不推荐,破坏封装性。
生命周期可用性:
- 在 beforeCreate 阶段,父组件实例尚未完全初始化,this.$parent 可能为 undefined。
- 从 created 阶段开始,父组件实例已可用。
动态性:若父组件被v-if动态替换,$parent 会自动更新指向新的父组件。
组合式API:在 setup() 函数中无法直接访问
this.$parent
,需通过 getCurrentInstance() 获取。jsimport { getCurrentInstance } from 'vue'; export default { setup() { const instance = getCurrentInstance(); const parent = instance?.parent?.proxy; // 访问父组件实例 return { parent }; } }
常见用途:
直接调用父组件方法:
html<!-- 子组件 Child.vue --> <template> <button @click="notifyParent">通知父组件</button> </template> <script> export default { methods: { notifyParent() { this.$parent.handleChildEvent(); // 触发父组件方法 } } } </script> <!-- 父组件 Parent.vue --> <script> export default { methods: { handleChildEvent() { console.log("子组件触发了父组件方法"); } } } </script>
访问父组件数据:
js// 子组件中 export default { mounted() { console.log("父组件的数据:", this.$parent.sharedData); } }
跨层级通信(慎用):
js// 子组件访问祖父组件(不推荐) export default { methods: { callGrandparent() { this.$parent.$parent.grandparentMethod(); } } }
注意事项:
- 破坏组件封装性:直接操作父组件状态可能导致代码难以维护,优先通过 Props/Events 或 Vuex/Pinia 管理通信。
- 耦合性风险:父组件结构变化时,依赖 $parent 的子组件可能失效。
- 无法访问根实例:访问根实例时,应使用
this.$root
属性。
示例:
- 通过 $parent 实现简单表单验证
js1// 子组件(表单输入项) export default { methods: { validate() { if (!this.value) { this.$parent.addError("字段不能为空"); // 调用父组件方法记录错误 } } } } // 父组件(表单容器) export default { methods: { addError(message) { this.errors.push(message); } } }
$watch()
$watch():()
,类似 watch,监听响应式数据的变化,并在变化时执行自定义逻辑,如异步操作、复杂逻辑、副作用。
$emit()
$emit():()
,类似 emits,显式声明组件可以触发的自定义事件,用于子组件向父组件通信。
$forceUpdate()
$forceUpdate():()
,Vue实例的内置方法,用于 强制触发当前组件及其子组件的重新渲染的应急手段。
特性:
- 手动触发更新:即使数据未发生响应式变化,也会强制组件重新渲染。
- 生命周期钩子:调用 $forceUpdate() 会触发 beforeUpdate 和 updated 生命周期钩子。
使用场景:在以下场景中,当 Vue 的响应式系统无法自动检测到数据变化时使用
直接修改对象/数组的某个属性或索引值:未通过
Vue.set
或this.$set
。jsexport default { data() { return { list: ['A', 'B', 'C'] }; }, methods: { updateItemDirectly() { this.list[1] = 'X'; // ❌ 非响应式修改 this.$forceUpdate(); // 强制更新视图 } } }
依赖非响应式数据:如通过
Object.freeze()
冻结的对象或普通对象。jsexport default { data() { return { frozenData: Object.freeze({ key: '初始值' }) }; }, methods: { updateFrozenData() { this.frozenData.key = '新值'; // ❌ 修改无效 this.$forceUpdate(); // 强制渲染 } } }
使用第三方库的不可变数据:如 Immutable.js。
注意事项:
性能影响:频繁调用 $forceUpdate() 会导致不必要的渲染,应优先通过 响应式数据 驱动视图更新。
替代方案:
使用
Vue.set
/this.$set
修改对象或数组。替换整个对象/数组,触发响应式更新(推荐)。
jsthis.list = [...this.list]; // 通过新数组触发更新
依赖响应式数据,将外部数据包装为响应式
jsdata() { return { externalData: Vue.observable({ value: 0 }) }; }
不适用于Props:无法通过 $forceUpdate() 强制父组件更新传递的 Props,需通过父组件自身状态变更。
示例:强制更新嵌套组件
jsexport default { methods: { refreshChild() { this.$children.forEach(child => { child.$forceUpdate(); // 强制更新所有子组件 }); } } }
$nextTick()
$nextTick():()
,类似 nextTick(),通常用来在下一次 DOM 更新刷新后执行某些操作。