Vue3里computed的deep选项有啥用?该咋合理用?
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.base是reactive对象,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,和computed的deep结合时,要明确谁负责“深跟踪”:
以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负责“浅拦截”,computed的deep负责“深穿透”,如果数据是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默认只跟踪引用)
这时要么给computed加deep: 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,说明没开deep或shallow拦截导致未跟踪到。
打印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,需确认computed的deep是否和它们的“浅响应”逻辑冲突,比如shallowReactive的对象,computed不开deep时,只有顶层属性变化才触发。
computed deep该咋用?
核心原则是“精准控制响应性边界”:
- 场景上:仅在“必须跟踪嵌套属性,且默认浅跟踪满足不了”时开启;
- 性能上:深嵌套、大数据场景谨慎使用,优先考虑
watch或手动触发; - 调试上:结合DevTools和
console.log快速定位响应性问题。
Vue3的响应性设计是“分层可控”的——shallow系列让你手动控制性能,deep选项让你在computed里灵活突破浅跟踪限制,理解透这层逻辑,写复杂业务时才能高效又稳当~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


