Vue3常用API

Vue3常用API
耳朵StriveVue3中常用的API
ref、reactive
Vue 3 的响应式系统是其能够自动更新视图的基石。当修改一个响应式数据时,Vue 能够追踪到这个变化,并自动重新渲染依赖于这个数据的组件部分。ref 和 reactive 就是创建这种响应式数据的两种主要方式
reactive
reactive()是 Vue 3 提供的用于创建响应式对象的函数它接收一个普通的 JavaScript 对象(或数组),并返回该对象的响应式代理(Proxy)
1
2
3
4
5
6
7
8
9
10import { reactive } from 'vue';
const state = reactive({
count: 0,
user: {
name: 'Alice',
age: 30
},
hobbies: ['reading', 'music']
});现在,对
state的任何修改都会触发响应式更新1
2
3
4// 所有这些都是响应式的
state.count++; // 基本类型
state.user.name = 'Bob'; // 嵌套对象
state.hobbies.push('coding'); // 数组
处理对象:专门用于对象类型(Object, Array, Map, Set等)
Proxy 实现:其核心基于 ES6 的
Proxy。它拦截对对象的各种操作(如 get, set, deleteProperty 等),从而实现依赖追踪和触发更新深度响应式:默认是深度响应的。即使嵌套很深的对象或数组,它们的修改也会被追踪到
访问方式:直接访问属性,无需额外语法。
state.count局限性:
- 不能用于原始值(如
string,number,boolean) - 如果你将响应式对象的属性解构或赋值给一个局部变量,该变量的访问和修改将会失去响应性
1
2
3
4
5
6// 错误:解构会使数据失去响应性
const { count, user } = state;
count++; // 不会触发视图更新!
// 正确:始终通过 `state.count` 来访问和修改
state.count++;- 为了解决解构丢失响应性的问题,可以使用
toRefs工具函数。
1
2
3
4
5
6
7
8
9
10import { reactive, toRefs } from 'vue';
const state = reactive({ count: 0, name: 'Alice' });
// `toRefs` 将响应式对象的每个属性都转换为一个 ref
const { count, name } = toRefs(state);
// 现在 count 和 name 都是 ref,需要使用 .value 访问
console.log(count.value); // 0
count.value++; // 有效,并且会更新源对象 state.count- 不能用于原始值(如
ref
ref()用于创建一个响应式的“引用”。它可以持有任何类型的值,包括原始值和对象它接收一个内部值(可以是原始值或对象),并返回一个响应式的、可变的 ref 对象。这个 ref 对象只有一个
.value属性,指向其内部值1
2
3
4import { ref } from 'vue';
const count = ref(0); // 持有原始值
const objectRef = ref({ name: 'Alice' }); // 持有对象访问和修改都必须通过
.value属性1
2
3
4
5
6// 读取值
console.log(count.value); // 0
// 修改值
count.value = 1;
objectRef.value.name = 'Bob'; // 修改对象属性
处理任何类型:既可以处理原始值,也可以处理对象。
.value 访问:这是 ref 最显著的特征。在 JavaScript 中需要通过
.value来操作数据。模板中自动解包:在模板中,你不需要写
.value。Vue 会自动解包顶层 ref。1
2
3
4<template>
<div>{{ count }}</div> <!-- 无需 .value,直接写 count -->
<button @click="count++">Increment</button> <!-- 这里也会自动解包 -->
</template>实现机制:
- 当持有原始值时,Vue 通过对象的 getter/setter 来拦截
.value的访问和修改,实现响应性 - 当持有对象时,它内部会调用
reactive()来深度转换这个对象,使其也变成响应式的。所以const objRef = ref({})等价于ref(reactive({}))
- 当持有原始值时,Vue 通过对象的 getter/setter 来拦截
watchEffect、watch
Vue 3 中两个用于执行副作用(Side Effects) 的核心函数:watchEffect 和 watch。它们是 Composition API 中响应式系统的重要组成部分,用于响应数据变化并执行相应的操作
副作用是指一段代码的执行会影响或依赖于函数外部状态的操作。在前端开发中,典型的副作用包括:
操作 DOM
发送异步请求(API calls)
操作浏览器存储(LocalStorage, Cookies)
打印日志(console.log)**
watchEffect 和 watch 的作用就是在响应式数据发生变化时,自动、高效地执行这些副作用
watchEffect
watchEffect会立即执行传入的函数,并在执行过程中自动追踪其依赖的所有响应式数据(如ref,reactive的属性)。当这些依赖项的任何一方发生变化时,该函数会再次自动执行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import { ref, watchEffect } from 'vue';
const count = ref(0);
const name = ref('Alice');
// watchEffect 会立即执行一次
watchEffect(() => {
// 这个函数内部使用了哪些响应式数据,Vue 就会自动把它们作为依赖收集起来
console.log(`Count is: ${count.value}, Name is: ${name.value}`);
// 副作用,比如操作DOM:
// document.title = `Count: ${count.value}`;
});
// 以下操作会触发上面的 effect 重新执行:
count.value++; // 输出: Count is: 1, Name is: Alice
name.value = 'Bob'; // 输出: Count is: 1, Name is: Bob核心特点:
- 自动依赖追踪:需要手动指定要监听哪些数据。Vue 在副作用函数第一次运行时,会记录下函数中用到了哪些响应式属性。这是它最方便的特性
- 立即执行:它会在初始化时立即执行一次,确保副作用在最初就能生效
- 无效回调(onInvalidate):副作用函数可以接收一个
onInvalidate函数作为参数。这个函数用于在副作用即将重新执行或组件卸载时,清理上一次副作用留下的资源(如取消未完成的异步请求)
1
2
3
4
5
6
7
8
9
10
11
12
13
14watchEffect(async (onInvalidate) => {
// 模拟一个异步请求
const data = await fetchData(count.value);
onInvalidate(() => {
// 这个清理函数会在两种情况下被调用:
// 1. 副作用即将重新执行(因为 count 又变了)
// 2. 组件被卸载
// 你可以在这里取消之前的请求
cancelRequest();
});
// 处理 data...
});
watch
watch的功能更接近 Vue 2 中的watch选项。它需要显式指定要监听的一个或多个数据源,并在数据源变化时执行指定的回调函数。它默认是惰性的,不会立即执行。监听单个数据源
数据源可以是一个 getter 函数,或者一个 ref1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import { ref, reactive, watch } from 'vue';
// 监听一个 ref
const count = ref(0);
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`);
});
// 监听一个 reactive 对象的某个属性(需要使用getter函数)
const state = reactive({ name: 'Alice', age: 30 });
watch(
() => state.name, // 数据源:一个返回具体值的getter函数
(newName, oldName, onInvalidate) => {
console.log(`Name changed from ${oldName} to ${newName}`);
}
);
// 触发变化
count.value++; // 输出: count changed from 0 to 1
state.name = 'Bob'; // 输出: Name changed from Alice to Bob监听多个数据源
传入一个数组,回调函数参数也对应为数组1
2
3
4
5
6
7watch(
[() => state.name, count], // 数据源数组:一个getter和一个ref
([newName, newCount], [oldName, oldCount]) => {
console.log(`Name: ${oldName} -> ${newName}`);
console.log(`Count: ${oldCount} -> ${newCount}`);
}
);深度监听和立即执行
watch的第三个参数是一个可选配置对象1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const deepObj = reactive({ nested: { value: 1 } });
watch(
() => deepObj,
(newValue, oldValue) => {
// 默认情况下, reactive 对象的属性变化不会触发,
// 因为新旧值指向同一个对象引用。
},
{
deep: true, // 开启深度监听,嵌套属性变化也会触发
immediate: true // 立即以当前值执行一次回调(类似 watchEffect 的初始执行)
}
);
deepObj.nested.value = 2; // 在 deep: true 时,会触发回调核心特点:
- 显式指定源:你必须明确告诉 Vue 要监听哪个或哪些值
- 惰性:默认不会在初始化时执行,只有在监听源发生变化时才会执行回调
- 访问新旧值:回调函数会提供变化前后的值,这对于比较逻辑非常有用
- 更具体控制:通过配置
deep和immediate等选项,可以更精细地控制监听行为
emit、prop
在深入细节之前,必须理解 Vue 的一个核心设计原则:单向数据流
- 数据向下:父组件通过 props 将数据传递给子组件
- 事件向上:子组件通过 emits 发送事件通知父组件
Props
Props 是父组件向子组件传递数据的一种方式。你可以将它理解为函数的参数
父组件 (Parent.vue): 使用
v-bind(或简写:)来传递数据1
2
3<template>
<ChildComponent :title="postTitle" :likes="42" :is-published="true" />
</template>子组件 (ChildComponent.vue): 使用
defineProps来声明接收的属性1
2
3
4
5
6
7
8
9
10
11
12
13
14<template>
<div>{{ title }} - {{ likes }} 个赞</div>
</template>
<script setup>
// 使用 defineProps 来声明 props
const props = defineProps({
title: String,
likes: Number,
isPublished: Boolean
})
// 在 JS 中访问需要使用 props.title
console.log(props.title)
</script>
在模板中可以直接使用 title,在 JavaScript 中则需要通过 props.title 访问
单向传递:Props 是只读的。子组件绝对不能直接修改一个 prop(如
props.likes = 43)。如果试图修改,Vue 会在控制台发出警告。这是为了防止子组件意外改变父组件的状态,从而导致数据流变得难以理解Prop 验证:你可以为 props 提供详细的验证要求,这在开发大型应用或组件库时非常有用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23defineProps({
title: String, // 基础类型检查
likes: Number,
// 多个可能的类型
propA: [String, Number],
// 必填的字符串
propB: {
type: String,
required: true
},
// 带有默认值的数字
propC: {
type: Number,
default: 100
},
// 自定义验证函数
propD: {
validator(value) {
// 值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].includes(value)
}
}
})命名规范:在父组件模板中传递 prop 时,应使用 kebab-case(短横线分隔命名),如
<MyComponent greeting-message="hello" />。在子组件defineProps内部声明时,使用 camelCase(驼峰命名)
Emits
Emits 是子组件向父组件通信的方式。当子组件中发生了某些事情(如按钮被点击、输入完成等),它可以通过触发(emit)一个自定义事件来通知父组件
子组件 (ChildComponent.vue): 使用
defineEmits声明要触发的事件,然后调用emit('事件名', 参数)1
2
3
4
5
6
7
8
9
10
11
12
13<template>
<button @click="onButtonClick">点赞</button>
</template>
<script setup>
// 声明触发的事件
const emit = defineEmits(['incrementLikes'])
function onButtonClick() {
// 当按钮被点击时,触发 'incrementLikes' 事件,并传递一个值
emit('incrementLikes', 1)
}
</script>父组件 (Parent.vue): 使用
v-on(或简写@)来监听子组件发出的事件1
2
3
4
5
6
7
8
9
10
11<template>
<ChildComponent @increment-likes="handleLikesIncrease" />
</template>
<script setup>
function handleLikesIncrease(incrementBy) {
// 当收到子组件的事件后,更新父组件自己的状态
// 这才是数据应该被修改的地方!
postLikes.value += incrementBy
}
</script>命名规范:与 props 不同,事件名不存在任何自动化的大小写转换。建议始终使用 kebab-case(短横线分隔命名)作为事件名,因为 HTML 属性是大小写不敏感的。
@my-event无法被监听成@myEvent
computed
computed` 是 Vue Composition API 中用于创建计算属性的函数。计算属性是基于其它响应式数据计算衍生出来的值
它的核心思想是:声明式地描述一个值如何依赖其它值
可以把它想象成一个高效的、自动缓存的计算器。它只会在其依赖的响应式数据发生变化时才会重新计算,否则会直接返回上一次缓存的计算结果
最常见的用法是只提供一个
get函数,此时计算属性是只读的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import { ref, computed } from 'vue';
const firstName = ref('张');
const lastName = ref('三');
// 创建一个计算属性 fullName
// 它接收一个 getter 函数
const fullName = computed(() => {
console.log('计算属性重新计算了!'); // 依赖变化时才会打印
return `${firstName.value}${lastName.value}`;
});
console.log(fullName.value); // 输出:'张三',同时打印 '计算属性重新计算了!'
console.log(fullName.value); // 输出:'张三',但不会打印!因为使用了缓存。
firstName.value = '李'; // 修改依赖项
console.log(fullName.value); // 输出:'李三',同时打印 '计算属性重新计算了!'关键特性:
- 响应式:
fullName本身是一个ref对象,需要通过.value访问,在模板中会自动解包 - 缓存:只要
firstName和lastName没变,多次访问fullName.value不会再次执行计算函数,而是直接返回缓存的值。这是它与普通方法最根本的区别 - 自动依赖追踪:和
watchEffect类似,Vue 会自动追踪getter函数内部使用了哪些响应式数据,并建立依赖关系
- 响应式:
get 和 set 的用途
上面的例子只使用了
getter,所以计算属性是只读的。如果你尝试fullName.value = '王五',将会导致一个错误但有时候,我们需要一个可写的计算属性。例如,你想同时允许设置
fullName,并且这个设置操作会反向分解并更新它依赖的firstName和lastName这时,你就需要为
computed传入一个包含get和set函数的对象,而不是单个 getter 函数get函数作用:获取计算属性的值。当有人读取
computedRef.value时,此函数被调用职责:根据依赖的响应式数据,计算并返回最终的值
set函数作用:设置计算属性的值。当有人尝试给
computedRef.value赋值(如fullName.value = '王 五')时,此函数被调用参数:接收一个参数,即试图设置的新值
职责:通常不是直接改变计算属性本身(因为它的值是由
get函数决定的),而是根据新值去修改计算属性所依赖的源数据
下面的例子展示了如何使用
get和set创建一个双向绑定的计算属性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41import { ref, computed } from 'vue';
// 原始的源数据
const firstName = ref('张');
const lastName = ref('三');
// 可写的计算属性
const fullName = computed({
// Getter:当读取 fullName.value 时工作
get() {
return `${firstName.value}${lastName.value}`;
},
// Setter:当设置 fullName.value 时工作
set(newValue) {
// 当 fullName 被设置时,
// 我们需要将新值分解并更新它所依赖的源数据(firstName 和 lastName)
console.log(`有人试图将 fullName 设置为: ${newValue}`);
// 假设新值的格式是 "姓 名"(中间有空格)
const names = newValue.split(' ');
if (names.length >= 2) {
firstName.value = names[0]; // 更新依赖的源数据
lastName.value = names[1]; // 更新依赖的源数据
}
// 注意:这里没有直接设置 fullName 的值,
// 因为下次读取 fullName.value 时,get() 会根据新的 firstName 和 lastName 重新计算。
}
});
// 1. 读取 get()
console.log(fullName.value); // 输出:'张三'
// 2. 写入 set()
fullName.value = '王 五'; // 这会触发 set('王 五')
// 3. Setter 函数内部更新了 firstName 和 lastName
console.log(firstName.value); // 输出:'王'
console.log(lastName.value); // 输出:'五'
// 4. 现在再读取 get(),得到的自然是新计算的值
console.log(fullName.value); // 输出:'王五'
provide、inject
provide和inject是 Vue 提供的一种依赖注入机制,它允许:祖先组件通过
provide向其所有后代组件(无论嵌套多深)”提供”数据或方法任何后代组件都可以通过
inject来”注入”并接收这些数据或方法
Provide
在祖先组件中,使用 provide 函数来提供数据
1 | import { ref, provide } from 'vue' |
Inject
在后代组件中,使用 inject 函数来注入需要的数据。
1 | import { inject } from 'vue' |
示例
祖先组件 (App.vue)
1 | <script setup> |
后代组件 (DeepChild.vue)
1 | <script setup> |
nextTick
简单一句话:
nextTick用来等 Vue 把响应式变更渲染到 DOM(并完成本轮的更新队列)后再执行代码。它保证你在 JS 中读写 DOM 时拿到的是更新后的真实 DOM 状态Vue 的 响应式数据(
ref/reactive)在 JS 层是即时更新的,但 把这些变化反映到真实 DOM 是异步、可批量合并的(为性能把多次变更合并在一轮渲染里)nextTick的作用是把回调排到「Vue 完成本轮渲染并把修改更新到 DOM 之后」再执行在 Vue 3 中,你可以
import { nextTick } from 'vue',如果不给回调它会返回一个Promise,因此可以await nextTick()常见使用场景
在显示/隐藏元素后立即访问该元素的 DOM(如
focus()、getBoundingClientRect())在条件渲染后触发动画前需要先确保 DOM 已存在
在单元测试里等待组件渲染完成再断言
读写依赖于最终 DOM 布局的值(高度、宽度、位置)
当你多次修改响应式值并需要在渲染完成后执行一次后续工作
1 | // MyComponent.vue |
使用回调或 Promise
1 | // 回调形式 |












