Code前端首页关于Code前端联系我们

Vue3里computed的deep选项有啥用?该咋合理用?

terry 16小时前 阅读数 110 #SEO

computed默认咋跟踪依赖?deep是干啥的?

Vue3的computed本质是响应式依赖的“计算器”,会自动跟踪依赖数据的变化并重新计算,但默认情况下,computed对象/数组的跟踪是“浅层次”的——只关注引用是否变化,不管内部属性。

举个例子感受下:

import { ref, computed } from 'vue'
const obj = ref({ name: '小明', age: 18 })
const fullInfo = computed(() => `姓名:${obj.value.name},年龄:${obj.value.age}`)
// 只改内部属性,computed不触发
obj.value.name = '小红' 
console.log(fullInfo.value) // 输出还是“姓名:小明,年龄:18”
// 改引用(整个对象替换),computed才触发
obj.value = { name: '小红', age: 18 }
console.log(fullInfo.value) // 输出变成“姓名:小红,年龄:18”

这时候deep: true的作用就体现了——开启后,computed递归跟踪对象的嵌套属性,哪怕只改内部属性,也会触发重新计算,修改上面的代码:

const fullInfo = computed(() => `姓名:${obj.value.name},年龄:${obj.value.age}`, { deep: true })
obj.value.name = '小红' 
console.log(fullInfo.value) // 直接输出“姓名:小红,年龄:18”

啥场景必须开computed的deep?

不是所有场景都要开deep,得看是否需要“精确跟踪嵌套数据”,这三类场景最典型:

表单处理嵌套对象

很多表单用对象存储多层数据,比如用户信息:

const user = reactive({ 
  base: { name: '小明', gender: '男' }, 
  contact: { phone: '123', email: 'xxx@xxx.com' } 
})
const displayName = computed(() => user.base.name, { deep: true })
// 改base.name时,displayName能实时更新
user.base.name = '小红' 

如果不用deep,哪怕user.basereactive对象,computed也只跟踪user.base的引用,内部name变化不会触发计算。

处理树形/层级化数据

像权限菜单、组织架构这类嵌套结构,需要基于内部节点做计算时:

const menu = reactive({
  label: '首页',
  children: [
    { label: '用户管理', children: [] }
  ]
})
const hasSubMenu = computed(() => menu.children.length > 0, { deep: true })
// 给children加子项时,hasSubMenu能及时响应
menu.children[0].children.push({ label: '角色管理' })

对接第三方库的复杂对象

有些库返回的配置对象/实例是嵌套结构(比如图表库的配置项),若要基于内部属性做计算,必须开deep

const chartConfig = ref(echarts.init(/* 初始化配置 */)) 
const isShowLegend = computed(() => chartConfig.value.options.legend.show, { deep: true })
// 第三方库内部修改了legend.show,isShowLegend能感知
chartConfig.value.options.legend.show = false 

和shallowRef、shallowReactive一起用时,deep咋配合?

Vue3还有shallowRef(只跟踪.value的引用变化)、shallowReactive(只跟踪对象顶层属性变化)这类“浅响应”API,和computeddeep结合时,要明确谁负责“深跟踪”

shallowReactive为例:

const shallowObj = shallowReactive({ 
  a: { b: { c: 1 } } 
})
// 场景1:computed不开deep → 只跟踪shallowObj的顶层属性
const val1 = computed(() => shallowObj.a.b.c) 
shallowObj.a = { b: { c: 2 } } // 改顶层a的引用,val1触发
shallowObj.a.b.c = 3 // 改嵌套c,val1不触发
// 场景2:computed开deep → 强制跟踪嵌套
const val2 = computed(() => shallowObj.a.b.c, { deep: true }) 
shallowObj.a.b.c = 3 // val2会触发

简单说:shallow类API负责“浅拦截”,computeddeep负责“深穿透”,如果数据是shallow包裹的,但又需要基于内部属性计算,开deep能突破shallow的限制。

不用deep容易踩哪些坑?

最典型的坑是“数据改了,computed没变化,UI也不更新”,调试时极易踩雷,举两个真实场景:

列表项内部属性变化,计算属性没反应

比如渲染用户列表,用computed统计“成年用户数”:

const users = ref([
  { name: 'A', age: 17 },
  { name: 'B', age: 19 }
])
const adultCount = computed(() => users.value.filter(u => u.age >= 18).length)
// 改某用户的age → adultCount不更新
users.value[0].age = 18 
console.log(adultCount.value) // 还是1(因为users的引用没改,computed默认只跟踪引用)

这时要么给computeddeep: true,要么手动用triggerRef(users)触发更新(但triggerRef更适合应急,长期维护用deep更优雅)。

嵌套对象的“伪更新”错觉

比如用computed做表单验证,依赖嵌套对象字段:

const form = reactive({ 
  address: { province: '北京', city: '朝阳' } 
})
const isAddressValid = computed(() => form.address.city.length > 0)
// 改city → isAddressValid没变化(因为form.address的引用没改)
form.address.city = '海淀' 
console.log(isAddressValid.value) // 逻辑本应更新,但computed没触发,显示旧值

这种情况容易误以为“逻辑写错了”,实际是响应性跟踪没到位,开deep就能解决。

开deep会影响性能不?咋平衡?

deep: true本质是递归遍历对象所有嵌套属性,给每个属性加响应式拦截,若数据是“深嵌套+体积大”(比如几百层的树、大数组套对象),开deep可能导致:

  • 初始化时递归遍历耗时增加
  • 每次嵌套属性变化,触发大量依赖更新

所以要“按需开启”

只在必要时开

先确认“是否真的需要跟踪嵌套属性”,比如前面的表单例子,只有“必须基于内部属性计算”时才开,否则用watch更灵活(watch可指定deep或具体路径)。

替代方案:手动触发/精确watch

若数据嵌套深但仅需跟踪个别属性,用watch指定路径更轻量:

// 只跟踪user.base.name的变化,比computed开deep更高效
watch(() => user.base.name, (newVal) => {
  // 执行和computed一样的计算逻辑
})

结合业务场景取舍

若为“用户频繁操作的表单”(如注册页),数据层级浅(2 - 3层),开deep性能影响可忽略;若为“大数据可视化”场景(如实时渲染万条数据的树),则需避免全局deep,改用局部watch或手动管理响应性。

咋调试computed的响应性问题?

遇到“computed不更新”时,按以下步骤排查:

用DevTools看依赖跟踪

Vue DevTools中找到对应的computed,查看“依赖项”是否包含你修改的属性,若依赖项只有对象引用(如obj),但你改了内部obj.a,说明没开deepshallow拦截导致未跟踪到。

打印computed的触发时机

computed的getter里加console.log,观察何时执行:

const fullInfo = computed(() => {
  console.log('computed触发了')
  return `姓名:${obj.value.name},年龄:${obj.value.age}`
}, { deep: true })
// 改obj.value.name后,看控制台是否打印“computed触发了”

检查数据是否被shallow包裹

若用了shallowRef/shallowReactive,需确认computeddeep是否和它们的“浅响应”逻辑冲突,比如shallowReactive的对象,computed不开deep时,只有顶层属性变化才触发。

computed deep该咋用?

核心原则是“精准控制响应性边界”

  • 场景上:仅在“必须跟踪嵌套属性,且默认浅跟踪满足不了”时开启;
  • 性能上:深嵌套、大数据场景谨慎使用,优先考虑watch或手动触发;
  • 调试上:结合DevTools和console.log快速定位响应性问题。

Vue3的响应性设计是“分层可控”的——shallow系列让你手动控制性能,deep选项让你在computed里灵活突破浅跟踪限制,理解透这层逻辑,写复杂业务时才能高效又稳当~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门