vue3 ref解析

在 Vue3 中,ref 是响应式系统的核心 API 之一,专门解决原始值(Number、String、Boolean 等) 无法被 Proxy 拦截的问题。我会从「设计初衷」「源码实现」「核心特性」「使用场景」四个维度,彻底解析 ref,帮你理解它的底层逻辑和使用细节。

一、为什么需要 ref?

Vue3 的 reactive 基于 Proxy 实现,但 Proxy 只能拦截对象 / 数组 的属性访问,无法直接拦截原始值(因为原始值不是引用类型,没有属性可拦截)。

举个例子:

ini 复制代码
// 原始值无法被 reactive 响应式化
let num = reactive(1); 
num = 2; // 只是重新赋值变量,不会触发更新

// 而 ref 可以解决这个问题
let num = ref(1);
num.value = 2; // 会触发响应式更新

ref 的核心设计思路:把原始值包裹成一个对象,通过访问器属性(get/set)拦截 .value 的读写,从而实现响应式

二、ref 核心源码解析

ref 的源码主要在 packages/reactivity/src/ref.ts 中,核心逻辑分为「创建 ref 实例」「依赖收集」「触发更新」三部分。

1. 核心入口:ref 函数

php 复制代码
// 对外暴露的 ref 函数
export function ref<T>(value: T): Ref<UnwrapRef<T>> {
  return createRef(value, false)
}

// 内部核心创建函数(区分 shallowRef)
function createRef(rawValue: unknown, shallow: boolean) {
  // 避免重复包装:如果已经是 ref,直接返回
  if (isRef(rawValue)) {
    return rawValue
  }
  // 核心:创建 RefImpl 实例(真正实现响应式的类)
  return new RefImpl(rawValue, shallow)
}

2. 核心实现:RefImpl 类

这是 ref 的核心,通过类的访问器属性(get value()/set value())拦截 .value 的读写:

kotlin 复制代码
class RefImpl<T> {
  // 私有属性:存储原始值和处理后的响应式值
  private _value: T
  private _rawValue: T

  // 标记:标识这是一个 ref(供 isRef 检测)
  public readonly __v_isRef = true

  constructor(value: T, public readonly _shallow: boolean) {
    // 1. 保存原始值(用于后续对比是否变化)
    this._rawValue = toRaw(value)
    // 2. 处理值:shallow 为 false 时,深层响应式(如 ref({a:1}) 会转 reactive)
    this._value = _shallow ? value : toReactive(value)
  }

  // 读取 .value 时触发(依赖收集)
  get value() {
    // 核心:收集依赖(和 reactive 的 track 逻辑一致)
    trackRefValue(this)
    // 返回处理后的值
    return this._value
  }

  // 赋值 .value 时触发(触发更新)
  set value(newVal) {
    // 转原始值,避免响应式对象对比出错
    newVal = this._shallow ? newVal : toRaw(newVal)
    // 只有值真正变化时,才更新并触发更新
    if (hasChanged(newVal, this._rawValue)) {
      // 更新原始值和响应式值
      this._rawValue = newVal
      this._value = this._shallow ? newVal : toReactive(newVal)
      // 核心:触发依赖更新
      triggerRefValue(this, newVal)
    }
  }
}

3. 关键辅助函数

  • toReactive:如果值是对象,自动转为 reactive(所以 ref({a:1}) 等价于 ref(reactive({a:1}))):

    typescript 复制代码
    export const toReactive = <T extends unknown>(value: T): T => {
      return isObject(value) ? reactive(value) : value
    }
  • hasChanged:判断值是否真的变化(处理 NaN、引用类型等特殊情况):

    typescript 复制代码
    export const hasChanged = (value: any, oldValue: any): boolean => {
      return !Object.is(value, oldValue)
    }
  • trackRefValue/triggerRefValue:专门针对 ref 的依赖收集和触发更新,底层复用了 track/trigger 逻辑。

三、ref 的核心特性

1. 自动解包(模板中无需 .value)

在模板中使用 ref 时,Vue 会自动解包,无需写 .value

xml 复制代码
<template>
  <!-- 直接写 num,等价于 num.value -->
  <div>{{ num }}</div>
</template>

<script setup>
import { ref } from 'vue'
const num = ref(1)
</script>

源码逻辑 :在组件渲染时,Vue 会遍历 setup 返回的对象,对 ref 类型的属性做「自动解包」处理(访问属性时自动取 .value)。

2. 嵌套对象的响应式

如果 ref 的值是对象 / 数组,会被 toReactive 转为 reactive,因此嵌套属性也能响应式:

ini 复制代码
const objRef = ref({ a: 1 })
objRef.value.a = 2 // 会触发更新(因为内部是 reactive)

3. ref 与 reactive 的互操作

  • ref 作为 reactive 对象的属性时,会自动解包:

    scss 复制代码
    const numRef = ref(1)
    const obj = reactive({ num: numRef })
    console.log(obj.num) // 1(自动解包,无需 .value)
    obj.num = 2 // 等价于 numRef.value = 2,会触发更新
  • 数组 / Map 中的 ref 不会自动解包:

    scss 复制代码
    const arr = reactive([ref(1)])
    console.log(arr[0].value) // 必须写 .value

四、ref 的衍生 API

1. shallowRef

浅响应式 ref,只监听 .value 的赋值,不监听内部对象的变化:

php 复制代码
export function shallowRef<T>(value: T): Ref<T> {
  return createRef(value, true) // _shallow 为 true
}

// 使用示例
const objRef = shallowRef({ a: 1 })
objRef.value.a = 2 // 不会触发更新(只监听 .value 赋值)
objRef.value = { a: 2 } // 会触发更新

2. isRef

检测是否为 ref 实例(通过 __v_isRef 标记):

sql 复制代码
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T> {
  return Boolean(r && (r as Ref).__v_isRef === true)
}

3. unref

语法糖,unref(x) 等价于 isRef(x) ? x.value : x

csharp 复制代码
export function unref<T>(ref: T | Ref<T>): T {
  return isRef(ref) ? ref.value : ref
}

总结

  1. 核心本质 :ref 是「原始值的响应式包装器」,通过类的访问器属性拦截 .value 的读写,解决原始值无法被 Proxy 拦截的问题;
  2. 关键逻辑:创建 RefImpl 实例 → get value 时收集依赖 → set value 时触发更新;
  3. 核心特性:模板自动解包、对象值自动转 reactive、与 reactive 互操作时的部分自动解包。

理解 ref 的核心是记住:ref 所有的响应式都围绕 .value 展开 ,无论是原始值还是对象,只有操作 .value(或模板自动解包)才会触发响应式更新。

相关推荐
Curvatureflight8 分钟前
浏览器音频采集实践:麦克风权限、降噪、回声消除与 PCM 转换
前端·javascript·音视频·信息与通信·web·pcm
Dontla10 分钟前
HTML实体转义(HTML Entity Escaping)介绍
前端·html
咸鱼翻身小阿橙11 分钟前
高斯模糊降噪/磨皮算法降噪图像
前端·opencv·算法·webpack·c#
ct97812 分钟前
ES6 新特性
前端·vue.js·性能优化
KaMeidebaby19 分钟前
卡梅德生物技术快报|抗原如何自己检测?FAdV-4 重组抗原制备与 ELISA 体系技术调试指南
前端·人工智能·物联网·算法·百度
一拳不是超人22 分钟前
AI 辅助研发时代,如何用“规范 Skill”缩短测试周期
前端·人工智能·代码规范
夜郎king2 小时前
湖南高考天气查询:基于 HTML5 与百度天气 API 实现页面展示
前端·html5·百度天气实践·天气信息可视化
云水一下9 小时前
TypeScript 从零基础到精通(五):高级类型与泛型
前端·javascript·typescript
counterxing9 小时前
vibe coding 之后,我更不想打字了
前端·agent·ai编程
copyer_xyf10 小时前
Python 模块与包的导入导出
前端·后端·python