Vue自动脱ref的原理

327 阅读2分钟

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

一.关于ref

ref使vue3为了实现对原始值的响应而做出的方案,如何实现一个ref,感兴趣的可以看一下,ref具体的使用方式,我们贴出官网的示例代码

const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1

我们在vue文件中一般这样写


<template>
  <div>{{count}}</div>
  <button @click="changeCount"></button>
</template>

<script lang='ts'>
import { ref} from 'vue'

export default {
      setup() {
        let count=ref(1)
         function changeCount(){
           count.value++
         }
         return {
            count,
            changeCount
             
         }

      }

  };
</script>

以上代码:

通过ref函数定义了一个count值,初始值是1,通过点击按钮实现count+1

我们知道:通过ref函数定义的数据,有一个特点,就是必须使用.value访问,但是我们在以.vue文件中却不需要使用.value的 方式访问,vue做了什么呢?

二.思考及分析

ref的原理:

通过obj将要代理的原始值包裹起来,键为value,值为原始值,然后通过Proxy去代理obj对象 setup函数返回的是一个对象,比如上面的代码,本质上返回的是

return{
    count:ref(1),// 也是:count:{value:1 }
    chnageCount:function(){
    
    }

    }

那等于说,当用户访问count属性的时候,再通过Proxy代理count的值,(ps:count值本质上是一个对象)只需要再往下访问一层.value,直接取.value的值不就行了。我们可以打印一下通过ref函数,定义的对象是啥

//test.js
const {ref}=require('vue')
let count =ref(1)
console.log(count)

image.png

结果:我们通过Proxy去代理return返回的对象,如果对象的value值是一个ref,就读取.value的值,否则就不读取,直接返回。

三.实现

function proxyObj(target){
    let obj=new Proxy(target,{
        get(target,key){
            let value=Reflect.get(target,key)
            //这里区分获取到的value是不是一个ref
            if(value['_is_ref']){
                return value.value
            }else{
                return value
            }

            
        },
        set(target,key,nVal){
            let value=target[key]
                //修改value的时候,也要做一下判断,此时的value={value:1},如果是ref,给value.value赋值 
            if(value['_is_ref']){
                value.value=nVal
                return true
            }else{
                return Reflect.set(target,key,nVal)
            }
           
            
        }
    })
    return obj
}

代码分析:

以上代码通过proxy代理一个对象target,以count为例,此时代理的时一个对象{count:{value:1,"_is_ref":true} }

1.当我们读取属性是count的时候,会触发get函数,此时我们去判断target[key]是否有_is_ref属性,如果有,我们把value的值取出,给到count

2.当我们修改count属性值的时候,会触发set函数,我们依旧要判断一下,是不是有_is_ref属性,如果有,那我门将新值nval赋值给.vaue,而不是{value:1,"_is_ref":true}。

这样我们在template模板中就可以不使用.value去访问了