【V3】Ref基础

179 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情

ref(primitive|Object)

ref接受一个内部值并返回一个响应式且可变的ref对象。ref对象只有一个value属性,指向内部的值。

import {ref} from 'vue'

let msg = ref < string > ('hello world')
let text: string = 'I am Text'

像上面这样,变量msg就被定义成一个响应式的变量,而text则是一个普通的字符串。

开头也说了,ref返回的是一个响应式且可变的ref对象,所以如果我们想要改变msg的值,不能直接给它赋值,而是要给它的value属性赋值。

// msg = 'hi world' // 错误
msg.value = 'hi world' // 正确

一般会使用ref将 原始类型 的值变为响应式的,像字符串、数值、布尔等等,而像对象、数组这些我们则应该使用reactive去将它们转变成响应式的。 但是,我们还是可以使用ref去转换对象和数组的,像下面这样使用

let arr = ref<number[]>([]);
let obj = ref({
    name: "mac",
});
// 改变它们的值
const change = () => {
    arr.value = [1, 2, 3];
    obj.value.name = 'xxx'
};

像上面这样,改变它们的值同样会在页面上反映出来。

为什么ref也可以转变复杂数据类型呢?通过源码可以知道,我们使用ref包裹复杂数据类型的时候,它会调用toReactive,而toReactive则调用reactive,相当于是Vue内部帮我们用reactive去转变了。

image.png

在JS中改变以及使用被ref转变后的响应式变量需要像下面这样使用

let num = ref < number > (1)
const two = num.value + 1 // 使用(读取)
num.value++ // 改变

但是在template中使用则不需要加上.value,因为vue会自动解包。

<div>{{num}}</div>

isRef(r)

判断参数r是否为ref对象

function isRef(r) {
    return !!(r && r.__v_isRef === true);
}

通过源码可以知道,当我们使用ref的时候,它会调用createRef,而createRef则会先调用isRef判断是否已经是ref对象了,如果是的话则直接返回,如果不是再将其转变成ref对象。

function createRef(rawValue, shallow) {
    if (isRef(rawValue)) {
        return rawValue;
    }
    return new RefImpl(rawValue, shallow);
}

shallowRef()

创建一个跟踪自身.value变化的ref,但是不会使其值(.value的值)变成响应式。这个可以用于节省性能。

import {shallowRef} from 'vue'

let msg = shallowRef({
    name: 'mac'
})
const change = () => {
    msg.value.name = 'xxx' // 这样页面上是不会改变的
    // 当然,只是页面上没有跟着变,实际上值已经改变了
}
const change2 = () => {
    msg.value = {name: 'xxx'} // 这样页面也会跟着变
}

triggerRef()

强制更新页面DOM,常和shallowRef搭配使用。比如说,上面我们知道使用shallowRef改变.value后面的值是不会在页面上反映出来的,但是在代码中却是实实在在被改变了,这个时候我们可以使用triggerRef去强制更新DOM,让页面上的值也改变

let msg = shallowRef({
    name: 'mac'
})
const change = () => {
    msg.value.name = 'xxx' // 这样页面上是不会改变的
    triggerRef() // 强制更新,这样页面也改变了
}

这里还有一种情况,看下面的代码

let msg = ref('hello')
let man = shallowRef({
    name: 'mac'
})
const change = () => {
    man.value.name = 'xxx'
    msg.value = 'hi'
}

像上面这种情况,man的name属性值的改变也会在页面上显示出来。通过源码可以知道,triggerRef是调用了triggerRefValue,而通过ref包裹的值在set的时候也会调用triggerRefValue(看最上面的RefImpl类),所以导致shallowRef包裹的值也在页面上更新了。

customRef

自定义ref,这个是一个工厂函数,要求我们返回一个对象,并且实现get和set

image.png

从下面的代码中可以知道,customRef接收一个工厂函数,函数返回一个对象,对象中有get和set,工厂函数factory有两个参数,一个是track,一个是trigger,track用于帮我们收集依赖,而trigger则是帮我们触发更新。

const {get, set} = factory(() => trackRefValue(this), () => triggerRefValue(this));

使用实例

image.png

当我们执行change的时候,改变msg的值,同时触发set,会在控制台输出set。