【Vue3】04-ref 全家桶

129 阅读3分钟

1. ref

1.1 作用

将数据包装成 RefImpl 对象,实现响应式

1.2 用法

<template>
<div>{{ Man }}</div>
<hr>
<button @click="change">修改</button>
</template>

<script setup lang="ts">
import { ref } from 'vue'
type M = {
    name: string
}
const Man = ref<M>({name: 'lzy'})  // 返回的是 RefImpl 对象
const change = () => {
    Man.value.name = "LZY"  // 在 js 中获取值,记得写 .value,在模板中则不用
}
</script>
其中,ref 的类型推断有 3 种方式
// 第一种:自动推断
const Man = ref({name: 'lzy'})
// 第二种:自定义泛型
type M = {
    name: string
}
const Man = ref<M>({name: 'lzy'})
// 第三种:利用 vue 内置的 Ref 接口
import { Ref } from 'vue'

type M = {
    name: string
}
const Man: Ref<M> = ref({name: 'lzy'})

2. isRef

2.1 作用

判断是否是 RefImpl 对象

2.2 用法

<template>
<div>{{ Man }}</div>
<hr>
<button @click="change">按钮</button>
</template>

<script setup lang="ts">
import { ref, Ref, isRef } from 'vue'

type M = {
    name: string
}

const Man: Ref<M> = ref({name: 'lzy'})
const man: object = {name: 'lzy2'}

const change = () => {
    console.log(isRef(Man))  // true
    console.log(isRef(man))  // false
}
</script>

3. shallowRef

3.1 作用

实现浅层次的响应式

3.2 用法

import { shallowRef } from 'vue'
const Man2 = shallowRef({name: 'lzy2'})

const change = () => {
    Man2.value.name = 'LZY2'  // 无法改变 Man2 的 name 在视图中的数据,但其值其实是变了的
    Man2.value = { name: 'LZY2' }  // 可以改变视图数据,说明浅层响应式只能响应 .value 的改变
}

3.3 对比 ref

ref 是深层次的监测, shallowRef 是浅层次的监测
② 注意不要把对 ref 和 shallowRef 监测数据的修改写在一起
    因为对 ref 数据的修改会影响到 shallowRef 对数据的监测(即 shallowRef 也可能变成深层次的了)
    具体为什么,下面会讲到
示例
<template>
<div>ref: {{ Man }}</div>
<div>shallowRef: {{ Man2 }}</div>
<hr>
<button @click="change">按钮</button>
</template>

<script setup lang="ts">
import { ref,shallowRef } from 'vue'

const Man = ref({name: 'lzy'})
const Man2 = shallowRef({name: 'lzy2'})

const change = () => {
    Man.value.name = "我名字改了"
    Man2.value.name = '我被影响了'
}

// 结果为:
// ref: { "name": "我名字改了" }
// shallowRef: { "name": "我被影响了" }
</script>

4. triggerRef

4.1 作用

强制更新我们收集的依赖

上面的 ref 与 shallowRef 值同时更新而 shallowRef 受 ref 影响,就是因为收集的 依赖 ref 的 值更新的时候,内部调用了 triggerRef 函数,导致收集的 依赖 shallowRef 的 值也被强制更新了

4.2 用法

<template>
<div>shallowRef: {{ Man2 }}</div>
<hr>
<button @click="change">按钮</button>
</template>

<script setup lang="ts">
import { shallowRef,triggerRef } from 'vue'

const Man2 = shallowRef({name: 'lzy2'})

const change = () => {
    Man2.value.name = '我被影响了'
    triggerRef(Man2)  // 强制更新 浅层响应式的 深层依赖
}
</script>

5. customRef

5.1 作用

自定义一个 ref 响应式对象

5.2 用法

5.2.1 用在 基本类型 上
<template>
<div>customRef: {{ obj }}</div>
<hr>
<button @click="change">按钮</button>
</template>

<script setup lang="ts">
import { customRef } from 'vue'

function MyRef<T>(value: T){
    return customRef((track, trigger) => {
        return {
            get(){
                track()  // 收集依赖
                return value
            }, 

            set(newValue){
                console.log("触发了")
                value = newValue 
                trigger()  // 触发依赖
            }
        }
    })
}

const obj = MyRef<string>("lzy")

const change = () => {
    console.log(obj)  // CustomRefImpl {dep: Set(1), __v_isRef: true, _get: ƒ, _set: ƒ}
    obj.value = "LZY"
}
</script>
按钮点击前结果:customRef: lzy
按钮点击后结果:customRef: LZY
控制台输出:触发了
说明调用了 set 方法
5.2.1 用在 引用类型 上
<template>
<div>customRef: {{ obj }}</div>
<hr>
<button @click="change">按钮</button>
</template>

<script setup lang="ts">
import { customRef } from 'vue'

function MyRef<T>(value: T){
    return customRef((track, trigger) => {
        return {
            get(){
                track()  // 收集依赖
                return value
            }, 

            set(newValue){
                console.log("触发了")
                value = newValue 
                console.log(newValue === value)
                trigger()  // 触发依赖
            }
        }
    })
}

const obj = MyRef<object>({name: 'lzy'})

const change = () => {
    obj.value.name = "LZY"  // 提示:类型“object”上不存在属性“name”。
    console.log(obj.value.name)  // LZY
}
</script>
可以看到,当想设置 obj.value.name 时,会产生提示信息。
实际上,obj.value.name 的值在控制台打印出来显示是修改了的,但是页面上的值没有变化
说明并没有调用 set 方法,说明 customRef 内部用的是 shallowRef,只能浅层响应

解决方案

// 方法一:直接更改 value
const change = () => {
    obj.value = {name: "LZY"}
}
// 方法二:使用 triggerRef() 强制更新依赖
const change = () => {
    obj.value.name = "LZY"  // 但是仍然会提示:类型“object”上不存在属性“name”。
    triggerRef(obj)
}

5.3 应用场景

5.1 可以看到,不断点击按钮,set 会不断触发
但由于 set 逻辑是用户可控的
因此我们可以在 set 里面作防抖处理
<template>
    <div>customRef: {{ obj }}</div>
    <hr>
    <button @click="change">按钮</button>
</template>
    
<script setup lang="ts">
import { customRef } from 'vue'

function MyRef<T>(value: T) {
    let timer: any
    return customRef((track, trigger) => {
        return {
            get() {
                track()  // 收集依赖
                return value
            },

            set(newValue) {
                // 实现防抖
                clearTimeout(timer)
                timer = setTimeout(() => {
                    console.log("触发了")
                    value = newValue
                    trigger()  // 触发依赖
                    timer = null
                }, 500)
            }
        }
    })
}

const obj = MyRef<string>("lzy")

const change = () => {
    obj.value = "LZY"
}
</script>

6. ref 用于获取 DOM 元素

6.1 用法

1. 在 DOM 元素上添加 ref 属性
<div ref="dom"></div>
2. 在 setup 中设置同名变量,设为 ref()
import { ref } from 'vue'
let dom = ref()
3. 打印一下
console.log(dom)  // Ref<undefined>,此时 DOM 还没挂载,所以为 undefined

onMounted(() => {
    console.log(dom)  // Ref<div>
})
4. 在 DOM 挂载之后再打印

7. ref 对象查看的小妙招

7.1 设置

1. F12 打开浏览器开发者工具
2. 点击右上角 “设置” 小齿轮
3. “偏好设置” 里面勾选 “启动自定义格式设置工具”

7.2 效果

设置之前打印 ref 对象:RefImpl {dep: Set(1), __v_isRef: true, _get: ƒ, _set: ƒ}

设置之后打印 ref 对象:Ref<"lzy">

也可以用于打印 reactive 对象