本文已参与「新人创作礼」活动,一起开启掘金创作之路,符合活动条件。
情景
公元2022年的一个夜里,小白独坐窗前,窗外的雨滴滴嗒嗒的下个不停,深深的敲击着小白的内心,他满脸愁容。😣 😣
紧接着,他轻轻的叹了一口气,轻声说道:但愿明日一切顺利。没错!他正在为明日的考试发愁,心中暗想:Ref我还没懂,明天如果考到这题了该如何是好?
不知不觉,小白拿出了纸和笔...
关于ref
ref是vue3新推出的api,它可以**接受一个内部值并返回一个响应式且可变的 ref 对象,ref 对象仅有一个 .value property,指向该内部值。
**,官网的示例如下:
所以,为什么需要ref?
我们都知道,vue3使用的Proxy去代理对象,去实现的响应式。关于Proxy的描述如下图
也就是说,Proxy只能代理对象,无法代理非对象值,比如字符串和布尔值等。像字符串和布尔值,我们可以称他们为原始值
原始值的响应式方案
思考
既然Proxy能代理对象,对象的键值一般是什么? 比如:
const obj={
name:"小白"
}
小白 是字符串, 那我们想要实现原始值的响应式,能不能在原始值的外层包裹一个对象,也就是将原始值做作为对象的键值存在?
比如:
let name="小白"
const obj={
value:name
}
这样修改之后,我们可以通过代理obj这个对象,从而实现name的数据的监听--读取和修改
ref的使用
我们先看官方的使用方式
const name=ref('小白')
console.log(name.value);//小白
name.value="大白"
console.log(name.value);//大白
上面的代码:
可以把ref看成一个函数,传入了一个字符串,然后返回一个name,这个神奇的name有一个value的属性,读取value,就可以输出小白
当修改name.value="大白",可以输出大白
实现ref
我们根据上述的结果反推一下,粗糙的实现一下
function ref(value){
const obj={
value:value
}
return obj
}
ok!第一步已经可以啦,我传入什么,他可以通过返回值的.value 返回我传入的值
第二步,我们修改value的值,然后再次访问,ref函数内部做了一些操作,可以拦截value更新的值,并将其更新后的值返回。
结合之前学过的Proxy代理
我们可以将obj进行代理,value作为obj的一个属性,当每次读取obj.value的时候,就去收集依赖;当修改obj.value的时候,就去触发依赖更新,有了上面的思路,我们可以修改如下:
import {reactive} from 'vue'
//如果使用common js规范,可以使用 const {reactive}=require('vue')导入
const {reactive}=require('vue')
function ref(val){
const warpper={
get value(){
return val
},
set value(newValue){
val=newValue
}
}
return reactive(warpper)
}
let name=ref("小白")
console.log(name.value)
name.value="大白"
console.log(name.value);
或者,结合之前实现的响应式来测试下效果,具体,可以看我前几篇文章,我们也可以使用这种方式查看效果
function ref(val){
let warpper={
get value(){
return val
},
set value(newValue){
val=newValue
}
}
Object.defineProperty(warpper,'_is_ref',{
value:true,
writable: false
})
//这里的实现,相当于reactive的实现
let newObj=new Proxy(warpper,{
get(target,key){
track(target,key)
return Reflect.get(target,key)
},
set(target,key,value){
Reflect.set(target,key,value)
trigger(target,key)
return true
},
})
return newObj
}
let name=ref('小白')
console.log(obj.value);
obj.value="大白"
console.log(obj.value);
效果如图
实现了!yeah! 😄 😄
优化
ref基本的功能已经实现,但是我们回过头来考虑一下,我们平时在业务开发的时候,是怎么写的
//test.vue
<template>
<div>{{name}}</div>
</template>
<script lang='ts'>
import { ref} from 'vue'
export default {
setup() {
let name=ref('小白')
return {
name
}
}
};
</script>
通过setup函数导出的name值,是可以直接通过name属性去访问的,不需要 .value,这样可以减少开发者的心智负担。先抛去实现不看,我们看如下的场景
const name1=reactive({
value:"小白"
})
const name2=ref(大白')
将name1 和name2 都渲染到template 模板上,vue是如何区分 是reactive对象,还是ref对象呢,
也就是说,如果是reactive对象,我就不处理,直接原数据返回,如果是ref对象,我需要访问到.value,然后渲染。
我们可以看下vue3的ref对象上都有哪些属性
//test.js node 环境下
const {reactive,ref}=require('vue')
const name=ref('小白')
console.log(name);
注意看,有一个 __v_isRef: true 字面意思很明确,就是说明,我是一个ref
所以,我们优化一下ref代码之后如下:
import {reactive} from 'vue'
//如果使用common js规范,可以使用 const {reactive}=require('vue')导入
const {reactive}=require('vue')
function ref(val){
const warpper={
get value(){
return val
},
set value(newValue){
val=newValue
}
}
//新增
Object.defineProperty(warpper,'__v_isRef',{
value:true
})
return reactive(warpper)
}
let name=ref("小白")
console.log(name.value)
name.value="大白"
console.log(name.value);
这样,在后续的处理中,我们就可以利用这个属性判断是ref还是reactive,从而控制是否需要读取.value的值去渲染了