六维教程

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>

读写规则:

  • 脚本中读写要带 .valuecount.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)

refreactive 怎么选

对比维度 ref reactive
能包什么 任意类型(含对象、数组) 只能对象、数组
读写方式 脚本中 .value,模板自动解包 直接用属性
整体赋值 支持(直接改 .value 不支持,会丢响应
解构 天然安全 会丢响应,需 toRefs
复杂度 单一规则,统一 规则多,坑也多
官方建议 日常开发优先用 ref 适合整块对象状态,看团队习惯
  • 官方更推荐以 ref 为主:规则统一(一种写法管一切类型)、心智负担小。reactive 的优势是”不需要 .value“,在整块对象数据上写起来更省,但理解代价更高。
  • 新手阶段统一用 ref 没有任何问题,本系列后续代码也以 ref 为主。
  • 顺带一提:如果数据量很大、想关闭深层响应来换性能,有 shallowRefshallowReactive 这类”浅响应”API,属于优化进阶,用到了再查文档即可,新手不必现在掌握。
上一篇
Vue3 模板基础
下一篇
Vue3 计算与侦听