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

Vue3里computed的setter和getter到底咋玩?一篇文章给你讲透!

terry 2小时前 阅读数 5 #SEO
文章标签 Vue3 computed

很多刚用Vue3的同学,对computed的setter和getter总是一知半解:默认只有getter?setter咋写?啥时候必须用?今天用问答形式,从基础到实战一次性讲清楚。

computed默认只有getter?先搞懂基础逻辑

在Vue3里写computed,最常见的是函数式写法

const fullName = computed(() => `${firstName.value} ${lastName.value}`)  

这种写法里,computed只负责“读”——也就是getter的工作:根据依赖的响应式数据(比如firstNamelastName)计算结果,这时候要是直接给fullName赋值(比如fullName.value = 'xxx'),控制台会报错,因为默认只有“读权限”,没有“写逻辑”。

那什么时候能“写”?得把computed改成对象形式,主动配置getset

const fullName = computed({
  get() {
    return `${firstName.value} ${lastName.value}`
  },
  set(newValue) {
    const [first, last] = newValue.split(' ')
    firstName.value = first
    lastName.value = last
  }
})

这样配置后,执行fullName.value = 'John Doe'时,会触发setter里的逻辑,把firstNamelastName分别修改。

computed默认只开“读权限”(getter),想要“写权限”(setter),必须用对象语法主动配置。

给computed加setter,语法咋写才对?

给computed加setter,核心是把computed的参数从“函数”改成“对象”,对象里必须包含get(负责计算返回值),可选包含set(负责处理赋值逻辑),步骤拆解:

定义依赖的响应式数据

refreactive包装数据(必须是响应式的,否则修改后不会触发界面更新):

import { ref, computed } from 'vue'  
const firstName = ref('')  
const lastName = ref('')  

配置computed的对象格式

对象里的getset都要接收响应式数据的变化:

const fullName = computed({  
  get() { // getter:计算并返回结果  
    return `${firstName.value} ${lastName.value}`  
  },  
  set(newValue) { // setter:接收赋值时的新值,处理逻辑  
    const parts = newValue.split(' ')  
    if (parts.length >= 2) { // 简单校验,避免拆分出错  
      firstName.value = parts[0]  
      lastName.value = parts.slice(1).join(' ') // 处理中间名等情况  
    }  
  }  
})  

页面中使用(双向绑定为例)

读的时候直接用fullName.value,写的时候可以配合v-model

<template>  
  <input v-model="fullName.value" placeholder="输入全名" />  
</template>  

当用户在输入框输入内容,会触发setter,修改firstNamelastName,进而让所有依赖这两个变量的地方(比如页面上的其他展示)自动更新。

注意:setter里必须操作响应式数据(如ref/reactive包装的变量),否则修改普通变量不会触发Vue的响应式更新,等于白忙活。

啥场景下非得用setter不可?举几个真实例子

别觉得setter是“花里胡哨”的功能,实际项目里这些场景离了它还真不行:

场景1:表单双向绑定+复杂处理

比如用户资料页,要做一个“全名”输入框(包含姓+名),但后端接口要求姓和名分开存储,这时候用computed的setter,输入框v-modelfullName,setter负责拆分值到姓和名的响应式变量,getter负责拼接展示,不用setter的话,得自己写input事件处理函数,代码会很零散。

场景2:数据格式双向转换

后端存的是“时间戳(毫秒)”,前端要显示“YYYY - MM - DD”格式,这时候computed的getter把时间戳转成日期字符串,setter把用户输入的日期字符串转回时间戳存到响应式数据里,这样输入输出的格式转换全交给computed,组件逻辑更干净。

场景3:多变量的联动修改

比如购物车的“商品数量+价格”联动,用户改总价时,setter根据单价算出数量(或者反过来),这种多变量依赖的情况,用computed的setter能把逻辑内聚,不用在各个input的事件里重复写计算逻辑。

举个具体例子(场景2:时间格式转换)

import { ref, computed } from 'vue'  
import dayjs from 'dayjs'  
const timestamp = ref(Date.now()) // 后端返回的时间戳  
const dateStr = computed({  
  get() {  
    return dayjs(timestamp.value).format('YYYY - MM - DD') // 转成字符串展示  
  },  
  set(newStr) {  
    const newTs = dayjs(newStr).valueOf() // 转成时间戳  
    timestamp.value = newTs // 存回响应式数据  
  }  
})  

页面中用<input v-model="dateStr.value" />,用户输入日期字符串时,setter自动转成时间戳存起来,其他依赖timestamp的组件(比如日历、时间显示)会自动更新——这就是setter的价值:把“展示格式”和“存储格式”的转换逻辑封装在computed里,外层组件不用关心细节

getter和setter啥时候执行?理清依赖逻辑

很多人搞不清computed的getter和setter的执行时机,其实和Vue3的响应式依赖系统强相关:

getter的执行时机

  1. 首次访问computed属性时:比如页面第一次渲染{{ fullName }},或JS里第一次读fullName.value,getter会执行,计算并缓存结果(Vue3的computed有缓存,依赖不变时直接用缓存)。
  2. 依赖的响应式数据变化时:比如firstNamelastName变了,下次访问fullName.value时,getter会重新执行,算出新结果。

setter的执行时机

只有主动给computed属性赋值时才会触发,比如fullName.value = '新值',这时候不管依赖的变量有没有变化,都会执行setter里的逻辑。

举个例子理解

  • 初始firstName='Alice'lastName='Wang' → get()执行,返回'Alice Wang'
  • firstName'Bob' → 此时getter不会立刻执行,只有当再次访问fullName.value,才会重新计算成'Bob Wang'
  • 执行fullName.value = 'Charlie Lee' → 触发setter,把firstName改成'Charlie'lastName改成'Lee' → 这时候firstNamelastName的变化,会让它们的依赖(比如fullName的getter)标记为“需要重新计算”,但getter不会立刻执行,等下次访问fullName.value时才会重新运行。

getter是“懒执行+缓存”,依赖变了也不立刻跑,等被访问时才更新;setter是“主动触发”,只有赋值时才执行,执行后修改响应式数据,进而让依赖它的getter后续更新。

setter背后的响应式原理,Vue3是咋实现的?

想彻底搞懂setter,得扒开Vue3响应式的底层逻辑,简单说,computed本身是个带缓存的effect,而setter的作用是触发响应式数据的更新,进而让依赖重新计算。

computed的getter是个effect

Vue3里,computed的getter会被包装成一个effect函数(可以理解为“依赖追踪的函数”),这个effect有两个特点:

  • 懒执行:默认不立刻执行,只有被访问时才会运行。
  • 带缓存:依赖不变时,直接返回缓存结果,避免重复计算。

setter里改响应式数据,触发依赖更新

当你给computed属性赋值(触发setter),setter里的代码会修改其他响应式数据(比如firstName.value = xxx),这时候,Vue的响应式系统会检测到firstName的变化(因为firstNameref/reactive包装的响应式数据),然后找到所有依赖firstName的effect(比如fullName的getter对应的effect),把这些effect标记为“需要重新运行”。

下次访问computed时,getter重新执行

因为getter是懒执行的,即便firstName变了,getter也不会立刻运行,而是等下次有人访问fullName.value时,才会重新计算最新的结果。

流程梳理
用户执行fullName.value = '新值' → 触发setter → setter里改firstName.value → Vue检测到firstName的value变化 → 找到依赖firstName的effect(即fullName的getter对应的effect)→ 把这个effect标记为dirty(脏了,需要重新计算)→ 当其他地方访问fullName.value时 → 发现effect是dirty的 → 执行getter重新计算 → 返回新结果。

所以setter的本质是:通过修改响应式数据,间接触发computed getter的重新计算,它不直接处理缓存或依赖,而是借助Vue的响应式系统,把“写computed”的操作转化为“改响应式数据”的操作,从而联动整个依赖链更新。

项目里怎么用setter+getter让代码更优雅?实战案例来了

光讲理论不够,上一个真实项目场景,看看setter+getter怎么让代码从“臃肿”变“丝滑”。

需求:用户信息编辑页,联动显示和修改

页面要显示用户的「姓名(姓+名)」「邮箱」「手机号」,姓名」要做一个输入框,输入全名(张三 四”)时,自动拆分存到firstNamelastName两个字段(后端接口需要分开存),其他字段直接双向绑定。

不用setter的写法(反面教材)

如果不用computed的setter,得引入中间变量和多个watch,代码冗余且易出错:

<template>  
  <input v-model="fullNameInput" placeholder="输入全名" />  
  <input v-model="email" placeholder="邮箱" />  
  <input v-model="phone" placeholder="手机号" />  
</template>  
<script setup>  
import { ref, reactive, watch, computed } from 'vue'  
const user = reactive({  
  firstName: '',  
  lastName: '',  
  email: '',  
  phone: ''  
})  
// 中间变量:全名输入框的值  
const fullNameInput = ref('')  
// 初始化:把firstName和lastName拼成fullNameInput  
watch([() => user.firstName, () => user.lastName], () => {  
  fullNameInput.value = `${user.firstName} ${user.lastName}`  
}, { immediate: true })  
// 监听fullNameInput变化,拆分到firstName和lastName  
watch(fullNameInput, (newVal) => {  
  const [first, last] = newVal.split(' ')  
  user.firstName = first || ''  
  user.lastName = last || ''  
})  
// 邮箱和手机的computed(直接双向绑定也可以,但用computed更灵活)  
const email = computed({  
  get() { return user.email },  
  set(val) { user.email = val }  
})  
const phone = computed({  
  get() { return user.phone },  
  set(val) { user.phone = val }  
})  
</script>  

问题:中间变量fullNameInput冗余,还要写两个watch同步数据,拆分逻辑没做校验(比如输入空值会报错),代码又多又乱。

用setter+getter的写法(优雅版)

fullName做成computed,用setter处理拆分逻辑,消除中间变量:

<template>  
  <input v-model="fullName.value" placeholder="输入全名" />  
  <input v-model="email.value" placeholder="邮箱" />  
  <input v-model="phone.value" placeholder="手机号" />  
</template>  
<script setup>  
import { reactive, computed } from 'vue'  
const user = reactive({  
  firstName: '',  
  lastName: '',  
  email: '',  
  phone: ''  
})  
// 全名的computed,带setter  
const fullName = computed({  
  get() {  
    return `${user.firstName} ${user.lastName}`.trim() // 去掉多余空格  
  },  
  set(newVal) {  
    const [first, ...rest] = newVal.split(' ') // 处理中间有多个空格的情况  
    user.firstName = first || ''  
    user.lastName = rest.join(' ') || '' // 剩下的部分作为lastName  
  }  
})  
// 邮箱和手机的computed(封装读写逻辑,也可直接v-model.user.email)  
const email = computed({  
  get() { return user.email },  
  set(val) { user.email = val }  
})  
const phone = computed({  
  get() { return user.phone },  
  set(val) { user.phone = val }  
})  
</script>  

对比之后的优势

  1. 消除中间变量:不用再搞fullNameInput这种冗余的ref,直接用fullName这个computed做双向绑定。
  2. 逻辑内聚:拆分全名的逻辑全放在fullName的setter里,初始化、用户输入的逻辑统一,还能加校验(比如处理空值、多空格)。
  3. 维护性高:以后要改拆分规则(比如支持中间名),只需要改setter里的代码,不用动其他地方。

掌握setter+getter,写出更优雅的Vue代码

Vue3里computed的setter和getter是一对“读写搭档”:

  • getter负责“读”:懒执行+缓存,根据依赖计算结果,性能更优。
  • setter负责“写”:主动赋值时执行自定义逻辑,修改响应式数据,联动依赖更新。

从语法到原理,再到实战场景,掌握这对搭档能让你在处理「双向转换」「联动修改」「表单复杂逻辑」时,写出更简洁、更内聚的代码,下次碰到“既展示又能改”的计算属性,别忘了解锁setter这个技能~

要是你还有疑问(setter里能调异步函数吗?”“多个computed互相依赖会不会有问题?”),评论区喊我,下次展开讲~

版权声明

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

热门