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

Vue3 Composition API里computed的get和set咋用?

terry 2小时前 阅读数 15 #SEO

很多刚接触Vue3 Composition API的同学,对计算属性的getset容易犯迷糊:明明Options API里的computed还挺熟,换成Composition API咋写读写逻辑?啥场景需要给computedset?写的时候又要注意啥?今天咱们一个个掰扯清楚。

Composition API的computed和Options API的computed有啥不同?

先回忆下Options API里的computed:在computed选项里定义对象,每个属性可以是仅get(函数形式),或者包含get和set的对象,比如处理全名拼接:

export default {
  data() { return { firstName: '', lastName: '' } },
  computed: {
    fullName: {
      get() { return this.firstName + this.lastName },
      set(val) { 
        this.firstName = val.slice(0, 1)
        this.lastName = val.slice(1)
      }
    }
  }
}

Composition API里,computed变成导入的函数,用法更“函数式”,核心区别是:

  • 写法上:得先import { computed } from 'vue',再在setup<script setup>里调用computed()创建计算属性。
  • 作用域上:Composition API的computed是局部变量(在setup里定义),和其他响应式数据、函数平级,更适合逻辑拆分(比如把计算属性逻辑抽到单独的composable里)。

举个Composition API的例子:

<script setup>
import { ref, computed } from 'vue'
const firstName = ref('')
const lastName = ref('')
// 用computed函数创建,传入{ get, set }对象
const fullName = computed({
  get() { return firstName.value + lastName.value },
  set(val) { 
    firstName.value = val.slice(0, 1)
    lastName.value = val.slice(1)
  }
})
</script>

简单说,功能逻辑没大变,但写法更灵活,和Composition API的“组合式”理念更契合。

啥时候需要给computed加set?

计算属性默认只有getter(只读),Vue会自动缓存计算结果,但如果需要让计算属性能被赋值(比如表单双向绑定、反向推导依赖值),就得加setter

举几个典型场景:

  • 表单联动:比如用户填“全名”输入框,要同步拆分到“姓”和“名”两个输入框;反之,改“姓”或“名”,全名也要自动更新,这时候全名作为计算属性,需要支持“写”操作(set)。
  • 反向推导依赖:比如购物车中“总价=单价×数量”,用户直接改总价时,要反向算出数量(假设单价不变),这时候总价作为计算属性,set里要处理数量的更新。
  • 复杂状态整合:多个子状态(如年、月、日)组合成一个“日期字符串”,改字符串时要拆分回子状态,这也需要setter

一句话:当你需要让“计算出来的值”能被主动修改,并反向影响它的依赖源时,就得给computedset

怎么写computed的get和set逻辑?

步骤很清晰,咱们结合例子一步步来(就用“全名拆分”的场景):

步骤1:准备响应式依赖

先用refreactive定义要依赖的响应式数据,比如姓和名:

import { ref } from 'vue'
const firstName = ref('') // 姓
const lastName = ref('')  // 名

步骤2:调用computed,传入{ get, set }

computed()创建计算属性,参数是一个对象,包含getset两个函数:

import { computed } from 'vue'
const fullName = computed({
  // 1. get函数:返回计算后的值(依赖firstName和lastName)
  get() {
    return firstName.value + lastName.value
  },
  // 2. set函数:接收“新值”,处理后更新依赖(firstName和lastName)
  set(newFullName) {
    // 这里写拆分逻辑(示例是简单拆分,实际要考虑格式验证)
    firstName.value = newFullName.slice(0, 1) // 假设姓1个字
    lastName.value = newFullName.slice(1)     // 剩下的是名
  }
})

步骤3:在模板中使用(双向绑定)

计算属性和普通响应式数据一样,能直接用v-model双向绑定:

<template>
  <input v-model="firstName" placeholder="姓" />
  <input v-model="lastName" placeholder="名" />
  <!-- 双向绑定fullName,改它会触发set -->
  <input v-model="fullName" placeholder="全名" />
</template>

关键点:

  • get必须访问响应式数据ref/reactive的属性),Vue才会自动追踪依赖,依赖变化时重新计算。
  • set必须主动修改响应式依赖(比如firstName.value),这样才能触发界面更新。

get和set里的响应式依赖咋处理?

这部分容易踩坑,得仔细看:

getter的依赖追踪

Vue的computed是“惰性求值+缓存”的:只有当getter被访问时,才会计算值;而且如果依赖(比如firstNamelastName)没变化,下次访问会直接读缓存。

getter里只要访问了响应式数据的.valueref)或属性(reactive,Vue就会自动把这些数据当作“依赖”,一旦依赖变化,下一次getter被调用时,就会重新计算。

比如前面的例子,get里用了firstName.valuelastName.value,所以当这两个值变化时,fullNamegetter会自动重新计算。

setter的依赖更新

setter的作用是接收新值,反向修改依赖,注意:

  • setter不能直接修改computed本身(比如fullName.value = newVal),否则会触发自己的setter,陷入无限循环!
  • 必须修改其他响应式数据(比如firstNamelastName),这样这些数据的变化会触发它们的依赖更新(包括fullNamegetter)。

举个错误示范(千万别这么写):

const fullName = computed({
  get() { ... },
  set(val) {
    fullName.value = val // 死循环!set里改自己,会再次触发set
  }
})

正确的做法是修改依赖源,比如前面的firstNamelastName

用computed带set时要避开哪些坑?

踩过这些坑,才算真正掌握:

循环依赖(无限递归)

只要setter里修改computed自身,就会无限触发setset里只改依赖源,别碰computed自己

拆分/处理逻辑不严谨

全名拆分”场景,如果用户输入“欧阳”(姓两个字),按之前的slice(0,1)逻辑,姓会变成“欧”,名变成“阳”,明显不符合预期。

解决办法:set里的逻辑要考虑边界情况,必要时加格式验证,比如拆分全名时,先判断是否有空格,按空格分割姓和名:

set(newFullName) {
  const [first, last] = newFullName.split(' ') // 假设用户按“姓 名”格式输入
  firstName.value = first || ''
  lastName.value = last || ''
}

响应式丢失(get里用了非响应式数据)

如果get里访问的是普通变量(不是ref/reactive),Vue没法追踪依赖,computed就不会更新。

错误例子:

let firstName = '张' // 普通变量,不是响应式
const lastName = ref('三')
const fullName = computed({
  get() { return firstName + lastName.value } // firstName非响应式,改它不会触发更新
})

解决:把普通变量用refreactive包装,确保get里访问的是响应式数据。

性能问题(set里逻辑太繁重)

如果set里要修改多个响应式数据,且这些数据还有很多依赖,可能导致界面频繁更新。

优化思路:

  • 尽量简化set里的逻辑,必要时用nextTick分批更新;
  • 对不需要实时同步的场景,考虑用watch代替computed(但computed更适合声明式逻辑)。

实际项目里computed get+set的典型场景有啥?

光说不练假把式,看几个真实项目里的例子:

场景1:表单双向绑定的复杂处理(日期拆分)

需求:用户可以通过“年-月-日”的输入框改日期,也能通过单独的年、月、日输入框改,两边实时同步。

代码实现:

<script setup>
import { ref, computed } from 'vue'
const year = ref(2000)
const month = ref(1)
const day = ref(1)
const dateStr = computed({
  get() {
    return `${year.value}-${month.value}-${day.value}`
  },
  set(val) {
    // 拆分字符串为年、月、日
    const [y, m, d] = val.split('-')
    year.value = Number(y)
    month.value = Number(m)
    day.value = Number(d)
  }
})
</script>
<template>
  <input v-model="dateStr" placeholder="yyyy-mm-dd" />
  <input v-model.number="year" type="number" placeholder="年" />
  <input v-model.number="month" type="number" placeholder="月" />
  <input v-model.number="day" type="number" placeholder="日" />
</template>

这里dateStr作为计算属性,既能整合年、月、日,又能反向拆分,完美支持双向绑定。

场景2:购物车总价反向推导数量

需求:商品“总价=单价×数量”,用户改总价时,自动计算数量(假设单价不变)。

代码实现:

<script setup>
import { ref, computed } from 'vue'
const price = ref(10) // 单价(假设固定)
const quantity = ref(2) // 数量
const total = computed({
  get() {
    return price.value * quantity.value
  },
  set(newTotal) {
    quantity.value = newTotal / price.value // 反向算数量
  }
})
</script>
<template>
  <p>单价:{{ price }}</p>
  <input v-model.number="quantity" type="number" placeholder="数量" />
  <input v-model.number="total" type="number" placeholder="总价" />
</template>

用户改total时,quantity会自动更新;改quantity时,total也会自动更新,逻辑非常简洁。

场景3:权限状态的整合与反向设置

需求:系统里“是否管理员”由多个子权限(如canManageUsercanManageOrder)决定;设置isAdmin时,要批量设置子权限。

代码实现(简化版):

<script setup>
import { reactive, computed } from 'vue'
const permissions = reactive({
  canManageUser: false,
  canManageOrder: false,
  canManageGoods: false
})
const isAdmin = computed({
  get() {
    // 假设管理员需要同时有三个权限
    return (
      permissions.canManageUser && 
      permissions.canManageOrder && 
      permissions.canManageGoods
    )
  },
  set(isAdmin) {
    // 设置isAdmin时,批量更新子权限
    permissions.canManageUser = isAdmin
    permissions.canManageOrder = isAdmin
    permissions.canManageGoods = isAdmin
  }
})
</script>
<template>
  <label>
    <input type="checkbox" v-model="isAdmin" /> 是管理员
  </label>
  <!-- 子权限也能单独控制 -->
  <label>
    <input type="checkbox" v-model="permissions.canManageUser" /> 管理用户
  </label>
  <!-- 其他子权限同理 -->
</template>

这里isAdmin作为计算属性,整合了多个子权限;设置isAdmin时,又能反向批量设置子权限,逻辑很清晰。

总结一下

Vue3 Composition API的computed,在getset的用法上,核心逻辑和Options API一脉相承,但写法更函数式、更灵活,记住这几点:

  • 只有需要“写回依赖源”时,才给computedset
  • get里访问响应式数据,Vue自动追踪依赖;
  • set里修改其他响应式数据,别碰computed自己;
  • 实际场景中,表单联动、反向推导、状态整合都是典型用武之地。

多写几个例子,踩踩坑(比如循环依赖、拆分逻辑不严谨),自然就掌握了~

如果还有疑问,computedwatch咋选?”“reactive对象在computed里咋处理?”,评论区喊我,下次再展开唠~

版权声明

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

热门