ref函数

Vue2 中的ref

首先我们回顾一下 Vue2 中的 ref。

ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例:

javascript 复制代码
<!-- `vm.$refs.p` will be the DOM node -->
<p ref="p">hello</p>

<!-- `vm.$refs.child` will be the child component instance -->
<child-component ref="child"></child-component>

其实也就是给元素或者是子组件打上标记,然后通过在父组件中 通过 this.refs.xxx拿到这个 DOM元素或者是组件实例,进而操作 DOM 或者访问组件实例。

在官方文档上声明,ref是一个特殊的属性,$refs是一个对象,持有注册过 ref attribute 的所有 DOM 元素和组件实例。

Vue3 setup 中直接定义的数据为什么改变之后,视图不同步

如果我按照上一节 setup 中的模式 ,直接创建变量且赋基础值,然后试图改变变量的值,我们看看会发生什么

javascript 复制代码
<template>
  <p>{{ name }}</p>
  <button @click="changeName">改变名称</button>
</template>

<script>
export default {
  name: "App",
 
  setup() {
    let name = "al";

    function changeName() {
      name = '汤圆仔'
    }

    return {
      changeName,
      name,
    };
  },
};
</script>

但是,在我点击按钮之后,我发现页面上的名称并没有发生改变。造成的原因可能有两个,

第一个可能是,数据改变了,但是视图没更新。第二种则是,数据本身就没改变所以不更新视图。

验证:改变数据之后,直接打印修改后的变量

从这里可以看到,我们修改的数据其实时发生了改变的,但是页面上却没有更新,这在 Vue2 中是不会存在的,因为 Vue2 中定义在 data 中的数据时响应式的,所以我们可以得出下面这个结论:那就是 直接在 setup 中声明的变量,不是响应式的。

为了解决这一个问题,Vue3 推出了 ref 函数 用来将 setup 中定义的变量转化为响应式。

Vue3 中的 ref 函数

首先,Vue3 中的 ref 是一个函数,区别于Vue2 中的ref是一个特殊属性。作用是将数据转化为响应式。在使用时,需要引入,然后将要转化的数据传入到 ref() 函数中。

javascript 复制代码
<template>
  <p>{{ name }}</p>
  <button @click="changeName">改变名称</button>
</template>

<script>
import { ref } from 'vue'
export default {
  name: "App",
 
  setup() {
    let name = ref("al");

    function changeName() {
      name = '汤圆仔'
      console.log(name,'name');
    }

    return {
      changeName,
      name,
    };
  },
};
</script>

到了这一步,我们再次点击按钮 ,发现还是同样的结果,数据改变了,但是视图没刷新。不是说好的 通过 ref() 函数就能将数据转化为响应式的么,我这都转化了,为啥还是这样?

ref() 函数处理基本数据类型

对于上面的 基本数据类型 name,我们可以在改变值之前,先看看这个 name 变量,被 ref() 函数处理成了什么样子。

可以看到 ref()函数将基本类型数据转化为了一个 ref引用对象(RefImpl对象),同时我们展开对象查看内部属性可以发现存在以下属性。

dep对象暂时猜测和Vue2一致,用来收集分发依赖的。四个带有_前缀的属性一般也是用来给 Vue底层源码使用的,所以这个value一看就是给我们开发者的。

而且我们还可以发现,鼠标放到value属性值的省略号上之后,提示是通过gettrt 方法获得的值,这和Vue2获取响应式数据一致。

然后打开 Prototype 属性,可以看到,针对于这个基础类型的值的 getter 和 setter方法,以及真正的value值。将方法以及初始值放在原型上是利用原型的作用,避免外层数据繁杂。然后将真正的value暴露给外层是为了方便开发者使用。

到这里我们就可以大胆的推论得出:ref() 函数在处理基础类型的值时,通过将其转化为了 RefImpl 引用实例对象后,还是通过 getter 和 setter 来实现响应式的。

得到结论之后还不够,我们要知道怎么去修改或访问通过ref()函数转化的基础数据啊。

在上面通过 name = '汤圆仔' 直接修改属性值被证明是已经行不通了的。因为这样修改之后,相当于是把这个响应式属性直接变成了一个基础类型的值,从而失去了响应式功能。

而看着 RefImpl 引用实例对象中的属性,我们能理解并使用的也就只有 value 属性了。所以当我们修改数据的时候,通过 name.value = '汤圆仔',就能在保证响应式的前提下修改数据了。

javascript 复制代码
function changeName() {
  name.value = '汤圆仔'
  console.log(name,'name');
}

同理,在模板中使用数据的时候,我们好像也应该通过 插值语法的形式 {{ name.value }} 去使用,但是在你真这么做之后,你会发现,页面渲染其实错误了。

html 复制代码
  <p>姓名:{{ name.value }}</p>

这其实是因为,Vue3底层设计考虑到了这一问题,在模板中使用变量,Vue3判断当前为插值语法,且使用的是通过 ref() 函数进行转化过后的响应式数据后,会自动解包,自动读取value值,而不需要开发者手动 xxx.value 去获取属性值。所以,我们还是像以前一样,通过插值语法直接使用该属性即可。

ref() 函数处理引用类型值

上面说的是ref()函数对于基本数据类型的值的处理。但是如果我的数据比较多,那我分别调用ref比较麻烦,所以 ref() 函数也支持传入对象形式的数据

javascript 复制代码
<template>
  <p>姓名:{{ userInfo.name }}</p>
  <p>姓名:{{ userInfo.age }}</p>
  <p>姓名:{{ userInfo.work }}</p>
  <button @click="changeName">改变名称</button>
</template>

<script>
import { ref } from 'vue'
export default {
  name: "App",
 
  setup() {
    let userInfo = ref({
      name: 'al',
      age: 29,
      work:'前端'
    });

    function changeName() {
      console.log(userInfo,'userInfo');
    }

    return {
      changeName,
      userInfo,
    };
  },
};
</script>

点击按钮,控制台打印出当前通过ref()函数转化后的 userInfo 属性,我们能发现返回的还是一个RefImpl引用实例对象,而且 value还是通过 getter转化为响应式的。此时我们不点开 value的值,按照基本类型的处理方式推测一下,此时的value应该是一个对象。

于此同时我们也应该想到一个问题,那就是在Vue2 中,实现了对象的深层响应,那么在Vue3中不可能丢掉这个功能,所以我们可以推断此时 ref()函数对于引用类型的值也做了深层响应式,也应该是针对于引用类型中每个属性都应该返回一个refImpl引用对象实例,以此来保障数据完全深层响应。

那么我们可以推断出,当我们在改变unseInfo内部 name 属性的时候,我们也应该通过 name.value 去修改,也就是说当我们需要修改对象内部属性时,我们需要这样做:先通过 userInfo.value 拿到转化为响应式的 userInfo 对象,然后修改name时,也需要拿到 name 的value去修改

javascript 复制代码
function changeName() {
  console.log(userInfo);
  userInfo.value.name.value = '汤圆仔'
}

但是这时候我们发现页面报错了,且提示信息为:不能在一个字符串 al 中读取 value 属性。也就是说 userInfo.value.name 之后是取不到 value属性的。这么搞就有点混乱了啊,那我到底是加还是不加呢?

为了解决这个问题,我们看看 userInfo.value 到底返回的是个啥玩意。点开 value属性之后我们发现 value 属性并不是一个 refImpl引用实例对象,而是一个 Proxy 代理对象。而且这个代理对象上的每个属性只有键值对对应,并没有所谓的 value 属性,所以这个时候我们就需要明白一个问题:Vue3 对于基础类型和引用类型转化为响应式,用的是不同的底层逻辑。

针对于基本类型的数据,Vue3走的还是和Vue2一样的 defineProperty 的getter、setter的数据劫持的方式实现的响应式。

而针对于引用类型的数据,Vue3 走的则是通过Proxy代理的方式实现的响应式( 下一节仔细讲讲怎么通过Proxy实现引用类型的响应式转化 )

搞明白了上面这个value属性值的问题,当我们需要改变引用类型中的数据时,我们就可以这样做

javascript 复制代码
function changeName() {
  userInfo.value.name = '汤圆仔'
}

总结

ref的作用:定义一个响应式的数据

ref的语法:let xxx = ref('initvalue')

  1. 创建一个 包含响应式数据的引用对象
  2. 在js中操作数据时,需要使用 xxx.value 来修改
  3. 在模板中使用数据时,不需要通过 .value来读取,因为 Vue底层会自动解包

ref的参数:可以是基本类型,也可以是引用类型,但是对于这两种数据,响应式处理是完全不同的两套逻辑

  1. 基本类型数据:依然是通过 Object.defineProperty() 中的 get 与 set 进行数据劫持完成响应式
  2. 引用类型数据:Vue3 内部求助了一个新函数 -- reactive 函数,通过ES6自带的 Proxy 方法完成了响应式( 参考下一节 -- reactive 函数实现引用类型响应式 )
相关推荐
拉不动的猪3 分钟前
前端常见数组分析
前端·javascript·面试
小吕学编程20 分钟前
ES练习册
java·前端·elasticsearch
Asthenia041227 分钟前
Netty编解码器详解与实战
前端
袁煦丞32 分钟前
每天省2小时!这个网盘神器让我告别云存储混乱(附内网穿透神操作)
前端·程序员·远程工作
Mr.app1 小时前
vue mixin混入与hook
vue.js
一个专注写代码的程序媛2 小时前
vue组件间通信
前端·javascript·vue.js
一笑code2 小时前
美团社招一面
前端·javascript·vue.js
懒懒是个程序员2 小时前
layui时间范围
前端·javascript·layui
NoneCoder2 小时前
HTML响应式网页设计与跨平台适配
前端·html
凯哥19702 小时前
在 Uni-app 做的后台中使用 Howler.js 实现强大的音频播放功能
前端