春眠不觉晓,处处闻啼鸟,夜来风雨声,Ref知多少

247 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路,符合活动条件。

情景

公元2022年的一个夜里,小白独坐窗前,窗外的雨滴滴嗒嗒的下个不停,深深的敲击着小白的内心,他满脸愁容。😣 😣

紧接着,他轻轻的叹了一口气,轻声说道:但愿明日一切顺利。没错!他正在为明日的考试发愁,心中暗想:Ref我还没懂,明天如果考到这题了该如何是好?

不知不觉,小白拿出了纸和笔...

关于ref

ref是vue3新推出的api,它可以**接受一个内部值并返回一个响应式且可变的 ref 对象,ref 对象仅有一个 .value property,指向该内部值。

**,官网的示例如下:

image.png

所以,为什么需要ref?

我们都知道,vue3使用的Proxy去代理对象,去实现的响应式。关于Proxy的描述如下图

image.png 也就是说,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);

效果如图

image.png

实现了!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);

image.png

注意看,有一个 __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的值去渲染了