Vue 3中计算属性与方法的使用边界你理清了吗?

0 阅读14分钟

一、计算属性与方法的基本概念

在Vue 3中,**计算属性(Computed)方法(Methods)**都是处理逻辑的常用方式,但它们的设计初衷和工作机制完全不同。我们先从最基础的定义讲起:

1. 计算属性(Computed)

计算属性是基于响应式依赖的缓存值——它会根据你指定的“依赖项”(比如refreactive包裹的响应式数据)自动计算结果,并且只在依赖项变化时重新计算
官方文档中对计算属性的核心描述是:“Computed properties are cached based on their reactive dependencies.”(计算属性基于响应式依赖进行缓存)。

2. 方法(Methods)

方法是可重复执行的函数——每次调用方法(比如通过事件绑定、模板渲染或手动调用),函数都会重新执行一遍逻辑,没有任何缓存。
用官方的话说:“Methods are functions that can be called from templates or other methods. They don’t have caching.”(方法是可从模板或其他方法中调用的函数,没有缓存)。

二、计算属性与方法的核心区别

要真正掌握两者的使用场景,必须先搞懂它们的4个核心差异

维度计算属性(Computed)方法(Methods)
缓存机制依赖不变则复用上次结果每次调用都重新执行函数
调用方式像“属性”一样直接用(无括号)像“函数”一样调用(必须加括号)
依赖追踪自动追踪响应式依赖,依赖变化才更新不追踪依赖,调用时执行所有逻辑
性能影响依赖稳定时性能更优(避免重复计算)频繁调用可能导致性能问题(重复执行逻辑)

三、用示例看差异:缓存 vs 每次执行

我们用一个实时过滤待办列表的场景,对比两者的表现:

1. 计算属性实现(带缓存)

<template>
  <div>
    <input v-model="searchText" placeholder="搜索待办" />
    <ul>
      <!-- 像属性一样用,无括号 -->
      <li v-for="todo in filteredTodos" :key="todo.id">{{ todo.text }}</li>
    </ul>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

// 响应式待办列表
const todos = ref([
  { id: 1, text: '学Vue 3' },
  { id: 2, text: '写博客' },
  { id: 3, text: '做项目' }
])
// 响应式搜索关键词
const searchText = ref('')

// 计算属性:过滤待办列表
const filteredTodos = computed(() => {
  console.log('计算属性执行了!') // 仅依赖变化时打印
  return todos.value.filter(todo =>
    todo.text.toLowerCase().includes(searchText.value.toLowerCase())
  )
})
</script>

执行结果

  • 首次渲染时,filteredTodos计算一次(打印日志);
  • 当你修改searchText todos时,才会重新计算(再次打印);
  • 若反复点击同一搜索词(依赖不变),计算属性不会重复执行。

2. 方法实现(无缓存)

我们把上面的计算属性改成方法:

<template>
  <div>
    <input v-model="searchText" placeholder="搜索待办" />
    <ul>
      <!-- 像函数一样调用(必须加括号) -->
      <li v-for="todo in getFilteredTodos()" :key="todo.id">{{ todo.text }}</li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const todos = ref([/* 同前 */])
const searchText = ref('')

// 方法:过滤待办列表
function getFilteredTodos() {
  console.log('方法执行了!') // 每次调用都打印
  return todos.value.filter(todo =>
    todo.text.toLowerCase().includes(searchText.value.toLowerCase())
  )
}
</script>

执行结果

往期文章归档
免费好用的热门在线工具
  • 首次渲染时,getFilteredTodos()执行一次(打印日志);
  • 当你点击页面任何元素(比如输入框聚焦),Vue会重新渲染模板,导致方法再次执行
  • 即使searchText todos都没变,方法也会反复执行。

四、适用场景:该用计算属性还是方法?

记住一个核心原则:

需要缓存结果 → 用计算属性;需要每次执行 → 用方法

具体场景对比:

1. 计算属性的最佳场景

  • 依赖响应式数据的“衍生值”:比如过滤列表、格式化日期({{ formattedDate }})、计算总价(totalPrice = quantity * price);
  • 多次复用结果:比如同一个过滤后的列表在模板中用了3次,计算属性只会算1次;
  • 需要“属性”般的使用体验:比如像user.fullName(结合firstNamelastName)这样的组合字段。

2. 方法的最佳场景

  • 事件处理:比如@click="handleClick",每次点击都要执行逻辑;
  • 异步操作:比如调用接口获取数据(async fetchData()),计算属性不支持异步;
  • 不依赖缓存的动态计算:比如生成随机数(getRandomNumber())、实时获取当前时间(getCurrentTime())——这些场景需要每次调用都返回新值。

五、课后Quiz:巩固你的理解

问题
你正在做一个电商页面,需要根据用户选择的“分类”和“价格区间”实时过滤商品列表,并且要在商品列表筛选结果计数两个地方用到过滤后的列表。此时应该用计算属性还是方法?为什么?

答案解析
应该用计算属性
原因:

  1. 过滤结果依赖“分类”和“价格区间”这两个响应式数据,计算属性会自动追踪它们的变化;
  2. 同一个过滤后的列表要在两个地方复用,计算属性的缓存能避免重复计算(只算1次);
  3. 若用方法,每次渲染这两个地方都会执行2次过滤逻辑,性能更差。

六、常见报错与解决

在使用计算属性和方法时,新手常踩这些坑:

1. 计算属性不更新?——依赖了非响应式数据

报错场景

// 错误:count是非响应式的!
let count = 0 
const double = computed(() => count * 2)

count = 2 // double不会更新为4

原因:Vue无法追踪非响应式数据(比如let定义的变量)的变化。
解决:把数据改成响应式(用refreactive):

const count = ref(0) 
const double = computed(() => count.value * 2)

count.value = 2 // double会更新为4

2. 计算属性里用了异步,结果不更新?

报错场景

// 错误:计算属性不能是异步的!
const asyncData = computed(async () => {
  const res = await fetch('/api/data')
  return res.data
})

原因:计算属性设计为同步执行,无法等待异步结果。
解决:用watch监听依赖,或用方法处理异步:

const asyncData = ref(null)
// 用watch监听依赖,执行异步操作
watch(() => dependency, async () => {
  asyncData.value = await fetch('/api/data')
})

3. 方法调用时没加括号?——模板中不生效

报错场景

<!-- 错误:方法调用必须加括号! -->
<button @click="handleClick()">点我</button> <!-- 正确 -->
<button @click="handleClick">点我</button> <!-- 错误(会传递事件对象) -->

原因:在模板中,@click="handleClick"会把event对象传给方法,而handleClick()才是正常调用。
解决:事件绑定的方法若不需要event,必须加括号;若需要event,可以不加(Vue会自动传递)。

参考链接