【Vue3】refs (持续更新)
ref
接受一个值,返回一个响应式并且可变的 ref 对象。ref 对象具有指向值的单个属性的值。
ref数据发生改变,界面会自动更新。
实例
<template>
<div class="container">
{{ count }}
<br>
<button @click="add">
点击
</button>
</div>
</template>
<script lang="ts" setup>
// import { ref, reactive } from 'vue'
import { ref } from 'vue'
const data: number = 0
const count = ref(data)
function add () {
count.value++
}
console.log('----')
console.log(count)
console.log(count.value.constructor)
// interface OriginObj {
// val: number
// }
// const origin:OriginObj = { val: 0 }// 原始数据为对象
// const count = ref(origin)
// // const count = reactive(origin)
// function add () {
// count.value.val++
// }
// console.log('----')
// console.log(count)
// console.log(count.value.constructor)
</script>
<style scoped lang="less">
.container {
background: #fff;
width: 100%;
height: 500px;
margin: 20px 20px;
}
</style>
传递的原始数据orgin可以是原始值也可以是引用值,但是需要注意,如果传递的是原始值,指向原始数据的那个值保存在返回的响应式数据的.value中,如上count.value;如果传递的一个对象,返回的响应式数据的.value中对应有指向原始数据的属性,如上count.value.val
我们看下打印的结果:
console.log(count)
console.log(count.constructor)
首先,如果传入的是原始值数据,返回的结果如下:
然后,如果传入的是对象,返回结果如下:
对比发现,不管传递数据类型的数据给ref,无论是原始值还是引用值,返回的响应式数据对象本质都是由RefImpl类构造出来的对象。但不同的是里头的value,一个是原始值,一个是Proxy对象。
RefImpl源码
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly _shallow: boolean) {
this._rawValue = _shallow ? value : toRaw(value)
this._value = _shallow ? value : toReactive(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
newVal = this._shallow ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : toReactive(newVal)
triggerRefValue(this, newVal)
}
}
}
可以看见RefImpl class传递了一个泛型类型T,里头具体包含:
有个私有属性_value, _rawValue,类型为T,有个公开只读属性__v_isRef值为true,有个dep属性
有两个方法,get value(){}和set value(){},分别对应私有属性的读写操作,用于供外界操作value
有一个构造函数constructor,用于构造对象。构造函数接受两个参数:
第一个参数value,要求是T类型
第二个参数_shallow,默认值为true
总结:
- ref本质是将一个数据变成一个对象,这个对象具有响应式特点
- ref接受的原始数据可以是原始值也可以是引用值,返回的对象本质都是RefImpl类的实例
- 无论传入的原始数据时什么类型,当原始数据发生改变时,并不会影响响应数据,更不会触发UI的更新。但当响应式数据发生改变,对应界面UI是会自动更新的,注意不影响原始数据。所以ref中,原始数据和经过ref包装后的响应式数据是无关联的
isRef
检查值是否为一个 ref 对象。
unref
如果参数为 ref,则返回内部值,否则返回参数本身,这是 val = isRef(val) ? val.value : val
实例
<template>
<p>{{count}}</p>
</template>
<script setup>
import { ref, isRef, unref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
console.log('-------')
const info = ref({name :'名字',info:{age:18,height:1111,}})
const infos = {name :'名字',info:{age:18,height:1111, }}
console.log('isRef:')
console.log(isRef(info))
console.log('unref info:')
console.log(unref(info))
console.log('unref infos:')
console.log(unref(infos))
</script>
toRef
可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
toRef接收两个参数,第一个参数是哪个对象,第二个参数是对象的哪个属性。
toRef当数据发生改变是,界面不会自动更新。
实例
<template>
<div>
<button @click="change">点击更新obj</button>
<span>obj.name: {{obj.name}} , </span>
<span>newObj: {{newObj}}</span>
</div>
</template>
<script setup>
import { toRef } from 'vue'
let obj = {name : 'alice', age : 12};
// let newObj= ref(obj.name);
let newObj= toRef(obj, 'name');
const change = () => {
newObj.value = 'Tom';
console.log('obj');
console.log(obj);
console.log('newObj');
console.log(newObj);
}
</script>
toRefs
有的时候,我们希望将对象的多个属性都变成响应式数据,并且要求响应式数据和原始数据关联,并且更新响应式数据的时候不更新界面,就可以使用toRefs,用于批量设置多个数据为响应式数据。
实例
<template>
<div>
<button @click="change">点击更新obj</button>
<span>obj.name: {{obj.name}} , </span>
<span>newObj.name: {{newObj.name}}</span>,
<span>newObj.age: {{newObj.age}}</span>,
</div>
</template>
<script setup>
import { toRefs } from 'vue'
let obj = {name : 'alice', age : 12};
let newObj = toRefs(obj)
const change = () => {
newObj.name.value = 'Tom';
newObj.age.value = 30;
console.log('obj');
console.log(obj);
console.log('newObj');
console.log(newObj);
}
</script>
customRef
创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并且应该返回一个带有 get 和 set 的对象。
<template>
<div>
customRef的例子:
<span>{{obj}}</span>
<button @click="inc">button</button>
</div>
</template>
<script>
import { customRef } from 'vue';
// customRef用于 自定义ref
// 自定义 ref 需要提供参数传值
function myRef(value) {
// 自定义 ref 需要提供 customerRef 返回值
// customer 需要提供一个函数作为参数
// 该函数默认带参数 track 和 trigger ,都是方法。
return customRef((track, trigger) => {
return {
// customer 需要提供一个对象 作为返回值
// 该对象需要包含 get 和 set 方法。
get() {
// track 方法放在 get 中,用于提示这个数据是需要追踪变化的
track();
console.log('get', value);
return value;
},
// set 传入一个值作为新值,通常用于取代 value
set(newValue) {
console.log('set', newValue);
value = newValue;
// 记得触发事件 trigger,告诉vue触发页面更新
trigger();
}
}
})
}
export default {
name: 'App',
setup() {
const obj = myRef(123);
function inc() {
obj.value += 1;
}
return {
obj,
inc
};
}
}
</script>
假如我们去掉了 track 和 trigger ,那么将失去视图层追踪变化的能力(可以显示控制)。如果需要进行视图层追踪,请注意在 set 中 value 发生变化后即刻执行 trigger。
shallowRef,triggerRef
创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的。
shallowRef生成的数据只有第一层key是响应式的,因为ref数据有一个特点,会默认加一个value属性,传入的对象默认会作为value的属性值,所有用shallowRef生成的响应式数据只有value是响应的,是能达到响应式效果的。如果想将页面UI改变,我们可以用triggerRef。
triggerRef: 手动执行与 shallowRef 关联的任何作用 (effect)。
<template>
<div>
shallowRef的例子:
<span>refNum:{{ refNum.age }}, </span>
<span>shallowRefNum:{{ shallowRefNum.age }}, </span>
<button @click="change">点击数字加1</button>
</div>
</template>
<script setup>
import { ref, shallowRef, triggerRef } from 'vue'
let refNum =ref({age:18})
let shallowRefNum = shallowRef({age:18})
const change = () => {
// refNum.value.age ++;
shallowRefNum.value.age++
triggerRef(shallowRefNum)
}
</script>