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

Vue3里computed的getter和setter咋用?这些细节得搞懂!

terry 10小时前 阅读数 66 #SEO

很多刚学Vue3的同学,对computed里的getter和setter总是一知半解:默认只写个函数是getter?加setter有啥用?和methods到底咋选?今天咱们把这些问题拆碎了讲明白,还结合实际场景举例子~

computed里的getter,到底负责啥?

先想清楚:计算属性的本质是“基于其他响应式数据,动态生成新值”,而getter就是实现这个逻辑的“默认配置”——你只写一个函数的时候,Vue自动把它当成getter。

举个最简单的例子:

import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
// 这里的computed只传了一个函数,这个函数就是getter
const fullName = computed(() => `${firstName.value}${lastName.value}`)

getter里做了这几件事:

  1. 依赖收集:Vue会自动跟踪firstNamelastName这两个响应式数据,只要它们的value变化,fullName就会重新计算。
  2. 结果缓存:如果firstNamelastName都没变化,哪怕你在模板里多次用{{ fullName }},Vue也不会重复执行getter函数,直接用上次的结果——这也是computed和methods最大的性能差异(后面会细讲)。

你可以把getter理解成“自动更新的动态变量”:它不是固定值,而是随依赖变化自动刷新的“计算结果”。

啥时候得给computed加setter?

默认情况下,computed只有getter,是只读的,如果直接给computed赋值,比如fullName.value = '李四',Vue会报错“Write operation failed: computed value is readonly”。

但实际开发中,经常需要“通过修改计算属性,反向更新它的依赖项”——这时候就得给computed加setter,让它变成可写的。

还是拿“全名”举例子,这次需求是:用户在输入框里输入“李-四”,自动拆分姓和名,更新firstNamelastName,这时候setter就派上用场了:

const firstName = ref('张')
const lastName = ref('三')
const fullName = computed({
  // getter:负责把姓和名拼成全名
  get() {
    return `${firstName.value}${lastName.value}`
  },
  // setter:负责把全名拆分成姓和名
  set(newValue) {
    // 假设用户输入格式是“姓-名”,李-四”
    const [f, l] = newValue.split('-')
    firstName.value = f
    lastName.value = l
  }
})
// 模拟用户输入后赋值
fullName.value = '李-四' 
// 此时firstName变成'李',lastName变成'四',fullName的getter也会自动更新为'李四'

场景延伸一下:比如表单里有“手机号(带空格格式)”和“原始手机号(纯数字)”两个字段,用户输入带空格的手机号时,你需要自动去掉空格存到原始字段里——这时候computed的setter负责“处理输入格式”,getter负责“展示带格式的内容”,特别顺手。

getter和setter结合,实际开发哪些场景能用上?

除了“姓名拆分”“手机号格式化”,还有这些常见场景:

表单数据的“显示 vs 存储”分离

比如后台返回的生日是时间戳(birthTimestamp),但界面要显示“YYYY - MM - DD”格式,这时候:

  • getter:把时间戳转成日期字符串展示;
  • setter:用户修改日期字符串时,转成时间戳存回birthTimestamp

代码大概长这样:

const birthTimestamp = ref(1672531200000) // 假设是2023 - 01 - 01的时间戳
const birthDisplay = computed({
  get() {
    return dayjs(birthTimestamp.value).format('YYYY - MM - DD')
  },
  set(val) {
    birthTimestamp.value = dayjs(val).valueOf() // 转成时间戳
  }
})

关联数据的双向同步

比如购物车中,“商品数量”和“商品总价”是关联的(总价 = 数量 × 单价),如果业务需要“修改总价时反向计算数量”(比如促销场景下的批量改价),就可以用computed的setter:

const price = ref(100) // 单价
const count = ref(2) // 数量
const total = computed({
  get() {
    return price.value * count.value
  },
  set(newTotal) {
    count.value = Math.floor(newTotal / price.value) // 假设只保留整数数量
  }
})
// 模拟修改总价
total.value = 500 
// 此时count会变成5(因为500 / 100 = 5)

复杂状态的“组合与拆分”

比如用户信息有user = { name: '张三', age: 18 },但界面有个输入框要同时改“姓名”和“年龄”(比如格式是“张三|18”),这时候computed的getter负责拼接,setter负责拆分:

const user = reactive({ name: '张三', age: 18 })
const userDisplay = computed({
  get() {
    return `${user.name}|${user.age}`
  },
  set(val) {
    const [name, age] = val.split('|')
    user.name = name
    user.age = +age // 转成数字
  }
})

computed的getter和methods,性能差异有多大?

很多同学疑惑:“既然getter能做的事,methods也能做,为啥非得用computed?” 核心差异就在“缓存机制”上。

用一段代码对比感受下:

const list = ref([1, 2, 3, 4, 5, 6, 7, 8, 9])
// computed的getter:只在依赖变化时重新计算
const evenList = computed(() => {
  console.log('computed执行了')
  return list.value.filter(num => num % 2 === 0)
})
// methods:每次调用都执行
function getEvenList() {
  console.log('methods执行了')
  return list.value.filter(num => num % 2 === 0)
}

然后在模板里渲染:

<template>
  <!-- 情况1:用computed -->
  <div>{{ evenList }}</div>
  <div>{{ evenList }}</div>
  <!-- 情况2:用methods -->
  <div>{{ getEvenList() }}</div>
  <div>{{ getEvenList() }}</div>
</template>

控制台输出会是这样:

  • computed只在list变化时执行1次,不管模板里用多少次evenList
  • methods每次渲染都会执行,哪怕list没变化,模板里用几次就执行几次。

如果list是几百条数据,或者在循环渲染(比如v - for里调用方法),methods的重复执行会让性能雪崩,而computed的缓存能极大减少计算次数——这也是Vue官方推荐“能用computed就不用methods”的原因(事件处理函数还是得用methods)。

用computed的setter,容易掉哪些“坑”里?

虽然setter很好用,但稍不注意就会踩坑,这几个常见问题得避着点:

忘记写setter,赋值时报错

前面说过,默认computed是只读的,如果代码里写了fullName.value = '李四',但没给fullName加setter,Vue会直接报错。

解决方法:只要需要给computed赋值,必须显式写setter(哪怕setter里暂时没逻辑,也得写个空函数兜底)。

setter里的依赖不是响应式的

比如你想在setter里修改一个普通变量,而不是ref/reactive包裹的响应式数据:

let firstName = '张' // 普通变量,不是响应式
const fullName = computed({
  get() { return firstName + lastName.value },
  set(newValue) { 
    firstName = newValue.split('-')[0] // 修改普通变量,界面不更新
  }
})

这时候哪怕firstName变了,fullName的getter也不会重新计算,因为Vue的响应式系统“盯不住”普通变量。

解决方法:所有在computed里用的变量,必须用ref或reactive包装成响应式

setter逻辑太复杂,引发意外副作用

比如在setter里做异步请求、修改完全不相关的状态,会让代码逻辑变得混乱,后续维护难度陡增。

举个反面例子:

const fullName = computed({
  set(newValue) {
    // 错误示范:setter里做异步 + 改其他状态
    axios.post('/updateName', { name: newValue }).then(() => {
      otherState.value = 'xxx' // 改了不相关的状态
    })
  }
})

这种写法会让“修改fullName”的逻辑变得不可预测。

解决方法:setter只负责“反向更新依赖项”,复杂逻辑拆到单独的methods或Vuex/Pinia的action里

最后补个小知识点:computed和watch咋选?

虽然这篇讲的是getter/setter,但很多同学会混淆computed和watch,这里顺道对比下:

  • computed:专注“同步计算派生值”,依赖变化时自动更新结果,强调“数据 → 数据”的转换;
  • watch:专注“监听数据变化后执行副作用”(比如异步请求、DOM操作、复杂逻辑),强调“数据 → 动作”的响应。

举个例子:计算“全名”用computed;监听“路由变化”后加载页面数据,用watch。

Vue3的computed里,getter负责“基于依赖生成值”,setter负责“反向更新依赖”,理解它们的分工和场景,能让你写出更简洁、高效的响应式逻辑~ 下次写表单联动、数据格式化的时候,别再绕弯路啦!

(如果还有疑问,评论区留言,咱们再唠~)

版权声明

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

热门