import { defineComponent } from "vue";export default d_vue 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,做了如下操作
申明一个私有属性 _value 内容为泛型T,申明了一个公开只读属性__v_isRef值为true
有一个构造函数constructor,用于构造对象。构造函数接受两个参数:
第一个参数_rawValue,要求是T类型,第二个参数_shallow,默认值为true
提供了两个方法,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是会自动更新的,注意不影响原始数据
总结
小结一下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。