vue3源码学习(五)ref 、toRef、toRefs、proxyRefs 源码学习

vue3源码学习(五) ------ ref 、toRef、toRefs、proxyRefs 源码学习

ref 源码学习回顾、复习。


ref 源码学习

  • [vue3源码学习(五) ------ ref 、toRef、toRefs、proxyRefs 源码学习](#vue3源码学习(五) —— ref 、toRef、toRefs、proxyRefs 源码学习)
  • [实现 ref 、toRef、toRefs、proxyRefs函数](#实现 ref 、toRef、toRefs、proxyRefs函数)
      • [1、实现 ref 函数](#1、实现 ref 函数)
      • [2、实现 toRef函数](#2、实现 toRef函数)
      • [3、实现 toRefs函数](#3、实现 toRefs函数)
      • [4、实现 proxyRefs 函数](#4、实现 proxyRefs 函数)
  • 二、ref与reactive的区别
  • 三、完整代码

使用示例:

javascript 复制代码
//---------------- ref使用 -------------------
const name = ref('zhangsan')
watchEffect(()=>{
   document.getElementById('app').innerHTML = name.vaue
})

setTimeout(()=> {
   name.value = 'lisi'
}, 1000)
//---------------- toRef使用 -------------------
const person = reactive({name:'zhagnsan', age: 18})
let name = toRef(person, 'name')
watchEffect(()=>{
   document.getElementById('app').innerHTML = name.vaue
})

setTimeout(()=> {
	// 这种情况下这两种写法都会触发更新
	 // person.name = 'lisi'
   name.value = 'lisi'
}, 1000)

//---------------- toRefs使用 -------------------
const person = reactive({name:'zhagnsan', age: 18})
const { name, age }= toRefs(person)
watchEffect(()=>{
   document.getElementById('app').innerHTML = name.value + age.value
})

setTimeout(()=> {
   name.value = 'lisi'
   age.value= 40
}, 1000)

//---------------- proxyRefs使用 -------------------
const person = reactive({name:'zhagnsan', age: 18})
watchEffect(()=>{
	// 解决解构响应式消失
	const { name, age }= toRefs(person)
   document.getElementById('app').innerHTML = name.value + age.value
})

setTimeout(()=> {
   person.name = 'lisi'
   person.value= 40
}, 1000)

实现 ref 、toRef、toRefs、proxyRefs函数

ref一般用于基本类型,还有就是整个对象替换时使用,对象类型一般使用reactive

1、实现 ref 函数

分析:ref函数返回的是RefImpl对象,其数据拦截是通过 getter/setter 拦截 .value(非 Proxy),取值取的是RefImpl中的value。当时ref后的变量在effect中使用时则会收集依赖,在修改时触发effect从而实现更新。

javascript 复制代码
function ref(value){
	return new RefImpl(value)
}
function toReative(value){
	 // 判断是否是对象,如果是对象就使用reactive转响应式,改与取都是使用的reactive中的get、set
    return isObject(value) ? reactive(value) : value
}
class RefImpl {
    dep = undefined;
    _value;
    __is__isRef = true
    constructor(public rawValue){
        this._value = toReative(rawValue)
    }
    get value(){
        // 当activeEffect为真时,说明当前正在执行一个effect,需要收集依赖
        if(activeEffect){
            trackEffect(this.dep || (this.dep = new Set()));
        }
        return this._value
    }

    set value(newValue){
        if(newValue !== this.rawValue){
            // 将处理后的值赋值
            this._value = toReative(newValue)
            this.rawValue = newValue
            // 触发更新
            triggerEffect(this.dep)
        }
    }
}

2、实现 toRef函数

分析: toRef的核心价值在于:为响应式对象(reactive/props)的单个属性创建一个保持双向绑定的 ref,避免解构时响应式丢失,同时精准传递属性引用。转换后具体数据结构结构如下图。

javascript 复制代码
// 定义类
class ObjectRefImpl {
    __is__isRef = true
    constructor(public _object, public key){}
    get value(){
        return this._object[this.key]
    }
    set value(newValue){
        this._object[this.key] = newValue
    }
}
export function toRef(target, key){
    return new ObjectRefImpl(target, key)
}

3、实现 toRefs函数

分析:toRefs解决的就是不写多行toRef。

javascript 复制代码
export function toRef(target, key){
    return new ObjectRefImpl(target, key)
}

4、实现 proxyRefs 函数

proxyRefs 是 Vue 3(3.2+ 版本导出为公开 API)中用于自动解包对象中 ref 属性的工具函数,核心作用是:

  • 访问属性时:若属性是 ref,自动返回 .value
  • 赋值时:若原属性是 ref,更新其 .value;否则创建新 ref
  • 保持响应式连接(修改会同步回源对象)
javascript 复制代码
export function proxyRefs(objectWithRefs){
    return new Proxy(objectWithRefs, {
        get(target, key, receiver){
            const v = Reflect.get(target, key, receiver)
            return v.__is_isRef ? v.value : v
        },
        set (target, key, newValue, receiver){
            const oldValue = target[key]
            if(oldValue.__is_isRef){
                oldValue.value = newValue
                return true;
            }
            return Reflect.set(target, key, newValue, receiver)
        }
    })
}

二、ref与reactive的区别

ref :针对单个值,通过 getter/setter 拦截 .value(非 Proxy)。
reactive :针对对象,返回 Proxy 实例,通过 Proxy 的 get/set 拦截属性访问。

(二者底层共享 track/trigger 逻辑,但拦截机制不同)

三、完整代码

javascript 复制代码
import { isObject } from "@vue/shared";
import { reactive } from "./reactive";
import { activeEffect, trackEffect, triggerEffect } from "./effect";
function toReative(value){
    return isObject(value) ? reactive(value) : value
}

// **ref**:针对单个值,通过 getter/setter 拦截 .value(非 Proxy)。
// **reactive**:针对对象,返回 Proxy 实例,通过 Proxy 的 get/set 拦截属性访问。
class RefImpl {
    dep = undefined;
    _value;
    __is__isRef = true
    constructor(public rawValue){
        this._value = toReative(rawValue)
    }
    get value(){
        // 当获取ref的value时,将ref的dep收集到activeEffect中
        // 当activeEffect为真时,说明当前正在执行一个effect,需要收集依赖
        if(activeEffect){
            trackEffect(this.dep || (this.dep = new Set()));
        }
        return this._value
    }

    set value(newValue){
        if(newValue !== this.rawValue){
            this._value = toReative(newValue)
            this.rawValue = newValue
            triggerEffect(this.dep)
        }
    }
}
export function ref(value){
    return new RefImpl(value)
}

class ObjectRefImpl {
    __is__isRef = true
    constructor(public _object, public key){}
    get value(){
        return this._object[this.key]
    }
    set value(newValue){
        this._object[this.key] = newValue
    }
}
export function toRef(target, key){
    return new ObjectRefImpl(target, key)
}

export function toRefs(Ojbect){
    const ret = {}
    for(let key in Ojbect){
        ret[key] = toRef(Ojbect, key)
    }
    return ret
}

export function proxyRefs(objectWithRefs){
    return new Proxy(objectWithRefs, {
        get(target, key, receiver){
            const v = Reflect.get(target, key, receiver)
            // __is_isRef判断是否是ref
            return v.__is_isRef ? v.value : v
        },
        set (target, key, newValue, receiver){
            const oldValue = target[key]
            // __is_isRef判断是否是ref
            if(oldValue.__is_isRef){
                oldValue.value = newValue
                return true;
            }
            return Reflect.set(target, key, newValue, receiver)
        }
    })
}

结语:

若没有了解过effect和reactive的实现可阅读相关内容:

vue3源码学习(一)reactive 源码学习
vue3源码学习(二)effect 源码学习

相关推荐
不光头强1 小时前
SpringBoot 开发第三天 学习内容
java·spring boot·学习
广州华水科技1 小时前
单北斗GNSS在变形监测中的应用与优势分析
前端
用泥种荷花2 小时前
【LangChain.js学习】大模型分类
前端
Coisinilove2 小时前
MATLAB学习笔记——第二章
笔记·学习·matlab
Titan20242 小时前
C++异常学习笔记
c++·笔记·学习
掘金安东尼2 小时前
Angular 中的增量水合:构建“秒开且可交互”的 SSR 应用
前端·angular.js
大龄程序员2 小时前
TypeScript 类型体操:如何为 SDK 编写优雅的类型定义
前端·aigc
大龄程序员2 小时前
别再用 ID 定位了!教你用"语义指纹"实现 99% 的元素定位成功率
前端·aigc
RaidenLiu2 小时前
拒绝重写!Flutter Add-to-App 全攻略:让原生应用“渐进式”拥抱跨平台
前端·flutter·前端框架