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

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

terry 2天前 阅读数 337 #SEO
文章标签 Vue3computed readonly

不少刚学Vue3的同学,对computed和readonly这两个API容易犯迷糊:它们到底是干啥的?啥时候用?和其他API有啥区别?今天用问答形式把常见疑问掰碎了讲明白。

啥是Vue3的computed?和methods有啥不一样?

computed叫“计算属性”,是基于响应式数据自动计算结果的工具,核心特点是依赖追踪+缓存,举个例子:购物车页面要显示商品总价,总价 = 每个商品的“单价×数量”之和,这时用computed,Vue会自动跟踪“商品列表、单个商品的单价/数量”这些依赖,只有依赖变化时,总价才会重新计算;如果依赖没变化,直接用上次计算的缓存结果。

那和methods有啥区别?假设把计算总价的逻辑放到methods里,写成calcTotal()函数,这时每次组件渲染(哪怕商品数据没变化),calcTotal()都会重新执行一遍,如果商品很多、计算逻辑复杂,重复执行就会拖慢页面,而computed靠缓存省性能,依赖不变时不重复计算。

简单说:需要“基于响应式数据做缓存计算”选computed;纯函数调用、不需要缓存选methods。

computed里的get和set咋用?实际场景举个例子?

默认情况下,computed是“只读”的(只有getter),但也能配置成“可读写”(同时有get和set),getter负责“读取时返回计算结果”,setter负责“修改时更新依赖数据”。

举个实际场景:用户资料里的“全名”由firstName和lastName拼接而成,显示时要全名,修改时要把全名拆分成firstName和lastName,代码这么写:

import { ref, computed } from 'vue'
const firstName = ref('')
const lastName = ref('')
const fullName = computed({
  // 读取全名时,拼接firstName和lastName
  get() { 
    return `${firstName.value} ${lastName.value}` 
  },
  // 修改全名时,拆分赋值给firstName和lastName
  set(newVal) { 
    const [first, last] = newVal.split(' ')
    firstName.value = first
    lastName.value = last
  }
})

这时在模板里用fullName,既能像普通响应式数据一样显示,也能直接赋值修改(比如fullName = 'Ada Lovelace',setter会自动拆分更新firstName和lastName)。

为啥要用computed的缓存?不用缓存会咋样?

缓存是computed的性能王牌,假设做一个“商品搜索过滤”功能:用户输入关键词,列表只显示包含关键词的商品,如果不用computed,把过滤逻辑放methods里,每次页面渲染、甚至用户滚动页面(触发重渲染),过滤函数都会重新执行——哪怕关键词没变化、商品列表也没变化,要是商品有几千条,重复计算会让页面变卡。

而computed的缓存逻辑是:只有依赖(比如关键词、商品列表)变化时,才重新计算,依赖不变时,直接返回上次的结果,跳过重复计算,这对“依赖少变、计算逻辑重”的场景(比如大数据量过滤、复杂公式计算)特别友好。

要是完全不需要缓存,每次调用必须 fresh 计算”,那反而该用methods,所以选computed还是methods,核心看“需不需要缓存+是否基于响应式依赖”。

啥是readonly?它和reactive、ref的只读有啥区别?

readonly是Vue3用来“锁死响应式数据”的API——把响应式对象变成只读的,任何修改操作(赋值、添加属性、删除属性等)都会被拦截并报错,举个例子:

import { reactive, readonly } from 'vue'
const config = reactive({ 
  apiBaseUrl: 'https://xxx.com', 
  theme: 'dark' 
})
const readonlyConfig = readonly(config)
readonlyConfig.apiBaseUrl = 'https://yyy.com' // 直接报错!

那和reactive/ref的“只读”有啥区别?

  • reactive/ref创建的是可修改的响应式对象,你可以随意赋值;
  • readonly是强制只读的响应式对象,修改会报错,但仍保持“响应式”(如果原对象是响应式的,readonly后的对象依赖变化时,仍能触发视图更新)。

简单说:reactive/ref是“可写的响应式”,readonly是“只读的响应式”。

readonly在项目里咋用?能举个团队协作的例子不?

readonly最实用的场景是保护“不该被修改”的数据,避免团队协作时误操作,举几个典型场景:

场景1:保护公共配置

团队开发时,全局配置文件(比如主题色、接口前缀、路由白名单)一旦确定,不能被业务代码随意修改,用readonly包一层:

// config.js
import { readonly } from 'vue'
export const globalConfig = readonly({
  primaryColor: '#42b983',
  apiPrefix: '/api/v1',
  layout: 'side-menu'
})

其他开发者导入globalConfig后,如果试图修改globalConfig.primaryColor = 'red',Vue会直接报错,从源头避免“配置被误改导致全局样式/功能崩掉”。

场景2:保护用户权限数据

用户登录后,角色、权限信息是全局状态(比如用Pinia存储),这些数据只能由登录逻辑修改,业务组件只能“读”不能“写”,用readonly包装:

// store/user.js
import { defineStore, readonly } from 'pinia'
export const useUserStore = defineStore('user', () => {
  const roles = ref(['admin'])
  // 对外暴露只读的roles,防止业务组件误改
  return { roles: readonly(roles) }
})

业务组件里userStore.roles.push('guest')会报错,保证权限系统的稳定性。

computed和readonly能结合起来用不?有啥典型场景?

必须能!结合后能解决“基于只读数据做动态计算”的需求,举个电商场景:运费计算

运费规则(首重、首重价格、续重价格)是公司定下的固定规则,不能随便改→用readonly包起来;商品重量是动态变化的→用ref;最终运费要根据“只读的规则+动态的重量”计算→用computed,代码如下:

import { ref, computed, readonly } from 'vue'
// 运费规则:只读,防止被修改
const freightRules = readonly({
  firstWeight: 1,  // 首重1kg
  firstPrice: 10,  // 首重价格(1kg内10元)
  extraPrice: 5    // 续重每kg5元
})
// 商品重量:动态变化(比如用户选择商品后更新)
const productWeight = ref(3) // 假设商品重3kg
// 计算运费:依赖freightRules和productWeight
const freight = computed(() => {
  if (productWeight.value <= freightRules.firstWeight) {
    return freightRules.firstPrice
  } else {
    const extraWeight = productWeight.value - freightRules.firstWeight
    return freightRules.firstPrice + extraWeight * freightRules.extraPrice
  }
})

这样一来:

  • 运费规则被readonly保护,不会被业务代码误改;
  • 商品重量变化时,computed自动重新计算运费;
  • 规则不变时,computed复用缓存结果,省性能。

用computed常见的坑是啥?咋避坑?

虽然computed好用,但新手容易踩这些坑,提前避坑能少掉头发:

坑1:依赖了“非响应式数据”,导致computed不更新

比如用普通变量当依赖,而不是ref/reactive包裹的:

let count = 1 // 普通变量,不是响应式的
const double = computed(() => count * 2)
count = 2 // double不会更新!因为Vue没追踪到count的变化

解决:确保computed的依赖是ref/reactive包裹的响应式数据,上面的代码改成const count = ref(1)就好。

坑2:在computed里写异步逻辑

computed是同步计算属性,不能返回Promise,也不能在里面用async/await,比如想“异步获取数据后计算”,这么写会报错:

const asyncComputed = computed(async () => {
  const res = await fetchData()
  return res.data
})

解决:异步逻辑改用watch或者组合式API(比如watchEffect)处理。

坑3:过度封装,把复杂逻辑全塞computed里

比如把“调用接口、数据过滤、格式转换”全堆在computed里,代码又长又难维护。解决:拆分逻辑——接口请求放onMountedwatch里,复杂过滤/转换用methods封装,computed只做“依赖数据的简单计算”。

坑4:setter里没正确更新依赖,导致get/set不同步

比如前面“全名”的例子,如果setter里没正确拆分赋值:

set(newVal) {
  // 错误写法:没更新firstName和lastName
  fullName.value = newVal 
}

这会导致死循环(set里修改fullName,又触发get,无限循环)。解决:setter里必须修改computed依赖的响应式数据源(比如firstName和lastName),而不是直接改computed本身。

readonly的深层只读是啥意思?嵌套对象咋处理?

Vue3的readonly是深层只读——不仅顶层属性不能改,嵌套的对象、数组、甚至对象里的对象,也不能改,举个嵌套的例子:

const user = reactive({
  info: {
    name: '张三',
    address: {
      city: '北京'
    }
  }
})
const readonlyUser = readonly(user)
// 下面这行代码会直接报错!
readonlyUser.info.address.city = '上海'

如果业务场景需要“浅层只读”(只锁顶层,不锁嵌套),Vue3没有内置API,但可以自己实现(比如手动遍历第一层属性,用Proxy只拦截顶层set),但实际项目里,深层只读更安全——毕竟你很难预判团队成员会不会动嵌套数据,用深层只读能彻底避免误改。

最后再唠两句

computed和readonly看似是两个简单的API,实则是Vue3“响应式体系+工程化最佳实践”的体现:

  • computed帮我们高效处理“依赖驱动的缓存计算”,避免性能浪费;
  • readonly帮我们守护“不可变数据”,减少团队协作时的意外Bug。

理解它们的设计意图后,再结合实际场景(比如权限控制、配置保护、复杂计算)灵活运用,能让代码更健壮、性能更优,要是你还有其他疑问,评论区随时聊~

版权声明

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

热门