手拉手带你了解Vue3的 ref 和相关函数以及计算属性(一)

简介: 前不久的一篇文章:手拉手带你了解Vue3的reactive和相关函数 已经对reactive以及相关的几个函数,做了一个综合介绍,今天继续和大家深入学习。

01/ 基础类型的响应性 —— ref

在vue3里面,我们可以通过 reactive 来实现引用类型的响应性,那么基础类型的响应性如何来实现呢?

可能你会想到这样来实现:

const count = reactive({value: 0})
count.value += 1

这么做确实可以实现,而且也很像 ref 的使用方式,都是要 .value 嘛。那么 ref内部 是不是这么实现的呢?

我们先定义两个 ref 的实例并且打印看看。

  const refCount = ref(0) // 基础类型
    console.log('refCount ', refCount )
    const refObject = ref({ value: 0 }) // 引用类型
    console.log('refObject ', refObject )


看一下结果:

37.png

基础类型的 ref

38.png

引用类型的 ref

我们都知道 reactive 是通过 ES6 的 Proxy 来实现的,基础类型的 ref 显然和 Proxy 没啥关系,而引用类型的 ref 是先把原型变成 reactive, 然后再挂到 value 上面。这样看来,和我们的猜测不太一样呢,那么 ref 到底是如何实现的呢?我们可以看一下 ref 的源码。


02/ref 的源码

代码来自于 vue.global.js ,调整了一下先后顺序。

  function ref(value) {
      return createRef(value);
  }
  function createRef(rawValue, shallow = false) {
      if (isRef(rawValue)) {
          return rawValue;
      }
      return new RefImpl(rawValue, shallow);
  }
  class RefImpl {
      constructor(_rawValue, _shallow = false) {
          this._rawValue = _rawValue;
          this._shallow = _shallow;
          this.__v_isRef = true;
          this._value = _shallow ? _rawValue : convert(_rawValue); // 深层 ref or 浅层ref
      }
      get value() {
          track(toRaw(this), "get" /* GET */, 'value');
          return this._value;
      }
      set value(newVal) {
          if (hasChanged(toRaw(newVal), this._rawValue)) {
              this._rawValue = newVal;
              this._value = this._shallow ? newVal : convert(newVal);
              trigger(toRaw(this), "set" /* SET */, 'value', newVal);
          }
      }
  }
  const convert = (val) => isObject(val) ? reactive(val) : val;


  • ref 这是我们使用的函数,里面使用 createRef 来创建一个实例。
  • createRef 做一些基础判断,然后进入主题,正式创建ref。这里还可以创建 shallowRef。
  • RefImpl 这个才是主体,显然这是 ES6 的 class,constructor 是初始化函数,依据参数创建一个实例,并且设置实例的属性。这个和上面 ref 的打印结果也是可以对应上的。整个class的代码也是非常简单,设置几个“内部”属性,记录需要的数据,然后设置“外部”属性 value,通过setter、getter 实现对 value 的操作拦截,set 里面主要是 trigger 这个函数,由它调用模板的自动刷新的功能。
  • convert 很显然,判断一下参数是不是 object,如果是的话,变成 reactive 的形式。这个就可以解释,引用类型的 ref 是如何实现响应性的,明显是先变成 reactive,然后在挂到 value 上面(挂之前判断一下是不是浅层的)。

03/ ref 和 reactive 的关系

通过打印结果的对比以及分析源码可以发现:

  • 基础类型的 ref 和 reactive 没有任何关系。
  • 引用类型的 ref ,先把 object 变成 reactive ,即利用 reactive 来实现引用类型的响应性。

关系就是这样的,千万不要再混淆了。

04/ shallowRef

浅层响应式,只监听 .value 的变化,真简单类型的响应式。

function shallowRef(value) {
      return createRef(value, true); // true 浅层
  }


通过源码我们可以发现,在把引用类型挂到 value 之前,先判断一下是不是浅层的,如果是浅层的,并不会变成 reactive,而是直接把原来的对象挂在 value 上面,shallowRef 和 ref 的区别就在于这一点。

我们写几个实例看看效果:


 setup () {
     // 浅层的测试 
    // 基础类型
    const srefCount = shallowRef(0)
    console.log('refCount ', srefCount )
    // 引用类型
    const srefObject = shallowRef({ value: 0 })
    console.log('refObject ', srefObject )
    // 嵌套对象
    const srefObjectMore = shallowRef({ info: {a: 'jyk'} })
    console.log('shallowRef ', srefObjectMore )
    // reactive 的 shallowRef
    const ret = reactive({name: 'jyk'})
    const shallowRefRet = shallowRef(ret)
    console.log('shallowRefRet ', shallowRefRet )
    // ==================== 事件 ==================
    // 修改基础类型
    const setNumber = () => {
      srefCount.value = new Date().valueOf()
      console.log('srefCount ', srefCount )
    }
    // 修改引用类型的属性
    const setObjectProp = () => {
      srefObject.value.value = new Date().valueOf()
      console.log('srefObject ', srefObject )
    }
    // 修改引用类型的value
    const setObject = () => {
      srefObject.value = { value: new Date().valueOf() }
      console.log('srefObject ', srefObject )
    }
    // 修改嵌套引用类型的属性
    const setObjectMoreProp = () => {
      srefObjectMore.value.info.a = new Date().valueOf()
      console.log('srefObjectMore ', srefObjectMore )
    }
    // 修改嵌套引用类型的value
    const setObjectMore = () => {
      srefObjectMore.value = { qiantao: 1234567 }
      console.log('srefObjectMore ', srefObjectMore )
    }
    // 修改reactive 的浅层ref
    const setObjectreactive = () => {
      shallowRefRet.value.name = '浅层的reactive'
      console.log('shallowRefRet ', shallowRefRet )
    }
  }


看看结果:

39.png

浅层的ref

测试了一下响应性:

  • 基础类型 srefCount 有响应性;
  • 引用类型 srefObject 的属性没有响应性,但是直接修改 .value 是有响应性的。
  • 嵌套的引用类型 srefObjectMore ,属性和嵌套属性都是没有响应性的,但是直接修改 .value 是有响应性的。
  • reactive 套上 shallowRef ,然后修改 shallowRef.value.属性 = xxx ,也是可以响应的,所以浅层的ref 也不绝对,还要看内部结构。

05/ triggerRef

手动执行与 shallowRef 关联的任何效果。

官网的中文版里面写的很绕,其实就是 让 shallowRef 原本不具有响应性的部分,具有响应性。shallowRef 是浅层的,深层部分是没有响应性的,那么如果非得让这部分也具有响应性呢?这时候可以用 triggerRef 来实现。好吧,目前还没有想到有啥具体的应用场景,因为一般都直接简单粗暴的用 ref 或者 reactive 了,全都自带响应性。

测试了各种情况,发现 triggerRef 并不支持 shallowReactive,还以为能支持呢。(或许是我写的测试代码有问题吧,官网也没提 shallowReactive)

基于上面的例子,在适当的位置加上 triggerRef(xxx)就可以了。


 setup () {
    // 引用类型
    const srefObject = shallowRef({ value: 0 })
    // 嵌套对象
    const srefObjectMore = shallowRef({ value: {a: 'jyk'} })
    // reactive 的 shallowRef
    const ret = reactive({name: 'reactive'})
    const shallowRefRet = shallowRef(ret)
    // 浅层的reactive
    const myShallowReactive = shallowReactive({info:{name:'myShallowReactive'}})
    const setsRet = () => {
      myShallowReactive.info.name = new Date().valueOf()
      triggerRef(myShallowReactive)  // 修改后使用,不支持
   }
    // ==================== 事件 ==================
    // 修改引用类型的属性
    const setObjectProp = () => {
      srefObject.value.value = new Date().valueOf()
      triggerRef(srefObject) // 修改后使用
    }
    // 修改引用类型的value
    const setObject = () => {
      srefObject.value = { value: new Date().valueOf() }
      triggerRef(srefObject)
   }
    // 修改嵌套引用类型的属性
    const setObjectMoreProp = () => {
      srefObjectMore.value.value.a = new Date().valueOf()
      triggerRef(srefObjectMore)
  }
    // 修改嵌套引用类型的value
    const setObjectMore = () => {
      srefObjectMore.value.value = { value: new Date().valueOf() }
      triggerRef(srefObjectMore)
    }
    // 修改reactive 的浅层ref
    const setObjectreactive = () => {
      shallowRefRet.value.name = '浅层的reactive' + new Date().valueOf()
      triggerRef(shallowRefRet)
    }
    return {
      srefObject, // 引用类型
      srefObjectMore, // 嵌套引用类型
      shallowRefRet, // reactive 的浅层ref
      myShallowReactive, // 浅层的reactive
      setsRet, // 修改浅层的reactive
      setObjectProp, // 修改引用类型的属性
      setObject, // 修改引用类型的value
      setObjectMoreProp, // 修改嵌套引用类型的属性
      setObjectMore, // 修改嵌套引用类型的value
      setObjectreactive // 试一试reactive的浅层ref
    }
  }

深层部分,不使用 triggerRef 就不会刷新模板,使用了 triggerRef 就可以刷新模板。话说,为啥会有这个函数?

目录
打赏
0
0
0
0
6
分享
相关文章
斩获开发者口碑!SnowAdmin:基于 Vue3 的高颜值后台管理系统,3 步极速上手!
SnowAdmin 是一款基于 Vue3/TypeScript/Arco Design 的开源后台管理框架,以“清新优雅、开箱即用”为核心设计理念。提供角色权限精细化管理、多主题与暗黑模式切换、动态路由与页面缓存等功能,支持代码规范自动化校验及丰富组件库。通过模块化设计与前沿技术栈(Vite5/Pinia),显著提升开发效率,适合团队协作与长期维护。项目地址:[GitHub](https://github.com/WANG-Fan0912/SnowAdmin)。
440 5
Vue 3 中的 nextTick 使用详解与实战案例
Vue 3 中的 nextTick 使用详解与实战案例 在 Vue 3 的日常开发中,我们经常需要在数据变化后等待 DOM 更新完成再执行某些操作。此时,nextTick 就成了一个不可或缺的工具。本文将介绍 nextTick 的基本用法,并通过三个实战案例,展示它在表单验证、弹窗动画、自动聚焦等场景中的实际应用。
192 17
基于 ant-design-vue 和 Vue 3 封装的功能强大的表格组件
VTable 是一个基于 ant-design-vue 和 Vue 3 的多功能表格组件,支持列自定义、排序、本地化存储、行选择等功能。它继承了 Ant-Design-Vue Table 的所有特性并加以扩展,提供开箱即用的高性能体验。示例包括基础表格、可选择表格和自定义列渲染等。
202 6
Vue 2 与 Vue 3 的区别:深度对比与迁移指南
Vue.js 是一个用于构建用户界面的渐进式 JavaScript 框架,在过去的几年里,Vue 2 一直是前端开发中的重要工具。而 Vue 3 作为其升级版本,带来了许多显著的改进和新特性。在本文中,我们将深入比较 Vue 2 和 Vue 3 的主要区别,帮助开发者更好地理解这两个版本之间的变化,并提供迁移建议。 1. Vue 3 的新特性概述 Vue 3 引入了许多新特性,使得开发体验更加流畅、灵活。以下是 Vue 3 的一些关键改进: 1.1 Composition API Composition API 是 Vue 3 的核心新特性之一。它改变了 Vue 组件的代码结构,使得逻辑组
508 0
vue2和vue3的响应式原理有何不同?
大家好,我是V哥。本文详细对比了Vue 2与Vue 3的响应式原理:Vue 2基于`Object.defineProperty()`,适合小型项目但存在性能瓶颈;Vue 3采用`Proxy`,大幅优化初始化、更新性能及内存占用,更高效稳定。此外,我建议前端开发者关注鸿蒙趋势,2025年将是国产化替代关键期,推荐《鸿蒙 HarmonyOS 开发之路》卷1助你入行。老项目用Vue 2?不妨升级到Vue 3,提升用户体验!关注V哥爱编程,全栈开发轻松上手。
251 2
高效工作流:用Mermaid绘制你的专属流程图;如何在Vue3中导入mermaid绘制流程图
mermaid是一款非常优秀的基于 JavaScript 的图表绘制工具,可渲染 Markdown 启发的文本定义以动态创建和修改图表。非常适合新手学习或者做一些弱交互且自定义要求不高的图表 除了流程图以外,mermaid还支持序列图、类图、状态图、实体关系图等图表可供探索。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解
onMounted作为vue3中最常用的钩子函数之一,能够灵活、随心应手的使用是每个Vue开发者的必修课,同时根据其不同写法的特性,来选择最合适最有利于维护的写法。博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
Pinia 如何在 Vue 3 项目中进行安装和配置?
Pinia 如何在 Vue 3 项目中进行安装和配置?
327 4
管理数据必备;侦听器watch用法详解,vue2与vue3中watch的变化与差异
一篇文章同时搞定Vue2和Vue3的侦听器,是不是很棒?不要忘了Vue3中多了一个可选项watchEffect噢。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问