Vue 3.5 Composition API 深度解析:掌握响应式编程的艺术
本文详细介绍了Vue 3.5 Composition API的高级特性,包括ref、reactive、computed、watch等核心API的最佳实践。
Vue 3.5 带来了许多令人兴奋的新特性和性能优化,其中 Composition API 已经成为 Vue 开发的首选方式。它提供了更灵活的代码组织方式、更好的 TypeScript 支持以及更强大的逻辑复用能力。本文将带你深入理解 Composition API 的核心概念和高级用法。
响应式基础:ref 与 reactive#
ref:基础响应式引用#
ref 是 Composition API 中最基本的响应式 API,适用于任何类型的值:
1
2
3
4
5
6
7
8
9
10
11
12
13
| import { ref } from 'vue'
// 基础用法
const count = ref(0)
const message = ref('Hello Vue')
const isVisible = ref(false)
// 在模板中使用时会自动解包
// <template>{{ count }}</template>
// 在 JavaScript 中访问需要 .value
console.log(count.value) // 0
count.value = 1
|
最佳实践:ref 的命名规范#
1
2
3
4
5
6
7
8
| // ✅ 好的命名
const isLoading = ref(false)
const errorMessage = ref('')
const userCount = ref(0)
// ❌ 避免的命名
const data = ref(null) // 太通用
const flag = ref(true) // 不够明确
|
reactive:对象响应式代理#
对于对象类型的数据,reactive 提供了更自然的访问方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| import { reactive } from 'vue'
const state = reactive({
user: {
name: 'John',
email: 'john@example.com'
},
settings: {
theme: 'dark',
language: 'zh-CN'
}
})
// 直接访问,无需 .value
console.log(state.user.name)
state.settings.theme = 'light'
|
reactive 的注意事项#
1
2
3
4
5
6
7
8
9
10
11
12
13
| // ✅ 推荐:始终使用响应式对象
const form = reactive({
username: '',
password: '',
email: ''
})
// ❌ 避免:解构会丢失响应性
const { username } = form // username 不再是响应式的
// ✅ 正确的解构方式
import { toRefs } from 'vue'
const { username, password, email } = toRefs(form)
|
计算属性:computed 的强大功能#
基础计算属性#
1
2
3
4
5
6
| import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
|
可写的计算属性#
1
2
3
4
5
6
7
8
9
10
11
12
13
| const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(newValue) {
const [first, last] = newValue.split(' ')
firstName.value = first
lastName.value = last
}
})
// 可以直接赋值
fullName.value = 'Jane Smith'
|
计算属性的缓存与性能#
1
2
3
4
5
6
7
8
9
10
11
12
| // expensiveCalculation 只在依赖项变化时执行
const expensiveResult = computed(() => {
console.log('执行复杂计算...')
return heavyCalculation(data.value)
})
// 避免在计算属性中进行副作用操作
const badExample = computed(() => {
// ❌ 不要在 computed 中修改其他响应式数据
otherValue.value = processData(data.value)
return processedData
})
|
侦听器:watch 与 watchEffect#
watch:精确的响应式侦听#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| import { ref, watch } from 'vue'
const searchQuery = ref('')
const results = ref([])
// 基础用法
watch(searchQuery, (newQuery, oldQuery) => {
console.log(`查询从 "${oldQuery}" 变为 "${newQuery}"`)
fetchResults(newQuery)
})
// 侦听多个源
watch([searchQuery, filterType], ([newQuery, newFilter], [oldQuery, oldFilter]) => {
fetchFilteredResults(newQuery, newFilter)
})
|
watch 的配置选项#
1
2
3
4
5
6
7
8
9
10
11
| watch(
source,
(newValue, oldValue) => {
// 回调函数
},
{
immediate: true, // 立即执行一次
deep: true, // 深度侦听
flush: 'post' // 'pre' | 'post' | 'sync'
}
)
|
watchEffect:自动追踪依赖#
1
2
3
4
5
6
7
8
9
10
11
12
| import { watchEffect, ref } from 'vue'
const userId = ref(1)
const userData = ref(null)
// 自动追踪回调中使用的响应式数据
watchEffect(async () => {
console.log(`获取用户 ${userId.value} 的数据`)
userData.value = await fetchUser(userId.value)
})
// 当 userId 变化时,effect 会自动重新运行
|
清理副作用#
1
2
3
4
5
6
7
8
9
10
| watchEffect((onCleanup) => {
const timer = setInterval(() => {
updateData()
}, 1000)
// 注册清理函数
onCleanup(() => {
clearInterval(timer)
})
})
|
组合式函数:逻辑复用的艺术#
创建可复用的组合式函数#
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
| // composables/useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const doubled = computed(() => count.value * 2)
const isEven = computed(() => count.value % 2 === 0)
const increment = (step = 1) => {
count.value += step
}
const decrement = (step = 1) => {
count.value -= step
}
const reset = () => {
count.value = initialValue
}
return {
count,
doubled,
isEven,
increment,
decrement,
reset
}
}
|
在组件中使用#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| <template>
<div>
<p>计数: {{ count }}</p>
<p>双倍: {{ doubled }}</p>
<p>偶数: {{ isEven ? '是' : '否' }}</p>
<button @click="increment()">增加</button>
<button @click="decrement()">减少</button>
<button @click="reset()">重置</button>
</div>
</template>
<script setup>
import { useCounter } from '@/composables/useCounter'
const { count, doubled, isEven, increment, decrement, reset } = useCounter(10)
</script>
|
高级组合式函数示例#
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
| // composables/useFetch.js
import { ref, watchEffect } from 'vue'
export function useFetch(url, options = {}) {
const data = ref(null)
const error = ref(null)
const loading = ref(false)
const execute = async () => {
loading.value = true
error.value = null
try {
const response = await fetch(url.value || url, options)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
data.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 如果 url 是响应式的,自动重新获取
if (typeof url === 'object') {
watchEffect(execute)
} else {
execute()
}
return {
data,
error,
loading,
execute
}
}
|
Vue 3.5 新特性探索#
defineModel 简化双向绑定#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| <!-- Vue 3.5 之前 -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const updateValue = (event) => {
emit('update:modelValue', event.target.value)
}
</script>
<!-- Vue 3.5 新语法 -->
<script setup>
const modelValue = defineModel()
</script>
<template>
<input v-model="modelValue" />
</template>
|
Teleport 的增强用法#
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
| <template>
<div>
<button @click="showModal = true">打开模态框</button>
<Teleport to="body">
<div v-if="showModal" class="modal">
<div class="modal-content">
<h2>模态框标题</h2>
<p>内容被传送到 body 元素下</p>
<button @click="showModal = false">关闭</button>
</div>
</div>
</Teleport>
</div>
</template>
<style>
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 8px;
max-width: 500px;
}
</style>
|
性能优化技巧#
使用 shallowRef 和 shallowReactive#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| import { shallowRef, shallowReactive, triggerRef } from 'vue'
// 对于大型对象,避免深度响应式
const largeData = shallowRef({
// 大量嵌套数据
})
// 修改深层属性需要手动触发
largeData.value.deep.property = 'new value'
triggerRef(largeData)
// 或者使用 markRaw 标记不需要响应式的对象
import { markRaw } from 'vue'
const staticConfig = markRaw({
// 静态配置,不需要响应式
})
|
使用 readonly 保护数据#
1
2
3
4
5
6
7
8
9
10
11
| import { reactive, readonly } from 'vue'
const state = reactive({
count: 0,
name: 'Vue'
})
// 提供只读版本给子组件
const readonlyState = readonly(state)
// 子组件无法修改,确保数据流向清晰
|
常见陷阱与解决方案#
陷阱 1:解构失去响应性#
1
2
3
4
5
6
7
8
9
10
11
| const state = reactive({ count: 0 })
// ❌ 错误:count 不再是响应式的
const { count } = state
// ✅ 解决方案 1:使用 toRefs
import { toRefs } from 'vue'
const { count } = toRefs(state)
// ✅ 解决方案 2:直接使用 state.count
// 在模板中:{{ state.count }}
|
陷阱 2:在 watch 中无限循环#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| const data = ref(0)
// ❌ 可能导致无限循环
watch(data, () => {
data.value = data.value + 1
})
// ✅ 使用 nextTick 或条件判断
watch(data, async () => {
await nextTick()
if (someCondition) {
data.value = data.value + 1
}
})
|
陷阱 3:异步操作的竞态条件#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| const data = ref(null)
// ❌ 多个请求可能导致竞态
watch(searchQuery, async () => {
data.value = await fetchResults(searchQuery.value)
})
// ✅ 使用取消令牌或 AbortController
watch(searchQuery, async (newQuery, oldQuery, onCleanup) => {
const controller = new AbortController()
onCleanup(() => {
controller.abort()
})
data.value = await fetchResults(newQuery, {
signal: controller.signal
})
})
|
Vue 3.5 的 Composition API 为我们提供了强大的工具来构建可维护、可复用的前端应用。通过合理使用 ref、reactive、computed、watch 等核心 API,我们可以写出更加清晰和高效的代码。
关键要点:
- 理解
ref 和 reactive 的使用场景和区别 - 善用
computed 进行数据派生和缓存 - 合理使用
watch 和 watchEffect 处理副作用 - 通过组合式函数实现逻辑复用
- 注意常见的性能陷阱和优化技巧
掌握 Composition API 不仅能提升你的 Vue 开发技能,更能让你在构建大型应用时游刃有余。
相关资源: