Vue3响应式原理之ref篇

在 Vue 3 的响应式系统中,ref 和 reactive 是用于定义响应式变量最核心的两个API。今天本文将带大家剖析 ref 的内部机制,以及与 reactive 的对比和应用场景。

一、什么是 ref

ref 是 Vue 3 提供的一个函数,用于创建一个可响应式的引用对象,它的值通过 .value 属性访问和修改:

javascript 复制代码
import { ref } from 'vue'

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

内部实现简述

ref 返回的是一个包含 value getter/setter 的对象(如 RefImpl类实例)。当读取 .value 时,会触发getter下的track() 收集依赖;当赋值时,会触发 setter中的trigger() 触发更新。

javascript 复制代码
/*
* - RefImpl类实现get和set
* @params {any} rawValue - ref传入的值
* @params {boolean|readonly} _isShallow - 判断是否浅层代理决定是否需要reactive
* @returns - 无
*/

class RefImpl {
  constructor(rawValue, _isShallow) {
      this._value = _isShallow ? _rawValue: convert(_rawValue)
      this._shallow = _isShallow
  }
  get value() {
    track(this, 'value') // 收集依赖
    return this._value
  }
  set value(newValue) {
    if (hasChanged(newValue, this._value)) {//旧值与新值比对
       this._value = this._shallow ? newValue : convert(newValue);
      trigger(this, 'value') // 触发更新
    }
  }
}
function convert(value) {
    return (Object.prototype.toString.call(value) == '[object Object]') ? reactive(value) : value
}

说明:ref 的响应式依赖对 .value 的访问

二、ref vs reactive 核心区别

适用类型:
ref: 基本类型(number, string, boolean)或对象
reactive: 仅对象/数组(不能用于基本类型)

访问方式:
ref: 必须通过 .value
reactive: 直接属性访问(如 obj.count)
解包行为
ref: 在模板中自动 .value 解包
reactive: 无解包,直接使用
嵌套响应式
ref: 对象会自动转为 reactive(除非 shallow)
reactive: 所有嵌套对象进行响应式转换

javascript 复制代码
示例对比

// 使用 ref(推荐用于基本类型)
const count = ref(0)
const user = ref({ name: 'Alice' })

// 使用 reactive
const state = reactive({
  count: 0,
  user: { name: 'Alice' }
})
//在模板中
<template>
 {{ count }}        
 {{ user.name }}    
 {{ state.count }}
 {{ state.user.name }}
</template>

说明:在 JavaScript 中,ref 必须手动 .value,而 reactive 不需要。

三、何时使用 ref,何时使用 reactive?

推荐使用 ref 的场景:

如果是处理基本类型数据,则使用ref,这也是 reactive 无法做到的, reactive处理基本类型数据无响应式更新,例如:

javascript 复制代码
let val = reactive(1)
// 以下更改都会失去响应式
 setTimeout(() => {
  val = 2
  val = reactive(2)
 }, 1000)

推荐使用 reactive 的场景:

javascript 复制代码
// 管理复杂对象或表单状态
  const form = reactive({
     email: '',
     password: '',
     errors: []
   })
   
//避免大量 .value 写法
// 比 ref({ ... }) 更简洁
 	const state = reactive({ a: 1, b: 2, c: 3 })
   state.a++ // 直接操作
   
//性能略优于 ref 的嵌套对象
//reactive 只创建一层 Proxy,而 ref 包裹对象时内部仍会调用reactive

四、常见误区

1:在 effect 或 watchEffect 中创建 ref

javascript 复制代码
// 错误,每次 effect 执行都会新建 ref,失去响应式
effect(() => {
  const count = ref(0) 
  console.log(count.value)
})
// 正确做法:在 effect 外定义 ref
const count = ref(0)
effect(() => {
  console.log(count.value) // 追踪稳定的 ref
})

2:混淆 ref 和普通变量

javascript 复制代码
let count = ref(0)
count = 1 // 赋值给变量本身,丢失响应式!

//正确:始终操作 .value
count.value = 1 

五、总结

对于掌握 ref 与 reactive 的差异,可以在适当场景下写出高效、可维护 Vue 3 应用的关键。本文主要基于ref的核心源码实现,其他功能如reactive和effect的依赖与收集实现可以参考本人之前的文章,也欢迎有问题的博友在评论区一起讨论!

相关推荐
vx_Biye_Design2 小时前
springbootNBA数据分析系统平台42434-计算机课程设计、毕业设计
java·vue.js·spring boot·python·django·课程设计·express
易朵朵19 小时前
CSS 格式化上下文
css·vue.js
雪芽蓝域zzs20 小时前
第6节:远程搜索弹窗(remote 模式),弹窗滚动加载分页
vue.js·elementui
Highcharts.js21 小时前
图表开发实战总结| 7 条“黄金法则”与“性能铁律”
前端·javascript·vue.js·编辑器·时序数据库·highcharts
Wx-bishekaifayuan1 天前
springboot陨石鉴收系统96265-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·python·spring·课程设计
FYKJ_20101 天前
springboot家政服务平台66766-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·mysql·spark·课程设计
墨鱼老师1 天前
Go+Gin+Vue 毕设项目:Gin 框架搭建后端基础接口实战
vue.js·golang·go·gin·前后端分离·计算机毕业设计·go 后端
ym hyd 1111 天前
试题库管理系统源码 Java+SpringBoot+Vue3 前后分离
java·vue.js·spring boot·毕设
雪芽蓝域zzs2 天前
第4节:el-select 弹窗事件控制(打开 / 关闭、选中事件、弹窗滚动事件)
vue.js·elementui
web打印社区2 天前
HTML5 静默打印:前端页面怎么调用打印机且尽量不弹窗
前端·javascript·vue.js·pdf·html·html5