三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

计算属性与侦听器

计算属性与侦听器

计算属性与侦听器

  • computed:从现有数据“算”出新数据(有缓存,无副作用)
  • watch:监听数据变化,执行副作用(如请求、日志、操作上报)

1. 计算属性 computed

为什么需要computed?

模板中直接写复杂表达式会导致臃肿、重复、不可复用。
把“根据现有数据算出新数据”的逻辑放在<script setup>中,模板只负责渲染。

基本用法

import{ref,computed}from'vue'constkeyword=ref('')constusers=ref([...])// 源数据constfilteredUsers=computed(()=>{returnusers.value.filter(user=>user.name.includes(keyword.value))})

三个要点

  1. 模板中直接使用,不要加.value(自动解包):v-for="user in filteredUsers"
  2. 脚本中必须用.value读取值:filteredUsers.value.length
  3. 只读:不能给计算属性赋值,要改变结果就修改它依赖的源数据

缓存特性

  • computed 会缓存计算结果,只有当依赖的响应式数据变化时才重新计算
  • 多次使用同一个计算属性,只会执行一次计算(后续读缓存)

对比普通方法

  • 方法每次被调用都会重新执行(无论依赖是否变化)
  • 模板渲染时多次调用方法就会执行多次,无缓存

类比:computed 像电子价目牌(商品不变就不动),方法像收银员每接一个任务就重新计算

误区:计算函数里如果使用了非响应式数据(如Date.now()),Vue 无法感知变化,缓存不会失效。

典型应用:搜索 + 筛选

同时根据关键字和状态过滤列表,两个条件用 AND 叠加:

constkeyword=ref('')conststatusFilter=ref('全部')constfilteredUsers=computed(()=>{returnusers.value.filter(user=>{constmatchKeyword=keyword.value===''||user.name.includes(keyword.value)||user.dept.includes(keyword.value)constmatchStatus=statusFilter.value==='全部'||user.status===statusFilter.valuereturnmatchKeyword&&matchStatus})})

模板中直接遍历filteredUsers,人数统计直接用filteredUsers.length


2. 侦听器 watch

与 computed 的区别

  • computed 是纯计算,不应包含副作用(请求、日志等)
  • watch 专门用于数据变化时执行副作用

口诀:能算就用 computed,要动才用 watch

基本用法

import{ref,watch}from'vue'constkeyword=ref('')watch(keyword,(newVal,oldVal)=>{console.log(`关键词从「${oldVal}」变为「${newVal}`)// 可以在这里:发请求、记日志、保存数据等})

回调参数:(新值, 旧值)

监听多个数据源

watch([keyword,statusFilter],([newKeyword,newStatus],[oldKeyword,oldStatus])=>{// 任一数据变化都触发})

防抖处理(debounce)

连续触发时,只执行最后一次(如输入暂停 500ms 后再发起请求):

lettimer=nullwatch(keyword,(newVal)=>{clearTimeout(timer)timer=setTimeout(()=>{// 执行上报或请求},500)})

清除上一次的定时器,保证只有停止输入超过指定时间才会执行。

选项:immediate(立即执行)

  • 默认 watch 只在数据第一次变化后才触发
  • 设置immediate: true可在页面初始化时立即执行一次回调(此时oldValueundefined
watch(statusFilter,(newVal)=>{// 初始化时也会执行},{immediate:true})

常用于:进入页面立即加载数据、记录初始状态等。

选项:deep(深度监听)

  • 普通 watch 只监听引用类型变量的引用变化(如数组整体替换)
  • 如果需要监听对象内部属性或数组元素变化,设置deep: true
watch(users,()=>{// 当 users 数组内某个对象的属性被修改时触发},{deep:true})

注意事项

  • deep有性能损耗,大对象或深层嵌套对象要小心使用
  • 深度监听时回调中的新旧值是同一个引用,无法获得变化前的快照(newVal === oldVal
  • 如果需要获取旧值,自己在外层保存快照

监听 reactive 对象的某个属性(getter 形式)

import{reactive,watch}from'vue'conststate=reactive({name:'张三',age:30})watch(()=>state.name,(newVal,oldVal)=>{// 只监听 name 属性,无需 deep})

这种写法更精确、性能更好。


3. computed vs watch 选型总结

computedwatch
用途派生数据(计算新值)执行副作用(动作)
返回值有返回值,可直接在模板使用无返回值,只执行回调
缓存有(依赖不变不重复计算)无(每次变化都触发)
触发条件依赖变化时自动重新计算被监听数据变化时执行回调
典型场景搜索过滤、列表统计、格式化显示数据变化请求接口、日志上报、防抖搜索、配置保存
依赖多个数据多个响应式数据都可作为依赖可监听单个或多个数据源
异步支持不能包含副作用适合放异步请求

选择原则

  • 需要得到一个新数据→ computed
  • 需要在数据变化时做一件事→ watch

两者常配合使用,并非二选一。


4. 关键代码速查

搜索筛选(computed)

constfilteredUsers=computed(()=>{returnusers.value.filter(user=>{constmatchKeyword=keyword.value===''||user.name.includes(keyword.value)||user.dept.includes(keyword.value)constmatchStatus=statusFilter.value==='全部'||user.status===statusFilter.valuereturnmatchKeyword&&matchStatus})})

联动上报(watch + 防抖)

lettimer=nullwatch([keyword,statusFilter],([newKeyword,newStatus])=>{clearTimeout(timer)timer=setTimeout(()=>{addLog(`筛选条件已变化:关键字「${newKeyword}」、状态「${newStatus}`)},500)})

immediate + deep 示例

// 页面打开立即记录当前筛选状态watch(statusFilter,(newStatus)=>{addLog(`当前筛选状态为「${newStatus}`)},{immediate:true})// 监听用户列表内部变化watch(users,()=>{addLog('用户列表数据发生变化')},{deep:true})

5. 常见错误排查

现象原因解决
模板中filteredUsers.value报错模板自动解包,不需要.value直接写filteredUsers
搜索无效,列表不变v-for仍遍历源数据users改为filteredUsers
给计算属性赋值报错computed 只读修改依赖的源数据
点击“切换状态”日志不触发没有加deep: true监听对象/数组内部变化需加deep
每输入一个字符都上报缺少防抖clearTimeout/setTimeout控制
刷新页面没有初始化日志忘记immediate: true在 watch 配置中添加
deep 监听时新、旧值相同深度监听的新旧值是同引用需要旧值请自行深拷贝保存
← 返回列表