为什么Vue 3需要ref函数?它的响应式原理与正确用法是什么?

39 阅读12分钟

1. 为什么需要ref函数?

在Vue 3的响应式系统中,reactive函数是处理对象类型数据的“主力”——它通过Proxy代理对象,实现属性的响应式跟踪。但**Proxy无法代理基本类型(如numberstringboolean)**,比如直接写let count = 0,修改count不会触发组件更新。

这时候,ref函数应运而生:它将基本类型数据包装成一个“响应式对象”(Ref对象),让基本类型也能享受响应式特性。换句话说,ref是Vue为基本类型定制的“响应式容器”。

2. ref函数的基本用法

2.1 定义与访问

使用ref需要先从vue中导入,然后用它包裹初始值:

<script setup>
import { ref } from 'vue' // 必须导入ref

// 用ref包裹基本类型,返回Ref对象
const count = ref(0) // 初始值为0
const message = ref('Hello Vue 3') // 字符串
const isDark = ref(false) // 布尔值
</script>

Ref对象有一个**value属性**,存储着实际的基本类型值。在JavaScript代码中(如setup、函数内),必须通过value访问或修改值

function increment() {
  count.value++ // 正确:修改Ref对象的value属性
}

function updateMessage() {
  message.value = 'Hello Ref' // 正确:更新字符串
}

为什么Vue 3需要ref函数?它的响应式原理与正确用法是什么?

往期文章归档
免费好用的热门在线工具

2.2 模板中的自动解包

在Vue模板中,Ref对象会自动“解开”value属性,直接用变量名就能访问值,无需写.value

<template>
  <button @click="increment">Count: {{ count }}</button> <!-- 自动解包,显示count.value -->
  <p>{{ message }}</p> <!-- 显示message.value -->
  <div :class="{ dark: isDark }">Dark Mode</div> <!-- 布尔值自动解包 -->
</template>

2.3 嵌套对象的响应式

如果ref包裹的是对象类型(如{ name: 'Alice' }),Vue会自动将对象转换为reactive代理,所以嵌套属性也能响应式更新:

const user = ref({ name: 'Alice', age: 20 })

function updateUser() {
  user.value.name = 'Bob' // 正确:修改reactive对象的属性,触发更新
  user.value = { name: 'Charlie', age: 21 } // 正确:替换整个对象,仍保持响应式
}

3. ref的响应式原理揭秘

要理解ref的工作机制,我们可以简化它的实现逻辑(Vue源码中的RefImpl类):

class RefImpl {
  constructor(initialValue) {
    this._value = initialValue // 存储原始值
    this.dep = new Set() // 存储依赖(如组件渲染函数)
  }

  // 访问value时,收集依赖
  get value() {
    track(this, 'get', 'value') // 告诉Vue:这个Ref对象被某个组件依赖了
    return this._value
  }

  // 修改value时,触发更新
  set value(newValue) {
    if (newValue !== this._value) {
      this._value = newValue // 更新原始值
      trigger(this, 'dep') // 通知所有依赖的组件重新渲染
    }
  }
}

核心流程(流程图)

graph TD
A[组件渲染] --> B[访问count.value]
B --> C[触发Ref对象的getter]
C --> D[收集依赖:记录当前组件]
D --> E[返回count.value(0)]
F[点击按钮调用increment] --> G[修改count.value为1]
G --> H[触发Ref对象的setter]
H --> I[检查值变化:0→1]
I --> J[通知依赖的组件重新渲染]

4. ref的实际应用场景

4.1 计数器组件(基本类型响应式)

最经典的场景:点击按钮增加计数,ref完美处理number类型的响应式:

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

const count = ref(0)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">Count: {{ count }}</button>
</template>

4.2 表单输入绑定

表单输入的value是字符串(基本类型),用ref绑定最适合:

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

const username = ref('') // 初始空字符串
const password = ref('')

function submit() {
  console.log('用户名:', username.value) // 读取输入值
  console.log('密码:', password.value)
}
</script>

<template>
  <input v-model="username" placeholder="用户名" /> <!-- v-model自动绑定value -->
  <input v-model="password" type="password" placeholder="密码" />
  <button @click="submit">提交</button>
</template>

4.3 动态样式切换

ref的布尔值控制组件样式,实现“暗黑模式”切换:

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

const isDark = ref(false)

function toggleMode() {
  isDark.value = !isDark.value // 切换布尔值
}
</script>

<template>
  <button @click="toggleMode">{{ isDark ? '浅色模式' : '暗黑模式' }}</button>
  <div :class="{ dark: isDark }">
    <p>当前模式:{{ isDark ? '暗黑' : '浅色' }}</p>
  </div>
</template>

<style>
.dark {
  background-color: #333;
  color: white;
}
</style>

5. 课后Quiz

问题1:以下代码点击按钮后,count是否会更新?为什么?

<script setup>
import { ref } from 'vue'
let count = ref(0)
function increment() {
  count = 1 // 直接替换count变量
}
</script>

答案:不会。因为count = 1直接替换了Ref对象,原来的响应式关联被切断。正确做法是count.value = 1

问题2:模板中写{{ count.value }}会导致什么问题?

答案:会报错。因为模板中count会自动解包成0count.value相当于0.value(字符串/数字没有value属性),导致Cannot read property 'value' of undefined

问题3:refreactive的核心区别是什么?各举一个场景。

答案

  • ref用于基本类型或需要独立响应式的对象,返回Ref对象,需通过.value访问;
  • reactive用于对象/数组,返回Proxy对象,直接访问属性。
    场景示例:ref用在计数器(count = ref(0)),reactive用在用户信息(user = reactive({ name: 'Alice' }))。

6. 常见报错与解决

报错1:Cannot read property 'value' of undefined

原因:未导入ref或变量未用ref包裹。
解决:确保导入import { ref } from 'vue',并使用ref定义变量(如const count = ref(0))。

报错2:修改ref变量不触发更新

原因:直接替换了Ref对象,而非修改.value
错误示例count = 1(替换Ref对象);
正确示例count.value = 1(修改Ref对象的value属性)。

报错3:模板中显示[object Object]

原因:模板中误写了.value
错误示例{{ count.value }}(模板会自动解包,无需.value);
正确示例{{ count }}

预防建议

  • 必写.value:在JavaScript代码中(setup、函数),修改ref变量必须用.value
  • 不写.value:在模板中,直接用变量名;
  • 不替换Ref对象:始终通过.value修改值,不要直接赋值替换(如count = ref(1))。

参考链接:
vuejs.org/guide/essen…
vuejs.org/guide/essen…