第 1 节 概述
Vue 3 最核心的能力是响应式系统。
简单说就是:你改了数据,页面自动跟着变。你不用手动操作 DOM。
在 Vue 3 中,我们使用组合式 API(Composition API) 来编写组件逻辑。
第 2 节 setup 函数
<script setup> 是 Vue 3 提供的一种简写方式。完整的写法是这样的:
1
2
3
4
5
6
7
8
| <script lang="ts">
export default {
setup() {
const count = 0
return { count }
},
}
</script>
|
setup 函数返回的对象,里面的属性可以在模板中直接使用。
使用 <script setup> 可以简写为:
1
2
3
4
5
6
7
| <script setup lang="ts">
const count = 0
</script>
<template>
{{ count }}
</template>
|
| 写法 | 优点 | 缺点 |
|---|
| 完整写法 | 结构清晰,可读性强 | 代码冗长 |
<script setup> | 简洁,少写样板代码 | — |
日常开发都用 <script setup>,这也是官方推荐的方式。
第 3 节 ref 函数
上面的 count 只是一个普通变量,改了它页面不会更新。
要让变量变成"响应式"的,需要用 ref 包裹:
1
2
3
4
5
6
7
8
9
| <script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
{{ count }}
</template>
|
ref 会创建一个 RefImpl 对象,它的 value 属性存的就是真正的值。
在 <script> 中读取或修改时,需要 .value:
1
2
3
| const count = ref(0)
console.log(count.value) // 0
count.value = 1
|
在 <template> 中不需要 .value,Vue 会自动解包:
1
2
3
| <template>
<p>{{ count }}</p> <!-- 不需要写 count.value -->
</template>
|
3.1 工作原理
ref 创建的对象有 getter 和 setter 方法:
- 读取时,
getter 会记录"谁在用这个数据" - 修改时,
setter 通知所有用到这个数据的地方重新渲染
这样数据一变,页面就自动更新了。
说明
ref 可以包裹任意类型的数据,包括数字、字符串、对象、数组。
第 4 节 reactive 函数
对于对象类型的数据,还可以使用 reactive:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <script setup lang="ts">
import { reactive } from 'vue'
const person = reactive({
name: '张三',
age: 18,
skills: ['Vue', 'TypeScript'],
})
</script>
<template>
<p>姓名:{{ person.name }}</p>
<p>年龄:{{ person.age }}</p>
</template>
|
reactive 创建的是 Proxy 对象。在 <script> 中读取和修改都不需要 .value:
1
2
3
| const person = reactive({ name: '张三' })
console.log(person.name) // 不需要 .value
person.name = '李四'
|
reactive 的响应式是深层的,修改嵌套对象也会触发更新。
4.1 ref vs reactive
| 对比项 | ref | reactive |
|---|
| 支持的数据类型 | 全部类型 | 仅对象类型 |
| script 中取值 | .value | 直接读取 |
| template 中取值 | 自动解包 | 直接读取 |
| 深层响应式 | 是 | 是 |
技巧
一个常见的做法是全部用 ref。这样写法一致,不需要纠结用哪个。ref 包裹对象时,内部会自动转成 Proxy,响应式效果一样。
第 5 节 toRefs 和 toRef
解构响应式对象时,会丢失响应式连接:
1
2
3
| const person = reactive({ name: '张三', age: 18 })
let { name, age } = person
name = '李四' // person.name 不会变
|
toRefs 可以把每个属性转成独立的 ref,保持连接:
1
2
3
4
5
6
| import { toRefs } from 'vue'
const person = reactive({ name: '张三', age: 18 })
const { name, age } = toRefs(person)
name.value = '李四' // person.name 也变了
|
如果 person 是 ref 创建的,需要先取 .value:
1
2
| const person = ref({ name: '张三', age: 18 })
const { name, age } = toRefs(person.value)
|
toRef 只解包某一个属性:
1
| const name = toRef(person, 'name')
|
第 6 节 计算属性
如果一个值需要从其他值计算得到,可以用 computed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <script setup lang="ts">
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
const fullName = computed(() => {
return firstName.value + ' ' + lastName.value
})
</script>
<template>
<p>{{ fullName }}</p>
</template>
|
computed 有两个重要特性:
缓存:只有当依赖的值变化时,才会重新计算。多次读取直接返回上次结果。
1
2
3
4
| const fullName = computed(() => {
console.log('计算了') // 只输出一次
return firstName.value + ' ' + lastName.value
})
|
可写:可以设置 setter,但用得少:
1
2
3
4
5
6
7
8
9
10
| const fullName = computed({
get() {
return firstName.value + ' ' + lastName.value
},
set(newValue: string) {
const [first = '', last = ''] = newValue.split(' ')
firstName.value = first
lastName.value = last
},
})
|
第 7 节 侦听器
7.1 watch
watch 监听一个数据的变化,变化时执行回调:
1
2
3
4
5
6
7
8
9
10
11
12
13
| <script setup lang="ts">
import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newValue, oldValue) => {
console.log(`从 ${oldValue} 变为 ${newValue}`)
})
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
|
watch 的常见配置选项:
| 选项 | 作用 |
|---|
{ deep: true } | 深层监听对象内部的变化 |
{ immediate: true } | 创建时立即执行一次回调 |
{ once: true } | 只触发一次 |
深层监听:当监听的是一个 ref 包裹的对象时,内部属性变化需要用 deep:
1
2
3
4
| const person = ref({ name: '张三', age: 18 })
watch(person, (value) => {
console.log('person 变了', value)
}, { deep: true })
|
注意
reactive 创建的对象默认就是深层监听,不需要 deep。
监听单个属性:用 getter 函数:
1
2
3
4
5
6
| watch(
() => person.value.name,
(newValue) => {
console.log('name 变了', newValue)
}
)
|
这样可以只监听 name 的变化,而不关心 age。
7.2 watchEffect
watchEffect 是 watch 的简化版。它会自动追踪内部用到的响应式数据,数据变化时重新执行:
1
2
3
4
5
| import { watchEffect } from 'vue'
watchEffect(() => {
console.log(`姓名:${person.value.name},年龄:${person.value.age}`)
})
|
不用声明要监听谁,它自己会分析依赖。
| 对比 | watch | watchEffect |
|---|
| 声明方式 | 明确指定监听的数据 | 自动追踪依赖 |
| 初始执行 | 默认不执行(可加 immediate) | 立即执行 |
| 适用场景 | 需要新旧值对比,或精确控制 | 简单副作用,不需要旧值 |
第 8 节 生命周期钩子
组件从创建到销毁,会经历一系列阶段。Vue 3 提供了对应的钩子函数。
8.1 完整流程
1
| setup → onBeforeMount → onMounted → onBeforeUpdate → onUpdated → onBeforeUnmount → onUnmounted
|
8.2 onMounted — 组件挂载后
组件已经渲染到 DOM 中,可以操作 DOM 元素、调用 API。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <script setup lang="ts">
import { ref, onMounted } from 'vue'
const count = ref(0)
onMounted(() => {
console.log('组件已经渲染完成')
// 可以在这里调接口获取数据
fetchData()
})
function fetchData() {
// 调用后端 API
}
</script>
|
8.3 onUnmounted — 组件卸载后
组件被移除,用来清理定时器、取消订阅。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| <script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const count = ref(0)
let timer: number | undefined
onMounted(() => {
timer = setInterval(() => {
count.value++
}, 1000)
})
onUnmounted(() => {
clearInterval(timer) // 清理定时器
})
</script>
|
8.4 onBeforeUpdate 与 onUpdated
数据变化导致 DOM 重新渲染时触发:
1
2
3
4
5
6
7
8
9
10
11
12
13
| <script setup lang="ts">
import { ref, onBeforeUpdate, onUpdated } from 'vue'
const count = ref(0)
onBeforeUpdate(() => {
console.log('DOM 即将更新', count.value)
})
onUpdated(() => {
console.log('DOM 已更新')
})
</script>
|
注意
不要在 onUpdated 中修改响应式数据,会触发无限循环。
8.5 生命周期钩子汇总
| 钩子 | 触发时机 | 常见用途 |
|---|
onBeforeMount | DOM 渲染前 | 准备工作 |
onMounted | DOM 渲染后 | 调接口、操作 DOM |
onBeforeUpdate | 数据变化,DOM 更新前 | 获取更新前的 DOM 状态 |
onUpdated | DOM 更新后 | 执行依赖新 DOM 的操作 |
onBeforeUnmount | 组件卸载前 | 保存临时数据 |
onUnmounted | 组件卸载后 | 清理定时器、取消监听 |
响应式系统是 Vue 3 的基石。你可以用 ref 和 reactive 定义数据,用 computed 派生数据,用 watch 监听变化。生命周期钩子则让你在合适的时机做合适的事。
数据准备好了,下一步是:怎么把数据显示到页面上,以及怎么处理用户的操作?