## Vue3
vue2 和vue3的区别

Vue2的API设计是Options(配置)风格的。
Vue3的API设计是Composition(组合)风格的。
Options API 的弊端
Options类型的 API,数据、方法、计算属性等,是分散在:data、methods、computed中的,若想新增或者修改一个需求,就需要分别修改:data、methods、computed,不便于维护和复用。
选项式API,可以用包含多个选项的对象来描述组件的逻辑,如:data,methods,mounted等。
组合式API:分散式维护变成集中式维护
1. setup:是一个标识,告诉Vue需要进行一些处理,让我们可以更简洁的使用组合式API
2. ref():接收一个内部值,返回一个响应式的ref对象,此对象只有一个指向内部值的属性 value
3. onMounted():在组合式API中的钩子方法,注册一个回调函数,在组件挂载完成后执行
Composition API 的优势
可以用函数的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。


起步案例 — 创建 Vue 应用
Vue3 使用 createApp 创建应用实例,通过 setup() 函数组织逻辑。
<!-- 挂载容器 -->
<div id="app">
<!-- 双花括号文本插值 -->
<div>{{ msg }}</div>
<div>{{ name }}</div>
</div>
<script src="js/vue.global.js"></script>
<script>
// 1. 从 Vue 全局对象中解构出 createApp
const { createApp } = Vue;
// 2. 创建 Vue 应用并挂载
createApp({
// setup() 是组合式 API 的入口函数
setup() {
// 在这里定义变量和函数
return {
// 返回的对象中的属性,可以在模板中直接使用
msg: `hello world`,
name: `张三`
};
}
}).mount("#app"); // 3. 挂载到 #app 容器
</script>
Vue3 应用创建的三个步骤:
createApp(options)— 创建应用实例(传入根组件选项)setup()— 组合式 API 入口,定义数据和方法.mount(selector)— 挂载到 DOM 容器
理解
setup()的 return:setup()中 return 的对象属性会自动暴露给模板。这里用到了 ES6 的对象简写语法(return { msg }=return { msg: msg })。
要点:
- 一个页面可以有多个 Vue 应用实例(挂载到不同的容器)
setup()在组件创建之前执行,此时this不可用- 模板中的 JavaScript 表达式只能访问
setup()返回的数据
setup 概述
setup是Vue3中一个新的配置项,值是一个函数,它是 Composition API “表演的舞台”,组件中所用到的:数据、方法、计算属性、监视……等等,均配置在setup中。setup() 钩子是在组件中使用组合式 API 的入口。
特点如下:
1. setup函数返回的对象中的内容,可直接在模板中使用。
2. setup中访问this是undefined。
3. setup函数会在beforeCreate之前调用,它是“领先”所有钩子执行的。
我们可以使用响应式 API 来声明响应式的状态,在 setup() 函数中返回的对象会暴露给模板和组件实例。
其他的选项也可以通过组件实例来获取 setup() 暴露的属性
setup函数有一个语法糖,这个语法糖,可以让我们把setup独立出去
<script setup name="Person">
模板语法
Vue 模板中支持多种语法来渲染数据。
文本插值与 JavaScript 表达式
<!-- 基本文本插值 -->
<div>{{ score }}</div>
<!-- {{}} 中可以使用 JavaScript 表达式(单条语句) -->
<div>{{ score >= 60 ? '及格' : '不及格' }}</div>
<div>{{ name.toUpperCase() }}</div>
<div>{{ price * quantity }}</div>
<!-- ❌ 以下写法不允许(不是表达式,是语句) -->
<!-- <div>{{ if (score >= 60) { return '及格'; } }}</div> -->
<!-- <div>{{ let x = 10; }}</div> -->
原始 HTML — v-html
<!-- v-html 将字符串作为 HTML 渲染 -->
<div v-html="link"></div>
const link = "<a href='https://www.baidu.com/'>baidu</a>";
⚠️ 安全警告:
v-html存在 XSS(跨站脚本攻击)风险!永远不要对用户输入的内容使用v-html,只用于你自己信任的内容。
原始 text — v-text
<!-- v-text 将字符串纯文本文字 渲染 -->
<div v-text ="link"></div>
const link = "<a href='https://www.baidu.com/'>baidu</a>";
属性绑定 — v-bind(单向绑定)
<!-- 完整写法 -->
<div v-bind:id="id">hello Vue</div>
<!-- 简写形式(只用冒号 : ) — 最常用 -->
<div :id="id">hello Vue</div>
<!-- 同名简写(Vue 3.4+)— 属性名和变量名相同时 -->
<!-- <div :id></div> 等价于 <div :id="id"></div> -->
<!-- 绑定多个属性 — 用对象 -->
<div :id="attrobj.id" :class="attrobj.class" :title="attrobj.title">
hello Vue
</div>
<!-- 动态属性名 — 方括号内写表达式 -->
<a v-bind:[link1.name]="link1.value">link</a>
<!-- 当 link1.name = 'href' 时,等价于 <a href="https://www.baidu.com/">link</a> -->
const id = 'div2';
const attrobj = {
id: 'div3',
class: 'c1',
title: '这是一个div'
};
const link1 = {
name: 'href',
value: 'https://www.baidu.com/'
};
事件绑定 — v-on(单击事件)
<!-- 完整写法 -->
<button v-on:click="test">click</button>
<!-- 简写(@ 符号) — 最常用 -->
<button @click="test">click</button>
<!-- 动态事件名 -->
<button @[eventName]="handler">click</button>
function test() {
console.log("---------");
}
要点总结:
| 指令 | 简写 | 用途 |
|---|---|---|
v-bind:属性="值" |
:属性="值" |
动态绑定 HTML 属性 |
v-on:事件="方法" |
@事件="方法" |
绑定事件处理器 |
v-html="值" |
无 | 渲染原始 HTML(注意安全) |
{{ 表达式 }} |
— | 文本插值 |
v-text="值" |
渲染纯文本 |
响应式基础 — ref 与 reactive
Vue3 提供两种创建响应式数据的方式,这是 Vue 最核心的概念。
ref — 适用于基本类型 + 引用类型
const { createApp, ref } = Vue;
createApp({
setup() {
// ref 包装一个值,使其变成响应式
const count = ref(0);
// 在 JS 中通过 .value 访问/修改
function text() {
count.value++;
}
return { count, text };
}
}).mount("#app");
<!-- 在模板中不需要 .value,Vue 会自动解包 -->
<div>{{ count }}</div>
<button @click="count++">click</button>
<button @click="text()">click</button>
核心认知:
ref()返回一个对象{ value: 原始值 }。JS 中操作需要.value,模板中自动解包。这是 Vue3 响应式系统的核心设计。
reactive — 适用于对象/数组类型
const { createApp, reactive } = Vue;
createApp({
setup() {
// reactive 将对象变成响应式代理
const count1 = reactive({ count: 0 });
// 直接修改属性,无需 .value
function text1() {
count1.count++;
}
// 表单数据绑定场景
const student = reactive({
name: '',
age: '',
address: ''
});
// 模拟从服务器获取数据
function getStudentInfo() {
// 发送 AJAX 请求从后台获取数据后赋值
student.name = "zs";
student.age = "10";
student.address = "qd";
}
function setStudentInfo() {
console.log(student); // 直接使用,不需要 .value
}
return { count1, text1, student, getStudentInfo, setStudentInfo };
}
}).mount("#app");
<!-- reactive 对象直接绑定到表单 -->
<div>
<div>姓名 <input type="text" v-model="student.name" /></div>
<div>年龄 <input type="text" v-model="student.age" /></div>
<div>地址 <input type="text" v-model="student.address" /></div>
</div>
<button @click="getStudentInfo()">修改</button>
<button @click="setStudentInfo()">确认</button>
ref:套一层外壳,数据放
.value;reactive:无外壳,直接操作对象本身
ref vs reactive 对比
| 特性 | ref |
reactive |
|---|---|---|
| 适用类型 | 任意类型(基本 + 引用) | 仅引用类型(对象/数组/Map/Set) |
| 访问方式(JS) | 需要 .value |
直接访问属性 |
| 访问方式(模板) | 自动解包,不需要 .value |
直接访问属性 |
| 重新赋值 | ✅ 可以(ref.value = newObj) |
Object.assign(person, {name:’80’,age: 4}); |
| 解构 | 不会丢失响应性(配合 toRefs) |
❌ 直接解构会丢失响应性 |
| 原理 | 内部也是用 reactive 包装 |
Proxy 代理 |
选 ref 还是 reactive?
// 社区共识和官方推荐:优先使用 ref
// 原因1:ref 适用于任何类型,心智模型统一
const count = ref(0);
const user = ref({ name: 'zs' });
// 原因2:ref 可以安全地重新赋值
user.value = { name: 'ls' }; // ✅ 响应性保持
// reactive 重新赋值会丢失响应性
const state = reactive({ name: 'zs' });
// state = reactive({ name: 'ls' }); // ❌ 响应性丢失!
// 原因3:ref 解构更安全
const { name } = user.value; // 明确知道这是取值操作
// reactive 适合的场景:管理一组相关状态(类似组件内部状态)
const formState = reactive({
username: '',
password: '',
remember: false
});
要点:
ref的本质:ref(0)→reactive({ value: 0 })- 模板中的自动解包仅对顶层 ref 有效(嵌套在对象里的 ref 不会自动解包)
reactive不能重新赋值整个对象,只能修改属性- 发展趋势:
ref越来越受推荐,Vue 官方也在推动”ref 优先”
toRefs 与 toRef
作用:将一个响应式对象中的每一个属性,转换为ref对象。
备注:toRefs与toRef功能一致,但toRefs可以批量转换。
import {ref,reactive,toRefs,toRef} from 'vue'
let person = reactive({name:'张三', age:18, gender:'男'})
// 通过toRefs将person对象中的n个属性批量取出,且依然保持响应式的能力
let {name,gender} = toRefs(person)
计算属性(computed)
计算属性基于响应式依赖进行缓存,只在依赖变化时重新计算。这是性能优化的关键手段。
方法 vs 计算属性
const { createApp, ref, computed } = Vue;
createApp({
setup() {
const score = ref(90);
const firstname = ref("bill");
const familyname = ref("gates");
const title = ref("ceo");
// 普通方法 — 每次组件重新渲染都会重新执行(无缓存)
function test() {
console.log('test 方法执行了');
if (score.value >= 80) return "优秀";
else if (score.value >= 70) return "良好";
else if (score.value >= 60) return "及格";
else return "不及格";
}
// 计算属性 — 有缓存,依赖不变则不会重新计算
const level = computed(() => {
console.log('level 计算属性执行了');
if (score.value >= 80) return "优秀";
else if (score.value >= 70) return "良好";
else if (score.value >= 60) return "及格";
else return "不及格";
});
// 组合多个数据源的计算属性
const allInfo = computed(() => {
return `${title.value}: ${firstname.value} ${familyname.value}`;
});
return { score, level, allInfo, test };
}
}).mount("#app");
<div>{{ score }}</div>
<!-- 计算属性可以绑定到属性上 -->
<div :title="level">{{ level }}</div>
<div>{{ allInfo }}</div>
<!-- 方法每次都要调用,模板中也要写括号 点击事件不用加括号-->
<div>{{ test() }}</div>
计算属性的缓存机制(图解说明)
score = 90 → level 计算 → "优秀"(缓存结果)
↓
模板渲染 3 次(其他数据变化导致的重新渲染)
↓
level 直接返回缓存 "优秀",不重新计算
↓
score = 75 → 依赖变了 → level 重新计算 → "良好"(更新缓存)
可写的计算属性
const fullName = computed({
get() {
return firstname.value + ' ' + familyname.value;
},
set(newValue) {
const [firstname.value, familyname.value] = newValue.split(' ');
}
});
fullName.value = 'Steve Jobs'; // 触发 setter
console.log(firstname.value); // 'Steve'
何时用 computed、何时用 method?
| 场景 | 用 | 原因 |
|---|---|---|
| 基于已有数据计算一个值 | computed |
有缓存,性能更好 |
| 点击按钮执行一个操作 | method |
需要主动触发,不是取值 |
| 列表过滤、排序的结果 | computed |
依赖变化时才重新计算 |
| 提交表单 | method |
这是动作,不是衍生值 |
要点:
computed()返回一个只读的 ref 对象(默认只读)- 计算属性有缓存:只有依赖的响应式数据变化时才重新计算
- 普通方法每次组件重新渲染都会执行,没有缓存
- 适用场景:依赖其他数据的衍生状态(如全名、等级、过滤后的列表)
- 计算属性应该纯粹(无副作用),不要在计算属性里发请求或修改 DOM
条件渲染
Vue 提供 v-if 系列和 v-show 两种条件渲染方式。
v-if / v-else-if / v-else`
<div id="app">
<!-- 单条件 — 为 false 时不渲染(DOM 中不存在该元素) -->
<div v-if="score >= 60">及格</div>
<!-- 二选一 — 两个中只有一个存在于 DOM -->
<div v-if="score >= 60">及格</div>
<div v-else>不及格</div>
<!-- 多条件 — 类似 if / else if / else 链 -->
<div v-if="score >= 80">优秀</div>
<div v-else-if="score >= 70">良好</div>
<div v-else-if="score >= 60">及格</div>
<div v-else>不及格</div>
</div>
关键规则:
v-if/v-else-if/v-else必须连续使用,中间不能插入其他元素。
<template> 分组
当需要对多个元素统一判断时,用 <template> 包裹(不产生额外 DOM 节点):
<template v-if="score >= 80">
<span>优秀</span>
<span>很棒!</span>
</template>
<template v-else-if="score >= 70">
<span>良好</span>
<span>不错!</span>
</template>
渲染结果中不会出现 <template> 标签,只有 <span> 元素。
v-show
<!-- v-show 通过 CSS display 属性切换显示,元素始终保留在 DOM 中 -->
<div v-show="score >= 60">及格</div>
<!-- 当条件为 false 时,等价于 <div style="display: none;">及格</div> -->
v-if vs v-show 对比
| 特性 | v-if |
v-show |
|---|---|---|
| 渲染方式 | 条件为 false 时,元素完全不渲染(不存在于 DOM 树) | 元素始终渲染,通过 display: none 切换 |
| 初始渲染开销 | 低(false 时不渲染) | 高(始终渲染) |
| 切换开销 | 高(切换时销毁/重建子组件和监听器) | 低(仅切换一个 CSS 属性) |
| 适用场景 | 条件很少改变时(如用户登录状态、权限判断) | 需要频繁切换时(如 Tab 页、展开/折叠) |
与 <template> 配合 |
✅ 可以 | ❌ 不可以(v-show 不能用 template) |
选择建议:
- 运行时条件很少改变 →
v-if - 需要非常频繁地切换 →
v-show - 条件大概率不会为 true →
v-if(避免不必要的渲染)
列表渲染(v-for)
v-for 用于遍历数组和对象,生成列表内容。
遍历数组
<table border="10px">
<tr>
<th>index</th>
<th>Id</th>
<th>name</th>
<th>age</th>
<th>addr</th>
</tr>
<!-- 基本遍历 -->
<tr v-for="student in studentList">
<td></td>
<td>{{ student.id }}</td>
<td>{{ student.name }}</td>
<td>{{ student.age }}</td>
<td>{{ student.addr }}</td>
</tr>
<hr>
<!-- 带索引遍历,推荐加 :key 以提升 Vue 的 diff 算法效率 -->
<tr v-for="(student, index) in studentList" :key="student.id">
<td>{{ index }}</td>
<td>{{ student.id }}</td>
<td>{{ student.name }}</td>
<td>{{ student.age }}</td>
<td>{{ student.addr }}</td>
</tr>
</table>
const studentList = reactive([
{ id: 1, name: 'zs', age: 10, addr: 'qd' },
{ id: 2, name: 'zs', age: 10, addr: 'qd' },
{ id: 3, name: 'zs', age: 10, addr: 'qd' },
{ id: 4, name: 'zs', age: 10, addr: 'qd' }
]);
遍历对象
<!-- (value, key) in object -->
<div v-for="(v, k) in student">{{ k }} - {{ v }}</div>
<!-- 输出:
id - 1
name - zs
age - 10
addr - qd
-->
使用范围值
<!-- v-for 也可以接受整数,从 1 开始计数 -->
<div v-for="n in 5">{{ n }}</div>
<!-- 输出: 1 2 3 4 5 -->
v-for 与 v-if 结合
当需要筛选列表中的某些项时,二者结合使用。但不建议在同一元素上同时使用(Vue3 中 v-if 优先级高于 v-for,容易混淆)。
推荐做法 — 用 <template> 包裹分层:
<template v-for="(student, index) in studentList" :key="student.id">
<!-- 过滤掉 id 为 3 的学生 -->
<tr v-if="student.id != 3">
<td>{{ index }}</td>
<td>{{ student.id }}</td>
<td>{{ student.name }}</td>
<td>{{ student.age }}</td>
<td>{{ student.addr }}</td>
</tr>
</template>
更好的做法 — 用计算属性替代模板内筛选:
const filteredStudents = computed(() =>
studentList.filter(s => s.id !== 3)
);
:key 的重要性
<!-- ✅ 正确:使用唯一 ID 作为 key -->
<tr v-for="item in list" :key="item.id">...</tr>
<!-- ⚠️ 迫不得已才用 index(列表不会重新排序/增删时) -->
<tr v-for="(item, index) in list" :key="index">...</tr>
为什么 key 很重要? Vue 的虚拟 DOM diff 算法依赖 key 来识别每个节点。使用唯一 ID 可以确保节点被正确地复用/移动/删除,避免不必要的 DOM 操作。使用 index 作为 key 在列表增删时可能导致渲染错误。
要点:
v-for的语法:item in list或(item, index) in list:key建议用唯一值(如id),避免用index- 不在同一元素上同时使用
v-if和v-for - 遍历对象:
(value, key) in obj - 范围值:
n in 5从 1 开始
事件处理
Vue 通过 v-on(简写 @)绑定事件处理器,支持丰富的修饰符。
事件处理器类型
<div id="app">
<!-- 1. 内联事件处理器 — 直接写 JS 代码 -->
<button @click="console.log('++++'); console.log('+++++');">click</button>
<!-- 2. 内联调用方法 — 带括号调用(可传参) -->
<button @click="printSth()">click</button>
<!-- 3. 方法事件处理器 — 只写方法名(推荐,Vue 自动传入事件对象) -->
<button @click="printSth">click</button>
</div>
function printSth() {
console.log("++++++");
}
方法处理器 vs 内联处理器:
- 方法处理器(
@click="fn"):Vue 会自动传入原生事件对象作为参数 - 内联处理器(
@click="fn()"):需要手动传$event才能获取事件对象
访问事件对象
<!-- 在内联事件处理器中使用 $event 传递事件对象 -->
<form action="https://www.baidu.com" @submit="test($event)">
<button type="submit">提交</button>
</form>
function test(event) {
// 阻止浏览器的默认行为(如表单提交后的页面跳转)
event.preventDefault();
console.log("test....");
}
注意:在方法处理器
@click="fn"中,Vue 自动将事件对象作为第一个参数传入。在v-on中,处理函数应尽量保持纯粹,副作用统一管理。
事件修饰符
事件修饰符是 Vue 对原生 DOM 事件的封装,让事件处理的逻辑更清晰。
| 修饰符 | 作用 | 对应原生 JS |
|---|---|---|
.prevent |
阻止事件的默认行为 | event.preventDefault() |
.stop |
阻止事件冒泡(阻止向父元素传递) | event.stopPropagation() |
.self |
事件只有在元素自身触发时才执行(子元素触发不执行) | event.target === event.currentTarget |
.once |
事件只触发一次,之后自动移除监听 | { once: true } |
.capture |
使用捕获模式(从外到内) | { capture: true } |
.passive |
不阻止默认行为(与 .prevent 互斥),提升滚动性能 |
{ passive: true } |
<!-- prevent 示例 — 阻止表单默认提交 -->
<form @submit.prevent="onSubmit">...</form>
<!-- 修饰符链式调用 -->
<a @click.stop.prevent="doSomething">...</a>
<!-- 多层嵌套演示 self 和 once -->
<div style="width: 300px; height: 300px; background-color: blue;"
@click.self="console.log('外层 — 只有点蓝色区域才触发')">
<div style="width: 200px; height: 200px; background-color: yellow;"
@click.self="console.log('中层 — 只有点黄色区域才触发')">
<div style="width: 100px; height: 100px; background-color: red;"
@click.once="console.log('内层 — 只触发一次')">
</div>
</div>
</div>
按键修饰符
<!-- 键盘按键修饰符 -->
<input @keyup.enter="handleSubmit" /> <!-- 回车键弹起 -->
<input @keyup.right="handler" /> <!-- 右方向键 -->
<input @keyup.esc="handler" /> <!-- Esc 键 -->
<input @keyup.delete="handler" /> <!-- Delete / Backspace -->
<!-- 系统修饰键 -->
<input @keyup.ctrl.enter="handler" /> <!-- Ctrl + Enter -->
<input @keyup.shift.a="handler" /> <!-- Shift + A -->
<!-- .exact — 精确匹配(不配合其他键) -->
<input @keyup.a.exact="handler" /> <!-- 只按 a 键,不配合 Ctrl/Shift 等 -->
<!-- 数字键 -->
<input @keyup.0="handler" /> <!-- 数字键 0 -->
<!-- 鼠标修饰符 -->
<button @click.left="handler">左键</button>
<button @click.right="handler">右键</button>
<button @click.middle="handler">中键</button>
修饰符可以链式调用:@click.stop.prevent="handler"(先阻止冒泡,再阻止默认行为)。
要点:
- 方法处理器优于内联处理器(更清晰、可复用)
- 修饰符让模板中的事件逻辑一目了然
.self场景:当父元素有点击事件,但不想被子元素的点击触发时.prevent和.passive不能同时使用
表单输入绑定(v-model)
v-model 实现表单元素与响应式数据的双向绑定——数据变化更新视图,视图变化更新数据。
各种表单元素的 v-model
const username = ref('');
const hobby = ref(['爬山']); // 复选框组用数组
const gender = ref('f'); // 单选框用字符串
const city = ref('beijing'); // 下拉选择用字符串
const text = ref(''); // 文本域用字符串
<!-- 文本输入框 -->
<input type="text" v-model="username" />
<!-- 复选框 — 绑定数组时,选中的 value 会自动加入数组 -->
<input type="checkbox" v-model="hobby" value="爬山" />爬山
<input type="checkbox" v-model="hobby" value="游泳" />游泳
<input type="checkbox" v-model="hobby" value="学习" />学习
<!-- 选中结果:hobby = ['爬山', '学习'] -->
<!-- 单选框 — 同一组用同一个 v-model -->
<input type="radio" value="m" v-model="gender" />男
<input type="radio" value="f" v-model="gender" />女
<!-- 下拉选择框 -->
<select v-model="city">
<option value="beijing">北京市</option>
<option value="tianjin">天津市</option>
<option value="shanghai">上海市</option>
<option value="wuhan">武汉市</option>
</select>
<!-- 文本域 -->
<textarea v-model="text"></textarea>
<!-- 显示当前值 -->
<div>{{ hobby }}</div>
<div>{{ city }}</div>
v-model 的本质
<!-- v-model 本质上等于以下两个指令的组合 -->
<input
:value="text"
@input="text = $event.target.value"
/>
<!-- 所以 v-model 是 v-bind:value + v-on:input 的语法糖 -->
v-model` 修饰符
| 修饰符 | 作用 | 示例 |
|---|---|---|
.lazy |
改为 change 事件后同步(而非 input) |
v-model.lazy="msg" |
.number |
自动将输入转为数值类型 | v-model.number="age" |
.trim |
自动过滤首尾空白字符 | v-model.trim="name" |
<!-- .lazy — 失去焦点时才更新(而非每次按键) -->
<input v-model.lazy="msg" />
<!-- .number — 输入自动转数字(parseFloat 失败则保留原值) -->
<input v-model.number="age" type="number" />
<!-- .trim — 自动去掉首尾空格 -->
<input v-model.trim="name" />
表单数据获取的完整流程
// 1. 定义响应式数据
const student = reactive({
name: '',
age: '',
address: ''
});
// 2. 从服务器获取数据,自动填充到表单
function getStudentInfo() {
// 模拟 AJAX 请求
student.name = "zs";
student.age = "10";
student.address = "qd";
}
// 3. 提交表单时收集数据
function setStudentInfo() {
console.log(student); // { name: 'zs', age: '10', address: 'qd' }
// 发送 AJAX 请求保存到服务器
}
要点:
v-model是:value+@input的语法糖- 复选框绑定数组时,选中的
value会自动加入数组 - 选中状态由响应式数据决定,
checked/selected属性只在初始渲染时生效,之后以v-model为准 .number在输入非数字时保留原始字符串,不会报错
侦听器(watch)
watch 用于监听响应式数据的变化,并执行副作用(如发请求、操作 DOM、写 localStorage 等)。与计算属性不同,watch 不返回值,只执行操作。
基本用法
<template>
<div class="person">
<h1>情况一:监视【ref】定义的【基本类型】数据</h1>
<h2>当前求和为:{{ sum }}</h2>
<button @click="changeSum">点我sum+1</button>
</div>
</template>
<script setup name="Person">
import {ref, watch} from 'vue'
// 数据
let sum = ref(0)
// 方法
function changeSum() {
sum.value += 1
}
// 监视,情况一:监视【ref】定义的【基本类型】数据
// watch方法的第一个参数不需要写sum.value,直接写sum
const stopWatch = watch(sum, (newValue, oldValue) => {
console.log('sum变化了', newValue, oldValue)
if (newValue >= 10) {
stopWatch()
}
})
</script>
const { createApp, ref, watch } = Vue;
createApp({
setup() {
const username = ref('as');
// 监听 username 变化
watch(username, () => {
console.log('username 变为:', username.value);
});
return { username };
}
}).mount("#app");
<div>
<input type="text" v-model="username" />
</div>
watch` 的监听多个对象
<template>
<div class="person">
<h1>情况二:监视【reactive】定义的【对象类型】数据</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changePerson">修改整个人</button>
</div>
</template>
<script setup name="Person">
import {reactive, watch} from 'vue'
// 数据
let person = reactive({
name: '张三',
age: 18
})
// 方法
function changeName() {
person.name += '~'
}
function changeAge() {
person.age += 1
}
function changePerson() {
//reactive重新分配一个新对象,会失去响应式(可以使用Object.assign去整体替换)
//person = {name: 'lisi', age: 23}
//person = reactive({name: 'lisi', age: 23})
Object.assign(person, {name: '李四', age: 80})
}
// 监视,情况二:监视【reactive】定义的【对象类型】数据
//newVal、oldVal 在回调里指向同一个内存对象 打印的值相同
watch(person, (newValue, oldValue) => {
console.log('person变化了', newValue, oldValue)
})
// vue在watch监听时:在变异 (不是替换) 对象或数组时,旧值将与新值相同,
// 因为它们的引用指向同一个对象/数组。Vue 不会保留变异之前值的副本。
//将对象字符串化,指向的值不同
let newPerson = computed(() => {
return JSON.stringify(person);
});
watch(newPerson, (newValue, oldValue) => {
console.log('person变化了', newValue, oldValue)
})
</script>
// 1. 监听单个 ref
watch(username, (newVal, oldVal) => {
console.log(`从 ${oldVal} 变为 ${newVal}`);
});
// 2. 监听 reactive 对象的某个属性(需要写成 getter 函数)
const student = reactive({ name: 'zs', age: 10 });
watch(
() => student.name, // getter 函数 — 返回要监听的属性
(newVal, oldVal) => {
console.log(`name 从 ${oldVal} 变为 ${newVal}`);
}
);
// 3. 同时监听多个数据源
watch(
[username, () => student.age], // 数组 — 每个元素是一个数据源
([newName, newAge], [oldName, oldAge]) => {
console.log(`name: ${oldName} → ${newName}`);
console.log(`age: ${oldAge} → ${newAge}`);
}
);
// 4. 深度监听整个 reactive 对象
watch(student, (newVal) => {
console.log('student 内部发生了变化', newVal);
}, { deep: true });
// 5. 立即执行(创建时先执行一次回调)
watch(username, (val) => {
// 发请求验证用户名
checkUsernameAvailability(val);
}, { immediate: true });
// 6. 一次性监听(触发一次后自动停止)
watch(username, (val) => {
console.log('只触发一次');
}, { once: true });
watchEffect` — 更简洁的副作用监听
watchEffect
const { watchEffect } = Vue;
// watchEffect 自动追踪回调中使用的所有响应式数据
watchEffect(() => {
console.log('count 当前值:', count.value)
})
// 对比 watch:不需要显式声明依赖,代码更简洁
watch:需明确指定侦听源,可拿到新旧值;默认懒执行watchEffect:自动收集依赖,立即执行一次watch深层次对象时需加{ deep: true }选项
watch vs computed vs watchEffect`
| 特性 | computed |
watch |
watchEffect |
|---|---|---|---|
| 返回值 | 返回一个计算后的值 | 不返回值 | 不返回值 |
| 用途 | 计算衍生数据 | 执行副作用 | 执行副作用 |
| 依赖追踪 | 自动 | 需显式声明 | 自动 |
| 缓存 | ✅ 有 | ❌ 无 | ❌ 无 |
| 立即执行 | 访问时 | 默认不执行(可配 immediate) |
✅ 立即执行 |
| 典型场景 | 过滤列表、格式化文本 | 发请求、存 localStorage | 调试、日志输出 |
要点:
watch是懒执行的(默认创建时不执行回调),加{ immediate: true }可立即执行- 监听 reactive 对象的属性时,需要用 getter 函数:
() => obj.prop - 直接 watch 一个 reactive 对象会隐式开启深度监听(无法关闭)
- 用
watch做副作用(异步请求、DOM 操作),用computed做衍生数据 watchEffect简单但不够精细(无法获取 oldValue,无法控制监听时机)
生命周期钩子
Vue3 的组合式 API 提供生命周期钩子函数,让你在组件生命周期的特定阶段执行代码。
基本用法
<script setup name="Person">
import {ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted} from 'vue'
// 数据
let sum = ref(0)
// 方法
function add() {
sum.value += 1
}
// 创建,不用写beforeCreate、created,对于vue3来说,使用setup替代了
// <script setup> 中所有的代码都是setup函数,所以想输出创建,直接打印就行
console.log('创建')
// 挂载前
onBeforeMount(() => {
console.log('挂载前')
})
// 挂载完毕
onMounted(() => {
console.log('挂载完毕')
})
// 更新前
onBeforeUpdate(() => {
console.log('更新前')
})
// 更新完毕
onUpdated(() => {
console.log('更新完毕')
})
// 卸载前
onBeforeUnmount(() => {
console.log('卸载前')
})
// 卸载完毕
onUnmounted(() => {
console.log('卸载完毕')
})
</script>
完整生命周期图
组件创建
│
▼
setup() 执行 ← 组合式 API 入口,此时没有 this
│
▼
onBeforeMount ← 挂载前,DOM 未生成
│
▼
onMounted ← 挂载完成,DOM 已生成 ★ 发送请求的最佳时机
│
▼
onBeforeUpdate ← 数据变化,DOM 更新前
│
▼
onUpdated ← DOM 更新完成
│
▼
onBeforeUnmount ← 卸载前 ★ 清理资源的最佳时机
│
▼
onUnmounted ← 卸载完成
所有生命周期钩子及典型用途
| 钩子 | 时机 | 典型用途 |
|---|---|---|
setup() |
组件创建时,最先执行 | 定义数据和方法 |
onBeforeMount |
挂载前 | 很少使用 |
onMounted |
挂载后 | 发 AJAX 请求、初始化第三方库、操作 DOM |
onBeforeUpdate |
数据变化后、DOM 更新前 | 获取更新前的 DOM 状态 |
onUpdated |
DOM 更新后 | 操作更新后的 DOM |
onBeforeUnmount |
卸载前 | 清理定时器、取消网络请求、移除事件监听 |
onUnmounted |
卸载后 | 最终清理 |
onActivated |
KeepAlive 缓存组件激活时 | 刷新缓存组件的数据 |
onDeactivated |
KeepAlive 缓存组件停用时 | 暂停定时器等 |
onErrorCaptured |
子组件错误被捕获时 | 错误日志上报 |
实际应用示例
setup() {
let timer = null;
onMounted(() => {
// 组件挂载后:获取数据、启动轮询
fetchUserData();
timer = setInterval(() => {
refreshData();
}, 30000); // 每30秒刷新一次
});
onBeforeUnmount(() => {
// 组件卸载前:清理定时器,防止内存泄漏
if (timer) {
clearInterval(timer);
timer = null;
}
});
return {};
}
⚠️ 内存泄漏警告:如果在组件内创建了定时器、事件监听、WebSocket 连接等,一定要在
onBeforeUnmount中清理,否则组件销毁后这些资源仍会运行。
要点:
onMounted是最常用的钩子 — 发送 AJAX 请求获取数据onBeforeUnmount必须清理资源(定时器、订阅、事件监听)- 生命周期钩子在
setup()中同步调用,Vue 自动将它们关联到当前组件实例 - 生命周期钩子可以在同一组件中多次调用(它们会按顺序执行,不会互相覆盖)
ES6 核心语法速查表
| 概念 | 语法 | 说明 |
|---|---|---|
let |
let x = 10; |
块级作用域,可修改 |
const |
const PI = 3.14; |
块级作用域,不可重赋值,必须初始化 |
| 解构赋值 | let [a, b] = arr; / let { name } = obj; |
从数组/对象中快速提取值 |
| 模板字符串 | ` hello ${name} ` |
反引号 + ${} 插值 |
| 对象简写 | { name, age } |
等价于 { name: name, age: age } |
| 方法简写 | fn() {} |
等价于 fn: function() {} |
| 扩展运算符 | { ...obj } / [...arr] |
浅拷贝/合并 |
| 剩余运算符 | { a, ...rest } = obj |
收集剩余属性 |
| 箭头函数 | x => x * 2 |
简洁函数,无自己的 this |
| 默认参数 | function fn(x = 10) {} |
参数默认值 |
Vue3 核心 API 速查表
| API / 指令 | 写法 | 说明 |
|---|---|---|
createApp() |
createApp({ setup() {} }).mount('#app') |
创建并挂载 Vue 应用 |
ref() |
const x = ref(0) |
声明响应式变量,JS 中用 .value 访问 |
reactive() |
const obj = reactive({}) |
声明响应式对象,直接访问属性 |
computed() |
const v = computed(() => expr) |
计算属性,有缓存 |
watch() |
watch(source, callback) |
侦听数据变化,执行副作用 |
v-bind / : |
:id="id" |
属性绑定 |
v-on / @ |
@click="fn" |
事件绑定 |
v-model |
v-model="data" |
双向数据绑定 |
v-if |
v-if="condition" |
条件渲染(DOM 销毁/重建) |
v-show |
v-show="condition" |
条件切换(display:none) |
v-for |
v-for="item in list" |
列表渲染 |
v-html |
v-html="htmlStr" |
渲染原始 HTML |
onMounted |
onMounted(() => {}) |
挂载后生命周期钩子 |
常见使用模式速查
// Vue3 组件的标准写法
const { createApp, ref, reactive, computed, watch, onMounted } = Vue;
createApp({
setup() {
// 1. 状态 — 响应式数据
const count = ref(0);
const user = reactive({ name: '', age: 0 });
// 2. 计算属性 — 衍生数据
const double = computed(() => count.value * 2);
// 3. 方法 — 事件处理
function increment() { count.value++; }
// 4. 侦听器 — 副作用
watch(count, (newVal) => { console.log(newVal); });
// 5. 生命周期 — 初始化和清理
onMounted(() => { /* 发请求 */ });
// 6. 暴露给模板
return { count, user, double, increment };
}
}).mount("#app");
常见错误与避免方法
| 错误 | 原因 | 正确做法 |
|---|---|---|
模板中 count.value |
忘记模板自动解包 ref | 模板中用 count,JS 中用 count.value |
| 解构 reactive 丢响应性 | reactive 解构后是普通值 | 使用 toRefs() 或直接用 ref |
reactive 重新赋值 |
proxy = newObj 断开响应链 |
用 ref 代替,或 Object.assign(proxy, newObj) |
v-for 不加 :key |
影响 diff 算法效率 | 始终加 :key="item.id" |
v-if 和 v-for 同元素 |
优先级歧义 | 用 <template> 分层 |
| 忘记清理定时器 | 内存泄漏 | 在 onBeforeUnmount 中 clearInterval |
v-html 渲染用户输入 |
XSS 安全风险 | 只用 {{ }} 或对内容做消毒处理 |