第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 (依赖变化,重新计算)

可写的计算属性

计算属性默认是只读的,但可以通过提供getset函数来创建可写的计算属性:

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函数支持以下选项:

  1. **deep**:是否深度监听对象的所有属性
  2. **immediate**:是否立即执行回调函数(初始执行一次)
  3. **flush**:回调函数的执行时机,可选值为prepostsync
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: World

watchEffect与watch的区别

特性 watch watchEffect
依赖追踪 需要显式指定依赖 自动追踪依赖
初始执行 默认为false 默认为true(立即执行)
回调参数 提供新值和旧值 不提供新值和旧值
深度监听 需要显式设置deep: true 自动深度监听
执行时机 可以通过flush选项控制 可以通过flush选项控制

watchEffect的执行时机

watchEffect的执行时机可以通过flush选项控制:

  1. **pre**:在组件更新前执行(默认)
  2. **post**:在组件更新后执行
  3. **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 停止侦听与副作用清除

watchwatchEffect函数都返回一个停止函数,调用该函数可以停止侦听或清除副作用。

停止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)
  })
})

计算属性与侦听器的选择

何时使用计算属性

  1. 需要派生数据:当需要从现有数据派生新数据时
  2. 需要缓存:当计算过程比较耗时,且依赖数据不频繁变化时
  3. 模板中使用:当需要在模板中直接使用派生数据时
  4. 只读数据:当派生数据主要用于展示,不需要修改时

何时使用watch

  1. 需要监听数据变化:当需要在数据变化时执行副作用时
  2. 需要访问旧值:当需要比较数据变化前后的值时
  3. 异步操作:当需要在数据变化时执行异步操作时
  4. 条件执行:当需要根据数据变化条件执行不同的逻辑时

何时使用watchEffect

  1. 自动追踪依赖:当需要自动追踪多个依赖时
  2. 副作用清除:当需要清理副作用产生的资源时
  3. 立即执行:当需要副作用立即执行一次时
  4. 简单副作用:当副作用逻辑比较简单,不需要访问旧值时

总结

Vue 3组合式API提供了computedwatchwatchEffect三个函数来处理派生数据和副作用:

  1. **computed**:用于创建带缓存的计算属性,适合处理派生数据
  2. **watch**:用于监听特定数据的变化,适合执行复杂的副作用和异步操作
  3. **watchEffect**:用于创建自动追踪依赖的副作用,适合简单的副作用和资源管理

通过合理使用这些函数,可以编写出更高效、更清晰的Vue组件代码。在选择使用哪个函数时,需要根据具体的使用场景和需求来决定。

在下一节中,我们将学习组合式API中的生命周期钩子与依赖注入。

« 上一篇 响应式基础 下一篇 » 生命周期钩子与依赖注入