第6章:组合式API
第15节:计算属性与侦听器
6.15.1 组合式API中的计算属性
计算属性是Vue中用于处理派生数据的重要特性,它具有缓存机制,只有当依赖的数据发生变化时才会重新计算。
基本用法
在组合式API中,使用computed函数来创建计算属性:
import { ref, computed } from 'vue'
const count = ref(0)
// 创建计算属性
const doubleCount = computed(() => count.value * 2)
console.log(doubleCount.value) // 0
count.value++
console.log(doubleCount.value) // 2 (自动更新)计算属性的缓存机制
计算属性会缓存计算结果,只有当依赖的数据发生变化时才会重新计算:
const count = ref(0)
const expensiveComputation = computed(() => {
console.log('计算中...')
// 模拟耗时计算
for (let i = 0; i < 1000000; i++) {
// 空循环
}
return count.value * 2
})
console.log(expensiveComputation.value) // 计算中... 0
console.log(expensiveComputation.value) // 0 (直接返回缓存结果,不重新计算)
count.value++
console.log(expensiveComputation.value) // 计算中... 2 (依赖变化,重新计算)可写的计算属性
计算属性默认是只读的,但可以通过提供get和set函数来创建可写的计算属性:
const firstName = ref('张')
const lastName = ref('三')
const fullName = computed({
// getter函数
get: () => `${firstName.value}${lastName.value}`,
// setter函数
set: (newValue) => {
const [newFirstName, newLastName] = newValue.split(' ')
firstName.value = newFirstName
lastName.value = newLastName || ''
}
})
console.log(fullName.value) // 张三
// 调用setter函数
fullName.value = '李 四'
console.log(firstName.value) // 李
console.log(lastName.value) // 四
console.log(fullName.value) // 李四计算属性在模板中的使用
在模板中使用计算属性时,不需要使用.value,Vue会自动解包:
<template>
<div>
<p>count: {{ count }}</p>
<p>doubleCount: {{ doubleCount }}</p> <!-- 自动解包 -->
<button @click="count++">增加</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
</script>6.15.2 watch函数的使用
watch函数用于监听数据变化并执行回调函数,它是组合式API中的侦听器。
基本使用
import { ref, watch } from 'vue'
const count = ref(0)
// 监听单个ref数据
watch(count, (newVal, oldVal) => {
console.log(`count changed: ${oldVal} -> ${newVal}`)
})
count.value++ // count changed: 0 -> 1
count.value++ // count changed: 1 -> 2监听多个源
watch函数可以同时监听多个数据源,当其中任何一个数据变化时,都会触发回调函数:
import { ref, watch } from 'vue'
const count = ref(0)
const message = ref('Hello')
// 监听多个ref数据
watch([count, message], ([newCount, newMessage], [oldCount, oldMessage]) => {
console.log(`count changed: ${oldCount} -> ${newCount}`)
console.log(`message changed: ${oldMessage} -> ${newMessage}`)
})
count.value++ // 触发回调
message.value = 'World' // 触发回调监听reactive对象
import { reactive, watch } from 'vue'
const state = reactive({
count: 0,
message: 'Hello'
})
// 监听reactive对象的所有属性
watch(state, (newState, oldState) => {
console.log('state changed:', newState, oldState)
}, { deep: true }) // 需要使用deep: true来深度监听对象
state.count++ // 触发回调监听reactive对象的单个属性
import { reactive, watch } from 'vue'
const state = reactive({
user: {
name: '张三',
age: 25
}
})
// 监听reactive对象的单个属性
watch(() => state.user.name, (newName, oldName) => {
console.log(`用户名 changed: ${oldName} -> ${newName}`)
})
state.user.name = '李四' // 触发回调watch的选项
watch函数支持以下选项:
- **
deep**:是否深度监听对象的所有属性 - **
immediate**:是否立即执行回调函数(初始执行一次) - **
flush**:回调函数的执行时机,可选值为pre、post或sync
const count = ref(0)
watch(count, (newVal, oldVal) => {
console.log(`count changed: ${oldVal} -> ${newVal}`)
}, {
immediate: true, // 立即执行一次
deep: false, // 不深度监听
flush: 'pre' // 在组件更新前执行
})6.15.3 watchEffect响应式副作用
watchEffect函数用于创建响应式副作用,它会自动追踪依赖,并在依赖变化时重新执行。
基本用法
import { ref, watchEffect } from 'vue'
const count = ref(0)
const message = ref('Hello')
// 创建响应式副作用
watchEffect(() => {
console.log(`count: ${count.value}, message: ${message.value}`)
})
count.value++ // 触发副作用:count: 1, message: Hello
message.value = 'World' // 触发副作用:count: 1, message: WorldwatchEffect与watch的区别
| 特性 | watch | watchEffect |
|---|---|---|
| 依赖追踪 | 需要显式指定依赖 | 自动追踪依赖 |
| 初始执行 | 默认为false | 默认为true(立即执行) |
| 回调参数 | 提供新值和旧值 | 不提供新值和旧值 |
| 深度监听 | 需要显式设置deep: true | 自动深度监听 |
| 执行时机 | 可以通过flush选项控制 | 可以通过flush选项控制 |
watchEffect的执行时机
watchEffect的执行时机可以通过flush选项控制:
- **
pre**:在组件更新前执行(默认) - **
post**:在组件更新后执行 - **
sync**:同步执行
watchEffect(() => {
// 副作用逻辑
}, {
flush: 'post' // 在组件更新后执行
})示例:使用watchEffect实现数据持久化
import { ref, watchEffect } from 'vue'
// 从localStorage加载初始数据
const count = ref(JSON.parse(localStorage.getItem('count') || '0'))
// 自动将count的变化保存到localStorage
watchEffect(() => {
localStorage.setItem('count', JSON.stringify(count.value))
})6.15.4 停止侦听与副作用清除
watch和watchEffect函数都返回一个停止函数,调用该函数可以停止侦听或清除副作用。
停止watch侦听
const count = ref(0)
const stopWatch = watch(count, (newVal, oldVal) => {
console.log(`count changed: ${oldVal} -> ${newVal}`)
})
count.value++ // 触发回调
stopWatch() // 停止侦听
count.value++ // 不再触发回调停止watchEffect副作用
const count = ref(0)
const stopEffect = watchEffect(() => {
console.log(`count: ${count.value}`)
})
count.value++ // 触发副作用
stopEffect() // 停止副作用
count.value++ // 不再触发副作用副作用清除函数
在watchEffect中,可以返回一个清除函数,用于在副作用重新执行或停止时清理资源:
import { ref, watchEffect } from 'vue'
const searchQuery = ref('')
watchEffect((onInvalidate) => {
const searchQueryValue = searchQuery.value
console.log(`搜索: ${searchQueryValue}`)
// 模拟异步搜索
const timer = setTimeout(() => {
console.log(`搜索结果: ${searchQueryValue}`)
}, 1000)
// 清除函数,在副作用重新执行或停止时调用
onInvalidate(() => {
clearTimeout(timer)
console.log(`清除搜索: ${searchQueryValue}`)
})
})
searchQuery.value = 'Vue' // 触发副作用,清除之前的定时器
searchQuery.value = 'React' // 触发副作用,清除之前的定时器示例:使用副作用清除函数实现防抖
import { ref, watchEffect } from 'vue'
const inputValue = ref('')
const debouncedValue = ref('')
watchEffect((onInvalidate) => {
const value = inputValue.value
// 防抖:300ms后更新debouncedValue
const timer = setTimeout(() => {
debouncedValue.value = value
console.log('防抖后的值:', debouncedValue.value)
}, 300)
// 清除函数,在输入变化时清除之前的定时器
onInvalidate(() => {
clearTimeout(timer)
})
})计算属性与侦听器的选择
何时使用计算属性
- 需要派生数据:当需要从现有数据派生新数据时
- 需要缓存:当计算过程比较耗时,且依赖数据不频繁变化时
- 模板中使用:当需要在模板中直接使用派生数据时
- 只读数据:当派生数据主要用于展示,不需要修改时
何时使用watch
- 需要监听数据变化:当需要在数据变化时执行副作用时
- 需要访问旧值:当需要比较数据变化前后的值时
- 异步操作:当需要在数据变化时执行异步操作时
- 条件执行:当需要根据数据变化条件执行不同的逻辑时
何时使用watchEffect
- 自动追踪依赖:当需要自动追踪多个依赖时
- 副作用清除:当需要清理副作用产生的资源时
- 立即执行:当需要副作用立即执行一次时
- 简单副作用:当副作用逻辑比较简单,不需要访问旧值时
总结
Vue 3组合式API提供了computed、watch和watchEffect三个函数来处理派生数据和副作用:
- **
computed**:用于创建带缓存的计算属性,适合处理派生数据 - **
watch**:用于监听特定数据的变化,适合执行复杂的副作用和异步操作 - **
watchEffect**:用于创建自动追踪依赖的副作用,适合简单的副作用和资源管理
通过合理使用这些函数,可以编写出更高效、更清晰的Vue组件代码。在选择使用哪个函数时,需要根据具体的使用场景和需求来决定。
在下一节中,我们将学习组合式API中的生命周期钩子与依赖注入。