本文转载自微信公众号《修仙之路》阿宝哥。转载本文请联系全栈修真之路公众号。本文是Vue3.0进阶系列的第九篇。RefsAPI经常出现在组合API设置配置项中,例如ref、toRef或toRefs。那么这些常用的API有哪些功能和使用场景呢?它们背后的实现原理是什么?接下来阿宝哥就带着这些问题,和大家一起深入研究响应式RefsAPI。1.ref这个函数接受一个内部值,返回一个响应式的、可变的ref对象,里面包含一个value属性。1.1例子const{ref}=Vueconstskill=ref("Vue3")console.log(skill.value)//Vue3skill.value="Vite2"console.log(skill.value)//Vite21.2函数实现//包/reactivity/src/ref.tsexportfunctionref(value:T):ToRefexportfunctionref(value:T):Ref>exportfunctionref():Refexportfunctionref(value?:unknown){returncreateRef(value)}从上面的代码我们可以看出ref函数的value参数不仅支持基本数据类型,还支持非基本数据类型的参数。在ref函数内部,调用createRef工厂函数来创建ref对象。createRef函数的具体实现如下://packages/reactivity/src/ref.tsfunctioncreateRef(rawValue:unknown,shallow=false){if(isRef(rawValue)){returnrawValue}returnnewRefImpl(rawValue,shallow)}时创建一个ref对象,如果发现rawValue参数本身就是一个ref对象,则直接返回该对象。否则,将调用RefImpl构造函数来创建一个ref对象。以使用ref为例,ref("Vue3")创建的ref对象的内部结构如下图所示:从上图中我们可以清楚的看到ref对象包含__v_isRef,_rawValue和_value等属性。那么这些属性有什么用呢?这里先介绍一下__v_isRef属性的作用。2.isRef该函数用于检查指定值是否为ref对象。2.1使用例子const{ref,isRef}=Vueconstname=ref("Abaoge")console.log(isRef(name))//true2.2函数实现//packages/reactivity/src/ref.tsexportfunctionisRef(r:Ref|unknown):risRefexportfunctionisRef(r:any):risRef{returnBoolean(r&&r.__v_isRef===true)}从上面的代码我们可以看出isRef函数内部是通过r&&r。__v_isRef===true表达式判断参数r是否为ref对象。我们之前分析过ref函数,已经知道ref对象的本质是RefImpl类的一个实例。那么这个实例的__v_isRef成员属性是什么时候设置的呢?我们从源码中寻找这个问题的答案://packages/reactivity/src/ref.tsclassRefImpl{private_value:Tpublicreadonly__v_isRef=true//省略了大部分代码}观察上面的代码,我们可以看出__v_isRef属性是公共只读属性,其值始终为真。好了,现在我们知道了如何创建ref对象,以及如何检查指定值是否为ref对象,下面我们来介绍下一个函数——unref。3.unref这个函数接受一个参数。如果参数是ref对象,则返回对象的内部值,否则返回参数本身。它是val=isRef(val)的语法糖函数吗?值:值。3.1使用示例const{ref,unref}=Vueconstname=ref("阿宝哥")console.log(unref(name))//"阿宝哥"3.2函数实现//packages/reactivity/src/ref.tsexportfunctionunref(ref:T):TextendsRef?V:T{returnisRef(ref)?(ref.valueasany):ref}在unref函数内部,会使用isRef函数判断ref参数是否为ref对象,如果是,则返回ref.value的值,否则返回参数本身。4.toRef这个函数可以用来为源响应对象的属性创建一个新的ref。之后,ref可以传递,它将保持与其source属性的反应连接。4.1使用示例const{reactive,toRef}=Vueconstman=reactive({name:"Abaoge",skill:"Vue3"})constskillRef=toRef(man,'skill');console.log(`skillRef.value:${skillRef.value}`);//skillRef.value:Vue3skillRef.value="Vite2";console.log(`man.skill:${man.skill}`);//man.skill:Vite24.2函数实现//packages/reactivity/src/ref.tsexportfunctiontoRef(object:T,key:K):ToRef{returnisRef(object[key])?object[key]:(newObjectRefImpl(object,key)asany)}toRef函数接受object和key作为两个参数,以及对象参数是非原始类型。函数中会判断object[key]对象是否为ref对象,如果是则直接返回object[key]的值,否则调用ObjectRefImpl类的构造函数并返回该类的实例://packages/reactivity/src/ref.tsclassObjectRefImpl{publicreadonly__v_isRef=true//用于标识ref对象构造函数(privatereadonly_object:T,privatereadonly_key:K){}getvalue(){returnthis._object[this._key]}setvalue(newVal){this._object[this._key]=newVal}}ObjectRefImpl类的定义非常简单。该类包含一个公共的只读属性__v_isRef,用于标识该类的实例是否为ref对象。此外,它还包含用于操作value值的setter和getter方法。5.toRefs该函数用于将响应式对象转换为普通对象,其中结果对象的每个属性都是一个ref,指向原始对象的相应属性。5.1使用示例const{reactive,toRefs}=Vueconstman=reactive({name:"Abaoge",skill:"Vue3"})console.log(toRefs(man));5.2函数实现//packages/reactivity/src/ref.tsexportfunctiontoRefs(object:T):ToRefs{if(__DEV__&&!isProxy(object)){console.warn(`toRefs()expectsareactiveobjectbutreceivedaplainone.`)}constret:any=isArray(object)?newArray(object.length):{}for(constkeyinobject){ret[key]=toRef(object,key)//调用toRef函数为对象的属性创建一个新的ref}returnret}从上面的代码中,toRefs主要用于将Reactive对象转换为普通对象,转换过程中通过调用toRef函数处理对象的各个属性。需要注意的是,toRefs函数为源对象中包含的属性生成refs。如果要为特定属性创建ref,则应使用toRef函数。当toRefs例子的代码执行成功后,控制台会输出如下结果:那么toRefs函数在实际项目中有什么用呢?toRefs在setup函数返回一个reactive对象时非常有用,可以让组件在不失响应性的情况下,解构返回的对象:
在上面的例子中,我们在useLoginInfo函数里面使用toRefs函数来转换响应式man对象。如果直接返回响应式man对象,解构时name和skill的值如下图:通过toRefs函数转换响应式对象man后,解构时name和skill的值如下如下:对比发现使用toRefs函数后,可以在不损失响应性的情况下进行解构操作。6.shallowRef这个函数用来创建一个ref,它跟踪自身.value的变化,但不让它的值响应。6.1使用示例const{shallowRef,isReactive}=VueconstshallowMan=shallowRef({name:"Abaoge",skill:"Vue3"})console.log(isReactive(shallowMan.value))//false6.2函数实现//packages/反应性/src/ref.tsexportfunctionshallowRef