🔥由浅入深,揭秘vue3中ref、setup、template三者的藕断丝连!

前言

大家好,今天打算写一个vue3中的ref函数,当然不单单是封装一个ref函数这么简单,还会在此基础上引申出vuetemplate模板中读取及设置ref值时怎么做到省略.value的写法,要深入理解其中原理又得引申出setup函数给template暴露属性时都做了什么,从而理解三者(setup、template、ref) 之间的藕断丝连。我们这里不讲源码,旨在用最通俗的方式让大家理解其中的实现思路。

ref函数揭秘

vue官方文档ref是这样介绍的:如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。

对其有了基本了解后,我们创建一个myvue.js文件,尝试自己封装一个ref函数:

js 复制代码
import { reactive } from 'vue'
export const ref = (value) => {
  const wrapper = {
    value
  }
  return reactive(wrapper)
}

封装好后,我们尝试在vue页面中引入使用:

js 复制代码
<template>
  <div>
    <span>当前值:{{ count.value }}</span>
    <button @click="increment">加一</button>
  </div>
</template>

<script setup>
// 自己封装的ref
import { ref } from './hooks/myvue'

const count = ref(0)
const increment = () => {
  count.value++
}
</script>

当我们点击加一按钮 时,页面显示的count.value值也同样能够正常刷新。But ,还没完!细心的同学可能已经发现,在template模板中使用count变量时,需要.value才行,我们想要的效果是像vue提供的ref函数 一样,不用.value也能正常读取值。

template中省略.value揭秘

其实不难 ,我们只需在前文自己封装的ref函数里增加一行代码即可!

js 复制代码
import { reactive } from 'vue'
export const ref = (value) => {
  const wrapper = {
    value
  }
  /**
   * 在wrapper上定义一个不可修改且不可迭代的私有属性,
   * vue内部会通过这个__v_isRef私有属性判断是否是ref响应式数据
   */
  // 新增代码
  Object.defineProperty(wrapper, '__v_isRef', {value: true}) 
  return reactive(wrapper)
}

这样我们就能在template中省略.value写法了!!!

到这,虽然功能实现了,但我们不能知其然而不知其所以然,下面说说其实现原理。

setup返回值暴露原理揭秘

我们这次不使用<script setup>语法糖,采用原始的setup函数暴露属性template模板使用:

js 复制代码
import { ref } from './hooks/myvue'
export default {
  setup() {
    const count = ref(0)
    const increment = () => {
      count.value++
    }
    // 将 ref 暴露给模板
    return {
      count,
      increment
    }
  }
}
</script>

从以上可看出,setup的返回值是一个对象 ,而这个对象中的属性之所以能够在template中直接使用,是归功于webpack/vite之类的编译打包工具,在.vue文件编译成js文件中的render函数执行时,依然是通过对象.属性(比如:obj.count)的形式读取setup返回的对象属性,只不过这个setup函数返回的对象经过了Proxy包装,变成了一个响应式对象,在读取/设置 属性时,在get/set方法中拦截判断是否为ref响应式对象,如果是则会自动补全.value并返回/设置相应值。

下面我们通过编写一个proxyRefs包装函数,更直观的了解其中原理。

js 复制代码
const proxyRefs = (obj) => {
  return new Proxy(obj, {
    get(target, key, receiver) {
      // 读取target[key]的值
      const value = Reflect.get(target, key, receiver)
      // __v_isRef为我们标记为ref的属性
      return value.__v_isRef ? value.value : value
    },
    set(target, key, newValue, receiver) {
      const value = target[key]
      if(value.__v_isRef) {
        // 是ref则设置.value值
        value.value = newValue
        // 返回设置成功
        return true
      }
      // 设置target[key]的值,并返回是否设置成功
      return Reflect.set(target, key, newValue, receiver)
    }
  })
}

以上就是我们实现的setup返回对象包装方法,之后我们使用ref时就不再需要.value了!主要是通过Proxyget方法拦截读取操作,如果读取的值是ref则返回其.value值,如果不是则直接返回原始值;set拦截也是同理,目的是在设置ref值时也能省略.value写法。

下面我们通过一个例子测试一下:

js 复制代码
<script>
import { ref, proxyRefs } from './hooks/myvue'
export default {
  setup() {
    const count = ref(0)
    const increment = () => {
      count.value++
    }
    const state = {
      count,
      increment
    }
    
    const state2 = proxyRefs(state)
    console.log(state2.count) // 0
    state2.count++
    console.log(state.count.value)  // 1
    // 将 ref 暴露给模板
    return state
  }
}
</script>

以上我们把setup返回对象通过刚刚我们自己写的proxyRefs方法做一层包装,并赋值给state2,并且我们通过state2.count成功读取和设置了state中的count值。

总结

首先,封装ref函数,并创建一个具有value属性wrapper对象,并把传入的参数赋值给value属性,最后再把wrapper对象通过reactive响应式API,包装成一个深层的响应式对象

其次,为了使ref能够省略.value的写法,我们给wrapper包装对象设置了一个私有属性__v_isRef,由于vue内部对setup函数返回的对象做了Proxy拦截包装,所以在template模板中读取ref的值时,可以省略.value读取及设置ref的值。

最后,我们手写了一个在读取或设置 ref的值时,可以省略.value的包装函数proxyRefs,深入了解setup函数给模板暴露属性时,是如何包装返回的对象给模板使用。

相关推荐
随风一样自由1 小时前
【前端独角兽】刚进入前端领域的前端开发用jQuery还是Vue3?
前端·javascript·vue3·jquery
IMPYLH2 小时前
HTML 的 <data> 元素
前端·html
YHHLAI2 小时前
[特殊字符] 面试中的 Promise —— 从入门到精通
前端·javascript·面试
weixin_BYSJ19873 小时前
SpringBoot + MySQL 乒乓球运动员信息管理系统项目实战--附源码04954
java·javascript·spring boot·python·django·flask·php
沪飘大军4 小时前
ll-llm:一个零依赖、可插拔的 OpenAI 兼容 LLM 代理
javascript
leoZ2314 小时前
Claude 驱动的全栈开发:Spring Boot + Vue 的 BS 架构实践
vue.js·spring boot·架构
kyriewen4 小时前
我扒了 GPT-5.6 的全部编程跑分——有一项 OpenAI 没敢放出来
前端·gpt·ai编程
前端H5 小时前
微前端 v3 架构升级:从加载隔离到状态协同
前端·架构·状态模式
山河木马5 小时前
GPU自动处理专题3-NDC怎么映射成屏幕像素坐标(视口变换)
javascript·webgl·图形学
陆枫Larry6 小时前
小程序包体积优化:用 SVG 图片替换 iconfont,并保留 CSS 控色能力
前端