Vue3里computed和reactive咋用?常见疑问一次说清
reactive是干啥的?怎么在Vue3里用?
简单说,reactive 是Vue3实现对象级响应式的核心工具,它能把普通JavaScript对象“变成活的”——当对象里的属性被修改时,依赖这些属性的视图、计算属性、侦听器会自动更新。
原理上,reactive 借助ES6的Proxy对对象进行“代理”,拦截对象的读取、修改、删除等操作,悄悄完成“依赖收集”和“触发更新”的逻辑,举个实际用法:
import { reactive } from 'vue'
// 把普通对象包成响应式
const state = reactive({
count: 0,
todos: [ { text: '学习Vue', done: false } ]
})
// 修改属性时,依赖它的地方会自动更新
state.count++ // 视图里用了count的地方会同步变
state.todos.push({ text: '写代码', done: false }) // 数组新增元素也能检测到
但用的时候得注意“不能直接替换整个对象”。state = { count: 1 } 会让原来的Proxy对象被丢弃,响应式就失效了,要改数据,得直接改属性(像 state.count = 1 这样)。
computed计算属性有啥用?和普通函数区别在哪?
computed 是用来生成“依赖其他响应式数据的派生值”的,说白点,就是把多个响应式数据“拼一拼、算一算”,得到新值,还能自动缓存结果,性能更优。
和普通函数最大的区别是“缓存机制”,比如有个展示全名的场景:
const state = reactive({ first: '张', last: '三' })
// 用computed定义
const fullName = computed(() => state.first + state.last)
// 用普通函数定义
function getFullName() { return state.first + state.last }
如果在模板里多次用 fullName,computed 只会在 first 或 last 变化时重新计算一次;但 getFullName() 每次渲染都会执行,哪怕数据没变化,所以计算逻辑越复杂、调用越频繁,computed 的性能优势越明显。
reactive和computed在响应式原理上有啥不同?
虽然最终都是为了“数据变了自动更新”,但两者的角色完全不同:
-
reactive负责“激活数据”:把普通对象变成Proxy代理对象,拦截数据的读写操作,默默记录哪些地方用了这个数据(依赖收集),数据变了就通知这些地方更新(触发更新),可以理解为“让数据有感知能力”。
-
computed负责“派生数据”:它本身不存原始数据,而是基于其他响应式数据(比如reactive/ref的数据)做计算,内部用
effect实现“懒计算+缓存”——默认不执行计算逻辑,等依赖的数据变了,才重新计算,而且只在真正被使用时才会触发第一次计算,可以理解为“让派生数据更聪明”。
怎么用reactive创建复杂响应式对象?要避开哪些坑?
处理对象、数组、嵌套结构时,reactive 挺“智能”,但这些细节要注意:
嵌套对象也能响应
reactive 是深层响应式,嵌套对象里的属性变化也能检测到。
const state = reactive({
user: {
info: { name: 'Jack', age: 18 }
}
})
state.user.info.age = 19 // 能触发更新,因为Proxy会递归代理嵌套对象
避开“替换对象”的坑
前面提过,直接给reactive对象赋值会丢响应式。
const state = reactive({ count: 0 })
state = { count: 1 } // 错!原来的Proxy被替换,响应式没了
要改成 state.count = 1 才对。
和ref的分工
reactive 更适合对象/数组这类“引用类型”;如果要存字符串、数字等“基本类型”,得用ref(因为reactive(1)没用,Proxy代理基本类型没意义)。
const count = ref(0) // 基本类型用ref const list = reactive([]) // 数组用reactive
computed里能直接改响应式数据吗?为啥?
默认情况下,computed 的回调是“getter(只读)”,不能直接改数据——否则会导致依赖关系混乱,甚至无限循环更新,但如果业务需要“通过计算属性修改数据”,可以给computed配setter:
const state = reactive({ first: '张', last: '三' })
// 带setter的computed
const fullName = computed({
get() { return state.first + state.last },
set(newVal) {
const [first, last] = newVal.split(' ')
state.first = first
state.last = last
}
})
// 调用setter修改
fullName.value = '李 四' // 会触发state.first和state.last的更新
getter里别改数据(容易出 bug),要改就用setter明确定义修改逻辑。
当reactive数据嵌套深的时候,computed怎么处理更高效?
遇到多层嵌套(比如state.user.info.address.city),computed 依赖这类数据时,要注意这些优化点:
避免无意义的深层依赖
如果只需要city,就别把整个user.info都塞给computed,精准依赖能减少不必要的重新计算。
const city = computed(() => state.user.info.address.city)
拆分响应式数据结构
如果嵌套太深,不妨把深层数据单独用reactive或ref包起来,比如把address单独抽成响应式对象:
const address = reactive({ city: '北京', street: 'xxx路' })
const state = reactive({ user: { info: { address } } })
这样computed依赖address.city时,结构更扁平,追踪更高效。
慎用shallowReactive
如果确定某些深层数据不需要响应式(比如纯展示的配置项),可以用shallowReactive减少代理开销,但要注意:shallowReactive 只代理第一层属性,深层属性变化不会触发更新,除非你完全确定需求,否则优先用reactive。
实际项目里,哪些场景适合用reactive,哪些适合computed?
分场景看更清楚:
reactive的典型场景
- 管理复杂状态集合:比如用户信息(含姓名、头像、权限)、购物车商品列表(每个商品的价格、数量、选中状态)、表单多字段(用户名、密码、验证码)。
- 需要多个属性联动:比如一个“筛选面板”组件,用
reactive包起所有筛选条件(关键词、分类、价格区间),改任何一个条件都能触发列表重新筛选。
computed的典型场景
- 派生新值:过滤列表(
state.list.filter(...))、拼接字符串(如全名)、计算总数(购物车商品总数量)。 - 缓存高频使用的计算逻辑:比如表格里的“每行的操作按钮是否显示”,依赖行数据的权限,用
computed缓存判断结果,避免每行渲染时重复计算。 - 双向绑定的计算属性:搜索关键词”同时关联输入框和URL参数,用带setter的
computed实现双向同步。
用computed时遇到依赖没更新的情况,咋排查?
遇到computed值“不跟着数据变”,按这几步查:
检查依赖是否真的“响应式”
如果依赖的是普通对象(没包reactive/ref),computed根本没法追踪变化。
const rawData = { count: 0 } // 普通对象,非响应式
const wrongComputed = computed(() => rawData.count + 1)
rawData.count = 1 // wrongComputed不会更新!
要改成 const data = reactive({ count: 0 }) 才对。
检查是否“替换了整个响应式对象”
比如用reactive包了个对象,后来直接赋值替换:
const state = reactive({ count: 0 })
state = { count: 1 } // 原来的Proxy被丢了,computed依赖的旧state没变化
改成 state.count = 1 就能触发更新。
检查是否用了shallowReactive
如果依赖的是shallowReactive的深层属性,修改深层属性不会触发更新。
const state = shallowReactive({ info: { name: 'Jack' } })
const nameComputed = computed(() => state.info.name)
state.info.name = 'Tom' // nameComputed不会更新!因为shallowReactive只代理第一层
这种情况要么换reactive,要么接受“手动触发更新”(但不推荐)。
检查computed回调里的“非响应式依赖”
如果回调里用了外部普通变量,修改它不会触发computed更新。
let externalVar = 0 const badComputed = computed(() => state.count + externalVar) externalVar = 1 // badComputed不会更新,因为externalVar不是响应式的
要把externalVar用ref包起来,改成响应式数据。
reactive和ref有啥关系?和computed结合怎么玩?
ref和reactive都是创建响应式数据的工具,但分工不同:
ref:更灵活,能包基本类型(如ref(0)),也能包对象(内部会自动用reactive处理对象),访问时要通过.value(模板里会自动解包)。reactive:只包对象/数组,访问时不用.value,但不能包基本类型(包了也没用)。
两者和computed结合超灵活——computed能同时依赖ref和reactive的数据:
import { ref, reactive, computed } from 'vue'
const count = ref(0) // 基本类型用ref
const user = reactive({ name: 'Jack' }) // 对象用reactive
// computed同时依赖ref和reactive的数据
const greeting = computed(() => `Hi ${user.name}, count is ${count.value}`)
// 修改数据,greeting会自动更新
count.value++
user.name = 'Rose'
Vue3的computed和Vue2的计算属性有啥不一样?
用过Vue2的同学会发现,Vue3的computed更灵活,底层也有变化:
使用方式更自由
Vue2里computed是选项式API里的配置项,只能写在computed里;Vue3的computed是组合式API的函数,能在setup、自定义 hooks 里随时用,复用性更强。
响应式原理升级
Vue2基于Object.defineProperty实现响应式,对数组索引、对象新增属性支持不好;Vue3用Proxy,能完美检测这些变化,所以Vue3的computed能响应更多场景(比如数组push、改对象新增的属性)。
自动解包与返回值
Vue3的computed返回的是ref对象,在模板里会自动解包(不用写.value);Vue2的计算属性返回的是普通值,没有这个特性。
更显式的setter控制
两者都支持setter,但Vue3的computed用函数式写法定义setter,更符合组合式API的风格,
// Vue3写法
const fullName = computed({
get() { ... },
set(val) { ... }
})
// Vue2写法
computed: {
fullName: {
get() { ... },
set(val) { ... }
}
}
通过这10个常见问题,你应该能把Vue3里computed和reactive的用法、原理、坑点摸透,实际项目里,reactive管对象级响应,computed管派生值缓存”,再结合场景灵活搭配,响应式逻辑就能写得既高效又好维护~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


