Vue3 响应式核心
没有响应式之前,JS 更改页面数据是比较麻烦的,因为要手动同步页面。
数据存在 JS 变量里,页面是 HTML,两者互不相通。
改数据只是第一步,还得手动找到 DOM、手动把新值塞回去,页面里有几处用到这个数据,就得同步几处:
<button id="btn">+1</button>
<p id="count">0</p>
<script>
let count = 0
document.getElementById('btn').onclick = () => {
count++ // ① 改逻辑里的数据
// ② 再手动同步页面,漏一步就"数据变了、页面没变"
document.getElementById('count').textContent = count
}
</script>
Vue 把这两件事合并了:模板里声明”这里展示 count”,之后只管改数据,页面自动跟着更新——这正是上一篇(Vue3 初识组件)ref 给你带来的响应式,也是 {{ }} 这些模板语法存在的意义。
响应式的意思是:用普通变量做不到的”数据一变、用到它的地方自动更新”,Vue 通过给数据包一层”追踪器”来实现。
你不需要关心底层实现,只需要知道如何声明、如何读写。
数据变了,Vue 怎么更新页面
Vue 更新页面不是清空重画:它把新旧两份结构做对比,找出真正需要动的地方,只改该改的节点,尽量复用已有的 DOM——文本变了就改文本,结构变了才增删元素,能不动就不动。
这个”能复用就复用”的策略是性能关键,也带来一个前提问题:Vue 得知道”新列表里的这一项,对应旧列表里哪一个 DOM”。
等列表渲染篇讲到 key 时,就是靠它来回答这个问题的——现在先记住”Vue 会尽量复用 DOM”这个背景即可。
ref
ref 可以包装任意类型(数字、字符串、布尔、对象、数组),返回一个内部带 .value 的响应式对象:
<script setup>
import { ref } from 'vue'
const count = ref(0) // 基本类型
const user = ref({ name: '小明' }) // 对象
const list = ref([1, 2, 3]) // 数组
</script>
读写规则:
- 脚本中读写要带
.value:count.value = 5。 - 模板中自动解包,直接写变量名:
{{ count }}。 - 对象、数组是深层响应式的:
user.value.name改了也会触发更新,不需要手动拆开。
<script setup>
import { ref } from 'vue'
const user = ref({ name: '小明', age: 18 })
const changeName = () => {
user.value.name = '小红' // 深层属性也是响应式的
}
</script>
<template>
<p>{{ user.name }},{{ user.age }} 岁</p>
<button @click="changeName">改名</button>
</template>
reactive
reactive 返回的响应式对象没有 .value,直接用属性,因此只接受对象、数组等引用类型:
<script setup>
import { reactive } from 'vue'
const state = reactive({
count: 0,
name: '小明'
})
const add = () => {
// 直接改属性,不需要 .value
state.count++
}
</script>
<template>
<p>{{ state.count }}</p>
<button @click="add">+1</button>
</template>
reactive 有两个新手常踩的坑:
坑一:不能用于原始类型
// 错误:reactive 包数字无效,因为它没有属性可以追踪
const count = reactive(0)
要给基本类型做响应式,只能用 ref。
坑二:整体替换或解构会丢失响应
const state = reactive({ a: 1, b: 2 })
// 错误:重新赋值整个对象时会脱离响应式追踪
state = reactive({ a: 3, b: 4 })
// 错误:解构出来的是"那一刻的快照",不再响应
const { a } = state
如果必须要解构并保持响应,用 toRefs(后面 Vue3 Pinia 实战 篇也用得上):
import { reactive, toRefs } from 'vue'
const state = reactive({ a: 1, b: 2 })
const { a, b } = toRefs(state)
ref 和 reactive 怎么选
| 对比维度 | ref |
reactive |
|---|---|---|
| 能包什么 | 任意类型(含对象、数组) | 只能对象、数组 |
| 读写方式 | 脚本中 .value,模板自动解包 |
直接用属性 |
| 整体赋值 | 支持(直接改 .value) |
不支持,会丢响应 |
| 解构 | 天然安全 | 会丢响应,需 toRefs |
| 复杂度 | 单一规则,统一 | 规则多,坑也多 |
| 官方建议 | 日常开发优先用 ref |
适合整块对象状态,看团队习惯 |
- 官方更推荐以
ref为主:规则统一(一种写法管一切类型)、心智负担小。reactive的优势是”不需要.value“,在整块对象数据上写起来更省,但理解代价更高。 - 新手阶段统一用
ref没有任何问题,本系列后续代码也以ref为主。 - 顺带一提:如果数据量很大、想关闭深层响应来换性能,有
shallowRef、shallowReactive这类”浅响应”API,属于优化进阶,用到了再查文档即可,新手不必现在掌握。