Vue Composition API:Computed 和 Ref 属性解释
Source: Dev.to
请提供您希望翻译的完整文本(包括正文、标题、段落等),我将按照要求将其翻译为简体中文并保留原始的 Markdown 格式、代码块和链接。谢谢!
1️⃣ State – the foundation
State 是指可以随时间变化并影响用户在屏幕上看到内容的数据。
如果数据发生变化且 UI 必须随之更新,那么这些数据就是 state(状态)。
前端应用中的状态示例包括:
- 计数器的数值
- 从 API 获取的用户列表
- 模态框是打开还是关闭
- 输入框的内容
- 当前登录的用户
如果你来自后端背景,你已经对状态很熟悉:数据库记录、请求/响应对象、会话数据、缓存值……区别在于 它所在的位置。
传统后端驱动的流程
- 客户端发送请求。
- 服务器处理数据。
- 服务器返回 HTML 或 JSON。
- 页面重新加载并显示更新后的数据。
在这种模型中,大部分状态都位于服务器端。
现代前端流程(Vue)
- 状态存在 浏览器中。
- 无需整页刷新。
- 用户点击按钮 → 数据在内存中改变 → UI 自动更新。
这种自动更新就是我们所说的 reactivity(响应式)。Vue 需要一种方式来知道 哪些 数据应该触发 UI 更新——这正是 ref 和 computed 所提供的功能。
2️⃣ ref – 响应式状态
ref 用来创建 响应式值(数字、字符串、布尔值等),Vue 会对其进行监听。当值变化时,Vue 会自动更新 UI。
基本示例(Vue 3 + TypeScript)
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
ref(0)告诉 TypeScript 这个ref包含的是一个数字。- 实际的值通过
.value访问。 - 当
count.value变化时,Vue 会重新渲染所有依赖该值的 UI。
在模板中使用
{{ count }}
<button @click="increment">Increment</button>
在模板中 不需要
.value,因为 Vue 会自动解包ref。
带对象的 ref
在处理对象(尤其是配合 TypeScript 接口)时,仍然可以使用 ref。
interface User {
name: string
age: number
}
const user = ref({
name: 'Lou',
age: 27
})
// 更新属性
user.value.age++
如果你主要处理对象,还会遇到 reactive 函数,但核心思想保持不变:Vue 追踪变化,并在状态改变时更新 UI。
3️⃣ computed – 派生(只读)状态
如果 ref 是你的 真相来源,computed 则表示 从该来源派生出的值。派生状态是由其他状态计算得来的,应该始终保持同步,且 不应 手动更新。
简单的派生状态示例
import { ref, computed } from 'vue'
const price = ref(10)
const quantity = ref(2)
const total = computed(() => {
return price.value * quantity.value
})
price与quantity是源状态(ref)。total是派生状态(computed)。
从后端的角度来看,这类似于 计算列 或 由其他字段构建的 DTO 字段。你不会在数据库中单独存储 total,因为它始终可以通过 price * quantity 计算得到;这样做会导致数据冗余和潜在的不一致性。computed 在客户端实现了同样的功能。
computed 与普通函数的对比
function fullName() {
return `${firstName.value} ${lastName.value}`
}
{{ fullName() }}
为什么更倾向于使用 computed?
| 普通函数 | computed | |
|---|---|---|
| 每次渲染时运行 | ✅ (始终) | ❌ (仅在依赖变化时) |
| 缓存 | 否 | 是(已缓存) |
| 依赖追踪 | 手动 | 自动 |
普通函数会 每次组件重新渲染时 执行,即使底层值没有变化。computed 属性是 缓存的;Vue 会追踪它依赖的响应式值,并仅在这些值变化时重新计算。在包含昂贵计算的大型组件中,computed 能让代码更高效、更易于理解。
决策规则
这是事实来源,还是从其他东西派生而来?
- 事实来源 → 使用
ref。- 派生 → 使用
computed。
这种思维模型与后端关注数据一致性和单一事实来源的理念高度契合。
带 getter 和 setter 的 computed
有时你需要一个既能 读取 又能 写入 数据的计算属性。可以同时提供 getter 和 setter:
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(value: string) {
const [first, ...rest] = value.split(' ')
firstName.value = first
lastName.value = rest.join(' ')
}
})
现在 fullName 可以像普通响应式属性一样使用:
First name: {{ firstName }}
Last name: {{ lastName }}
当用户编辑输入时,setter 会相应地更新 firstName 和 lastName,界面保持同步。
4️⃣ 回顾
| 概念 | 用途 | 典型用法 |
|---|---|---|
ref | 创建 响应式原始值 或 对象 状态 | 计数器、开关、API 响应 |
computed | 基于其他 ref 定义 派生的、缓存的 状态 | 计算总计、格式化字符串、过滤后的列表 |
reactive (not covered in depth) | 创建 深度响应式对象(对象的 ref 替代方案) | 复杂的嵌套状态 |
记住:
ref= 真相来源(可变)。computed= 派生的、只读的(除非你添加 setter)。
理解这两个构建块将让你在 Vue 3 的响应式系统中如鱼得水,无论你的后端背景如何。
编码愉快! 🚀
使用 Vue 3 + TypeScript 中的 ref 和 computed
watch(() => fullName.value, (value) => {
const parts = value.split(' ')
firstName.value = parts[0]
lastName.value = parts[1]
})
现在你可以直接赋值:
fullName.value = 'Lou Creemers'
这在表单中很有用,因为一个输入字段代表多个状态。
Source: …
Vue 3 + TypeScript 中的类型
在使用 Vue 3 与 TypeScript 时,最好提前考虑类型。
TypeScript 通常可以自动推断类型:
const isVisible = ref(true) // inferred as Ref<boolean>
你也可以显式地定义它(相同的语法同样适用):
const isVisible = ref(true)
对于 computed,返回类型通常会被推断。如果需要,你可以显式定义:
const total = computed(() => {
return price.value * quantity.value
})
明确类型有助于防止细微的错误,尤其是在大型项目或团队协作的环境中。
常见错误
- 在脚本中忘记使用
.value。 - 在未定义 setter 的情况下尝试更改计算属性。
- 将
computed用于本应是普通常量的情况。
如果模板中的某些内容没有更新,首先要检查的就是你是否真的在使用 响应式源。
为什么 ref 和 computed 很重要
ref 和 computed 是 Vue 3 响应式模型的核心部分。
一旦你理解了 源状态(ref)和 派生状态(computed)之间的区别,你的组件就会更容易推理。
如果你正在使用 Vue 3 和 TypeScript 构建应用,掌握这两个概念将为后续的所有内容奠定坚实的基础。
有问题吗?
随时联系我: louella.dev/socials 或在下方留下评论。
再见!