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

Vue3里computed和reactive咋用?常见疑问一次说清

terry 9小时前 阅读数 71 #SEO

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 }  

如果在模板里多次用 fullNamecomputed 只会在 firstlast 变化时重新计算一次;但 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(只读)”,不能直接改数据——否则会导致依赖关系混乱,甚至无限循环更新,但如果业务需要“通过计算属性修改数据”,可以给computedsetter

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)  

拆分响应式数据结构

如果嵌套太深,不妨把深层数据单独用reactiveref包起来,比如把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不是响应式的  

要把externalVarref包起来,改成响应式数据。

reactive和ref有啥关系?和computed结合怎么玩?

refreactive都是创建响应式数据的工具,但分工不同:

  • ref:更灵活,能包基本类型(如ref(0)),也能包对象(内部会自动用reactive处理对象),访问时要通过.value(模板里会自动解包)。
  • reactive:只包对象/数组,访问时不用.value,但不能包基本类型(包了也没用)。

两者和computed结合超灵活——computed能同时依赖refreactive的数据:

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里computedreactive的用法、原理、坑点摸透,实际项目里,reactive管对象级响应,computed管派生值缓存”,再结合场景灵活搭配,响应式逻辑就能写得既高效又好维护~

版权声明

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

热门