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

Vue3 setup 里 computed 的 get 和 set 咋用?看这篇就够啦!

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

Vue3 setup 中,computed 为啥要有 get 和 set?

得先从 computed 的基础逻辑 说起,在 Vue3 的 setup 语法里,普通 computed 长这样:

import { ref, computed } from 'vue'
const count = ref(1)
const double = computed(() => count.value * 2)

这时候 double只读 的——只能用 double.value 读取计算结果,要是尝试赋值(double.value = 10),Vue 会直接报错“计算属性是只读的”。

getset 是干啥的?简单说:

  • get 负责“读”的时候怎么计算结果(比如把多个响应式数据拼起来);
  • set 负责“写”的时候怎么处理新值(比如把新值拆分成多个响应式数据的更新)。

举个生活例子:做用户“全名”功能,姓(lastName)和名(firstName)是两个独立的输入框,全名(fullName)要自动拼接显示;但用户也能直接在“全名”输入框改内容,这时候得把新输入的内容拆成“姓”和“名”,再更新对应的输入框,这时候 computed 就得同时有 get)和 set)。

咋写带 get 和 set 的 computed?举个能跑的例子!

直接上代码(用 <script setup> 语法),看明白每一步干啥:

import { ref, computed } from 'vue'
// 定义“姓”和“名”的响应式数据
const lastName = ref('张')
const firstName = ref('三')
// 定义带 get 和 set 的 computed
const fullName = computed({
  // get:读取 fullName 时,返回“姓 + 名”
  get() {
    return lastName.value + firstName.value
  },
  // set:给 fullName 赋值时,拆分新值到“姓”和“名”
  set(newValue) {
    // 假设输入格式是“姓+名”(李四”拆成姓“李”、名“四”)
    const [newLast, newFirst] = newValue.split('') 
    lastName.value = newLast
    firstName.value = newFirst
  }
})
// 测试逻辑
console.log(fullName.value) // 初始输出:张三
// 给 fullName 赋值,触发 set
fullName.value = '李四' 
console.log(lastName.value) // 输出:李
console.log(firstName.value) // 输出:四

解释下关键逻辑:

  • get 里,把 lastNamefirstName 拼起来,所以读 fullName.value 时能拿到拼接结果;
  • set 里,接收新值 newValue(李四”),拆分后更新 lastNamefirstName——这一步必须修改响应式数据,否则 computed 下次读取时不会更新(后面会讲坑)。

实际开发中,哪些场景必须用带 get/set 的 computed?

光看例子不够,得结合真实需求理解,这 3 类场景最常见:

场景 1:表单的“双向计算”需求

比如做日期选择组件,后端存的是时间戳timestamp),但前端要显示格式化字符串(2024-10-01”),这时候:

  • get 负责把时间戳转成字符串显示;
  • set 负责把用户输入的字符串转成时间戳,再存回响应式数据。

代码逻辑:

import { ref, computed } from 'vue'
import { formatDate, parseDate } from './date-utils' // 假设的工具函数
const timestamp = ref(Date.now())
const dateStr = computed({
  get() {
    return formatDate(timestamp.value) // 时间戳 → 字符串
  },
  set(val) {
    timestamp.value = parseDate(val) // 字符串 → 时间戳
  }
})

场景 2:购物车的“数量-总价”联动

商品单价(price)固定,数量(num)和总价(total)要双向联动:

  • 用户改数量,总价自动变(get 负责 num * price);
  • 用户改总价(比如促销调整),数量自动变(set 负责 total / price)。

代码逻辑:

const price = ref(100) // 单价
const num = ref(2)     // 数量
const total = computed({
  get() {
    return num.value * price.value
  },
  set(newTotal) {
    num.value = newTotal / price.value
  }
})

场景 3:主题切换的“双状态”同步

比如用 isDarkMode(布尔值,true 代表深色)控制主题,但 UI 上要显示“dark/light”字符串(displayMode),这时候:

  • get 负责把布尔值转成字符串(isDarkMode ? 'dark' : 'light');
  • set 负责把字符串转成布尔值(val === 'dark')。

代码逻辑:

const isDarkMode = ref(false)
const displayMode = computed({
  get() {
    return isDarkMode.value ? 'dark' : 'light'
  },
  set(val) {
    isDarkMode.value = val === 'dark'
  }
})

只用 get 的 computed 和带 set 的,区别到底是啥?

核心区别就两点:

能否“赋值”

  • 只有 get 的 computed:只读,赋值会报错(double.value = 10 会触发“计算属性是只读的”警告);
  • set 的 computed:可写,赋值时会执行 set 里的逻辑。

setter 必须“更新依赖”

set 的 computed,set 里必须修改用来计算 get 的响应式数据,否则,computed 下次读取时不会更新。

举个反面教材(错误示范):

const badFullName = computed({
  get() { return lastName.value + firstName.value },
  set(newValue) {
    console.log('赋值了,但没改依赖') // 只打日志,没更新 lastName/firstName
  }
})
badFullName.value = '王五' 
console.log(lastName.value, firstName.value) // 还是原来的“张”“三”
console.log(badFullName.value) // 还是“张三”(因为依赖没变化)

写带 get/set 的 computed 时,容易踩哪些坑?怎么避?

踩过这些坑,才懂啥叫“纸上得来终觉浅”……

坑 1:setter 里没更新依赖,导致 computed 不更新

比如前面的反面教材,set 里只打日志没改 lastName/firstName解决方法set 必须修改参与 get 计算的响应式数据。

坑 2:setter 里出现“循环赋值”

const loopFullName = computed({
  get() { return lastName.value + firstName.value },
  set(newValue) {
    loopFullName.value = newValue // 直接赋值给自己,无限循环!
  }
})

一赋值就会栈溢出报错。解决方法:永远只修改依赖的响应式数据(lastName/firstName),别碰 computed 自身。

坑 3:TypeScript 下类型不匹配

如果用 TS,get 的返回类型和 set 的参数类型必须一致。get 返回 stringset 的参数也得是 string,否则 TS 会报错。

坑 4:<script setup> 里误用 this

<script setup> 语法中,不能用 thisget/set 里要直接访问 ref 变量(lastName.value),而不是 this.lastName,如果是选项式 API(非 setup),才用 this.lastName

再给个复杂例子:结合组件通信咋玩?

实际开发中,组件间传值也常用 computed 的 get/set,比如父组件控制“是否禁用”,子组件用按钮文字(“启用/禁用”)显示状态,同时点击按钮要切换状态。

父组件

<template>
  <Child :isDisabled="isDisabled" @update:isDisabled="isDisabled = $event" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const isDisabled = ref(true)
</script>

子组件

<template>
  <button @click="buttonText = buttonText === '禁用' ? '启用' : '禁用'">
    {{ buttonText }}
  </button>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps(['isDisabled'])
const emit = defineEmits(['update:isDisabled'])
const buttonText = computed({
  get() {
    // 根据父组件传的 isDisabled,返回按钮文字
    return props.isDisabled ? '启用' : '禁用'
  },
  set(newText) {
    // 点击后,把文字转成布尔值,emit 给父组件
    const newState = newText === '启用'
    emit('update:isDisabled', newState)
  }
})
</script>

这里子组件的 buttonTextget 负责根据父组件的 isDisabled 显示文字,set 负责点击后把文字转成状态并通知父组件,既实现了“计算显示”,又实现了“反向修改”,是 get/set 的典型实战场景。

总结一下核心知识点

  • get 的作用:计算属性“读”的时候,怎么整合依赖数据;
  • set 的作用:计算属性“写”的时候,怎么拆分新值、更新依赖数据;
  • 场景:表单双向计算、购物车联动、主题切换、组件通信等需要“双向交互”的场景;
  • 避坑:set 必须更新依赖、不能循环赋值、TS 类型要一致、setup 不用 this。

把这些逻辑吃透,再遇到“计算属性需要赋值”的需求,就知道咋下手啦~

版权声明

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

热门