Vue3源码之Refs

前言

Vue3的响应性API除了reactive、readonly等基础的,还提供了相关Refs API其中包括ref、customRef等。在之前的响应系统的文章中,对reactive的逻辑有较为详细的分析,本文会对梳理相关API的逻辑,对比reactive API看看二者的不同,加强对Refs API使用的理解(Vue 3.1.1版本)。

ref

官网对ref API的描述如下:

接受一个内部值并返回一个响应式且可变的 ref 对象,ref 对象具有指向内部值的单个属性value。

通过上面的描述总结下面几点关键信息:

  • 内部值:可以是原始值、Object、Array、Map、Set等
  • 响应式且可变:proxy对象
  • ref对象存在value属性

结合源码来看看上面几点的处理逻辑:

function ref(value) {
	return createRef(value);
}
function createRef(rawValue, shallow = false) {
	// 判断是否已是ref对象
	if (isRef(rawValue)) {
    	return rawValue;
    }
    return new RefImpl(rawValue, shallow);
}

从上面可知,Ref API就是返回一个RefImpl实例对象,RefImpl类的具体逻辑如下:

class RefImpl {
	constructor(_rawValue, _shallow) {
    	this._rawValue = _rawValue;
        this._shallow = _shallow;
        this.__v_isRef = true;
        this._value = _shallow ? _rawValue : convert(_rawValue);
    }
    get value() {
    	track(toRaw(this), "get" /* GET */, 'value');
        return this._value;
    }
    set value(newVal) {
    	if (hasChanged(toRaw(newVal), this._rawValue)) {
        	this._rawValue = newVal;
            this._value = this._shallow ? newVal : convert(newVal);
            trigger(toRaw(this), "set" /* SET */, 'value', newVal);
        }
    }
}

使用class定义RefImpl类,其逻辑非常清晰。首先其构造函数中定义的属性:

  • _rawValue:传入的原始数据
  • _shallow:是否是浅层代理
  • __v_isRef:标记ref对象
  • _value:返回的ref对象

然后就是定义了value属性的getter和setter,这里解释了ref对象存在value属性的原因。实际上从上面RefImpl的整体处理逻辑,有3点核心:

  • value属性的getter
  • value属性的setter
  • shallow与convert
value属性的getter

value属性的getter处理逻辑具体如下:

get value() {
	track(toRaw(this), "get" /* GET */, 'value');
    return this._value;
}

内部会调用track函数对value属性进行追踪,需要对target进行toRaw操作,即始终使用目标代理的原始对象。

value属性的setter

value属性的setter处理逻辑具体如下:

set value(newVal) {
	if (hasChanged(toRaw(newVal), this._rawValue)) {
    	this._rawValue = newVal;
        this._value = this._shallow ? newVal : convert(newVal);
        trigger(toRaw(this), "set" /* SET */, 'value', newVal);
    }
}

内部会调用trigger函数来触发视图的更新,其次会替换内部属性_value的值为最新的值。ref API定义的代理对象是可变的,所谓的可变是指什么?实际上就是value属性值支持可变,即内部属性_value属性值的可变。

从_shallow可知,ref会存在浅层响应的处理,那convert函数的逻辑是什么呢?convert函数具体逻辑如下:

 const convert = (val) => isObject(val) ? reactive(val) : val;

如果值是对象类型就会调用reactive实现完全代理,从这里就可以知道:

如果传入给ref的值是对象,实际上内部会调用reactive来实现完全代理,即value属性的值也会被代理

shallowRef

官方对其API的描述如下:

创建一个 ref,它跟踪自己的 .value 更改,但不会使其值成为响应式的

实际上shallowRef是浅层响应,该API的源码如下:

function shallowRef(value) {
	return createRef(value, true);
} 

而createRef就是new RefImpl,其中会存在_shallow实例属性,就是createRef函数的第2个参数值,即是否浅层,对于浅层的,实际上就是不会调用reactive来实现对value属性值的代理。这也是官网说的不会使其值成为响应式的本质。

toRef

官网文档中对toRef API的描述如下:

可以用来为源响应式对象上的 property 创建一个 ref。然后可以将 ref 传递出去,从而保持对其源 property 的响应式连接

其调用语法是:toRef(Object, key),再结合官网文档对其的描述,实际上toRef只能用来对指定的对象属性创建一个ref对象。

toRef API相关源码具体如下:

class ObjectRefImpl {
	constructor(_object, _key) {
    	this._object = _object;
        this._key = _key;
        this.__v_isRef = true;
    }
    get value() {
    	return this._object[this._key];
    }
    set value(newVal) {
    	this._object[this._key] = newVal;
    }
}

function toRef(object, key) {
	return isRef(object[key])
    	? object[key]
        : new ObjectRefImpl(object, key);
}

toRef的逻辑非常简单,如果对象属性的值本身就是ref对象就不会再次处理,否则会调用ObjectRefImpl类创建对应实例。ObjectRefImpl中不会对属性做任何操作,仅仅是定义value属性,而value属性的getter、setter只是简单的获取和赋值操作:

toRef只是定义了value属性而已,之所以实现保持对 对象源属性的响应式连接,完全是属性所在对象自身必须是响应式

toRefs

function toRefs(object) {
	// Object必须是代理对象
	if (!isProxy(object)) {
    	console.warn(`toRefs() expects a reactive object but received a plain one.`);
    }
    const ret = isArray(object) ? new Array(object.length) : {};
    for (const key in object) {
    	ret[key] = toRef(object, key);
    }
    return ret;
}

从上面的逻辑中可知如下的信息:

  • toRefs实际上就是批量的执行toRef
  • 支持数组类型
  • 只是浅层处理,即只处理对象自身的属性,不会去处理所有嵌套属性

customRef

使用customRef创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。实际上

customRef背后实现的逻辑实现具体如下:

class CustomRefImpl {
	constructor(factory) {
    	this.__v_isRef = true;
        const { get, set } = factory(
        	() => track(this, "get" /* GET */, 'value'),
        	() => trigger(this, "set" /* SET */, 'value')
        );
        this._get = get;
        this._set = set;
    }
    get value() {
    	return this._get();
    }
    set value(newVal) {
    	this._set(newVal);
    }
}

function customRef(factory) {
	return new CustomRefImpl(factory);
}

从上面可知customRef实际上是调用CustomRefImpl类创建一个实例,开发者需要传入一个函数类型的参数,这个函数类实例化过程中会被调用,而这个函数会被传入 track 和 trigger 函数作为参数,返回值需要是一个带有 get 和 set 的对象,返回值对应get和set会在value属性的getter和setter中执行。

通过源码逻辑的了解customRef的使用语法如下:

function createCustomRef(value) {
	return customRef(function(track, trigger) {
		return {
			get() {
				// 必须调用,否则无法实现属性的响应式
				track();
				return value;
			},
			set(newVal) {
				value = newVal;
				// 必须调用
				trigger();
			}
		}
	})
}

你可以在set中做一些异步操作,例如Ajax的请求等,具体使用完全取决于开发者了。

triggerRef

该API的源码逻辑非常简单:

function triggerRef(ref) {
	trigger(toRaw(ref), "set" /* SET */, 'value', ref.value );
}

就是手动调用trigger触发,为什么会存在该API呢?实际上是搭配shallowRef来使用的。通过前面对ref、shallowRef的分析可以知道:

ref对象实际上就是存在value属性,Ref API不仅仅对value属性做代理,对value属性的值会调用reactive来处理;而shallowRef是不会对value属性的值做代理处理的

shallowRef处理的ref对象是浅层的,如果value属性的值是对象类型,对其做赋值是不会触发作用的,而通过triggerRef函数来手动调用trigger,就可以手动触发一次作用(视图更新或其他逻辑),即使value属性值的是多层对象结构也是有效的。

总结

通过对Refs相关API的源码阅读,实际上可以看出相关API背后所做的工作。相对于reactive,ref等API存在的意义是什么?个人觉得最核心的点是:

reactive实现对复杂类型(对象、数组等)的代理处理,ref等API支持对原始值的代理处理,而且在保持响应性的前提下通过value属性支持可变

至于ref处理复杂类型,本质上与reactive并没有什么区别。相对来说ref的使用更加轻量化,这是它的优点之一,ref有没有缺点呢?个人觉得还是存在的,对于不理解其背后逻辑前提下相关场景ref的使用相对来说会让人有些困惑。

这里概括下ref:

所谓的ref对象就是提供一个对value属性进行响应的普通对象(需要注意的是ref对象不是Proxy对象)。如果value属性的值是复杂类型,ref API会实现深度代理,shallowRef、customRef只是浅层代理,当然可以通过triggerRef手动触发响应作用来解决浅层代理的问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值