vue3.0 ref 函数

先写一个基础的vue3模板

<template>
  <div>
    <p>个人信息</p>
    <p>姓名:{{ name }}</p>
    <p>年龄:{{ age }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  name: "Home",
  components: {},
  setup() {
    const name = "张三";
    const age = 18;
    return {
      name,
      age
    };
  },
});
</script>

页面正常显示
在这里插入图片描述
这时候加一个定时器,修改张三的年龄

<template>
  <p>个人信息</p>
  <p>姓名:{{ name }}</p>
  <p>年龄:{{ age }}</p>
</template>

<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
  name: "Home",
  components: {},
  setup() {
    let name = "张三";
    let age = 18;
    setInterval(function () {
      age++;
      console.log("age", age);
    }, 1000);
    return {
      name,
      age,
    };
  },
});
</script>

在这里插入图片描述
会发现控制台正常打印,但是数据不是响应式的,也就是数据发生了变化,页面却没有更新。原因是我们仅仅定义了一个string类型和number类型的普通数据,并不是一个vue响应式数据。现在引入vue3中一个函数,ref,官方定义如下:

接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property .value

根据vue官方文档的提示,通过ref包裹普通数据,通过.value能够拿到响应式数据,我们可以先打印一下name

<template>
  <p>个人信息</p>
  <p>姓名:{{ name }}</p>
  <p>年龄:{{ age }}</p>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
  name: "Home",
  components: {},
  setup() {
    let name = ref("张三");
    let age = ref(18);
    console.log("name", name);
    return {
      name,
      age,
    };
  },
});
</script>

发现’name’是被refImpl类包裹的一个实例对象,这个类可以等会研究研究,先看实例对象上的一个属性:value,值为(…),并且提示’invoke property getter’,意思是调用属性 getter,这说明vue3.0的ref函数是通过vue2.x数据响应式方法Object.defineProperty()作为响应式数据的手段。

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。–MDN

在这里插入图片描述
再继续往下看原型链_proto_,打印如下图,其中包含了Object.defineProperty的get和set方法,来读/写value属性完成一个ref数据响应式。_proto_下还存在了一个value属性,值为’张三’,通过数据代理传递到最外层,方便调用。(可以理解成vue2.x中,vm.data与vm._data都能获取data中定义的数据,在template中写value比_data.value更加方便)

数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)

在这里插入图片描述

<template>
  <p>个人信息</p>
  <p>姓名:{{ name }}</p>
  <p>年龄:{{ age }}</p>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
  name: "Home",
  components: {},
  setup() {
    let name = ref("张三");
    let age = ref(18);
    console.log("name", name);
    return {
      name,
      age,
    };
  },
});
</script>

通过控制台发现name是被refImpl包裹的一个对象
在这里插入图片描述
再通过.value获取到name值

<template>
  <p>个人信息</p>
  <p>姓名:{{ name }}</p>
  <p>年龄:{{ age }}</p>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
  name: "Home",
  components: {},
  setup() {
    let name = ref("张三");
    let age = ref(18);
    setInterval(function () {
      age.value++;//被ref包裹的数据,需要通过.value获取值
      console.log("age", age);
    }, 1000);
    return {
      name,
      age,
    };
  },
});
</script>

此时数据被ref包裹成为vue响应式数据,页面也可以正常更新。
在这里插入图片描述

此时一个响应式的ref数据便完成了,此时再回去看为什么要通过.value的形式获取值,先来看一下refImpl,全称是reference Implement,可以理解成引用对象,来看一下RefImpl关键源码

class RefImpl<T> {
  private _value: T
  public readonly __v_isRef = true
  constructor(private _rawValue: T, private readonly _shallow = false) {
    this._value = _shallow ? _rawValue : convert(_rawValue)
  }
  get value() {
    track(toRaw(this), TrackOpTypes.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), TriggerOpTypes.SET, 'value', newVal)
    }
  }
}

可以看见RefImpl class传递了一个泛型类型T,做了如下操作

  1. 申明一个私有属性 _value 内容为泛型T,申明了一个公开只读属性__v_isRef值为true

  2. 有一个构造函数constructor,用于构造对象。构造函数接受两个参数:
    第一个参数_rawValue,要求是T类型,第二个参数_shallow,默认值为true

  3. 提供了两个方法,get value(){}和set value(){},分别对应私有属性的读写操作,用于供外界操作value

当通过它构建对象时,会给对象的_value属性赋值为_rawValue或者convert(_rawValue)
再看convert源码如下:

const convert = <T extends unknown>(val: T): T =>
  isObject(val) ? reactive(val) : val

最终Vue会根据传入的数据是不是对象isObject(val),如果是对象本质调用的是reactive,否则返回原始数据。
现在思考一个问题,通过ref包装的结果,当原始数据改变时会触发界面更新吗?即原始数据和返回的响应式数据是否有关联?
修改一段代码:

<template>
  <p>个人信息</p>
  <p>姓名:{{ name }}</p>
  <p>年龄:{{ age }}</p>
  <button @click="add">++</button>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
  name: "Home",
  components: {},
  setup() {
    let name = ref("张三");
    let age = 18;
    let curAge = ref(age);
    console.log("name", name);
    const add = () => {
      age++;
      console.log("age", age);
      console.log("curAge", curAge.value);
    };
    return {
      name,
      age,
      add,
    };
  },
});
</script>

再打印一下
在这里插入图片描述
实例发现,当原始数据发生修改时,并不会影响响应式数据,更不会触发界面UI的更新。
再修改一段代码,让curAge.value++

<template>
  <p>个人信息</p>
  <p>姓名:{{ name }}</p>
  <p>年龄:{{ curAge }}</p>
  <button @click="add">++</button>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
  name: "Home",
  components: {},
  setup() {
    let name = ref("张三");
    let age = 18;
    let curAge = ref(age);
    console.log("name", name);
    const add = () => {
      curAge.value++;
      console.log("age", age);
      console.log("curAge", curAge.value);
    };
    return {
      name,
      age,
      curAge,
      add,
    };
  },
});
</script>

打印如下
在这里插入图片描述
实例发现如果响应式数据发生改变,对应界面UI是会自动更新的,注意不影响原始数据
总结
小结一下:

  1. ref本质是将一个数据变成一个对象,这个对象具有响应式特点
  2. ref接受的原始数据返回的对象本质都是RefImpl类的实例
  3. 无论传入的原始数据时什么类型,当原始数据发生改变时,并不会影响响应数据,更不会触发UI的更新。但当响应式数据发生改变,对应界面UI是会自动更新的,注意不影响原始数据。所以ref中,原始数据和经过ref包装后的响应式数据是无关联的。
  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值