前言
好了,咱们今天继续聊 Vue 3 提供的那些实用的工具 API。这些能帮我们快速判断数据的类型,还能轻松地从响应式数据里提取出非响应式的原始数据。听起来是不是挺方便的?掌握它们,写起代码来绝对事半功倍!那咱们接下来就一起看看这些工具怎么用,绝对让你大开眼界!
1. isRef()
Vue 3 里一个挺实用的工具函数------isRef()。这个函数呢,说白了就是帮咱们检查一个值是不是 ref 类型的数据。通过之前的学习,大家应该已经知道了,像 ref()
、shallowRef()
和 computed()
这仨方法返回的都是 Ref
类型的数据。那么问题来了,我们怎么确认它真的是个 Ref
呢?这时候 isRef()
就派上用场了。
接下来,咱们一起瞅瞅 isRef() 的具体使用方法,然后再通过几个小例子来加深理解
我们先看下官网提供的TypeScript类型:
r
function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
通过isRef
类型, 可以看出以下几点信息:
isRef
函数接受一个参数, 参数可以是任意数据类型isRef
函数返回一个布尔值, 如果参数是ref
类型的数据,则返回true
, 否则返回false
示例代码:
这里要特别注意的是,isRef()
的返回值是一个类型判定(type predicate)
,这是什么意思呢?简单来说,就是它返回布尔值(true 或 false),是可以能在 TypeScript 中作为一个类型守卫来用。也就是说,isRef()
不仅能告诉你传进去的值是不是 Ref
类型,还能在后续代码中帮你锁定类型,让 TypeScript 知道它确实是个 Ref
。
举个实际的例子,假设你传了个参数给 isRef()
,如果它返回 true,那咱们就可以确信这玩意儿就是个 Ref
数据, 它的TypeScript 类型就是Ref<T>
, 你就可以在接下来的代码中直接按照 ref
的规则来处理它,不用再担心类型问题了。是不是很贴心?所以这个功能不仅实用,还能让你的代码更加安全可靠!
示例
在咱们的实例中,当你用 isRef
判断返回 true
时,TypeScript
会很聪明地把 msg
的类型推断为 Ref<unknown>
。这意味着,这时候你就可以放心大胆地用 .value
属性访问数据了,TS
绝对不会给你报错。但如果 isRef
返回 false
,那就说明 msg
根本就不是个 ref
,咱们自然也就不能用 .value
去获取数据了。所以isRef
在帮我们判断类型时,还帮我们解决了另人头疼的类型问题!
我们还可以使用泛型, 解决后续操作类型问题
在咱们的实际示例中,没有使用泛型的 isRef
判断场景是这样的:如果 isRef
返回true
,它会把类型判定为 Ref
,但它的泛型是默认的 unknown
类型。这时候,当你用 .value
获取值时,值的类型就是 unknown
,因此如果你尝试调用 toUpperCase
方法,TypeScript 一定会报错------毕竟 unknown
类型可不能随便调用字符串方法!
但如果咱们用泛型指定了类型,比如把 isRef
的判断条件明确为 Ref<string>
,情况就大不一样了。这时,isRef
会判定类型为 Ref<string>
,而用 .value
获取值时,值的类型就是 string
类型 了,于是调用 toUpperCase
方法就完全不会有问题。
另外需要补充一点,自定义泛型不会影响 isRef
的判断结果,它依然会准确地告诉你一个值是不是 Ref
,只是通过泛型你可以更明确地锁定类型,让代码更加可靠和易于维护。这样一来,你的 TypeScript 体验也会更丝滑,写代码再也不用提心吊胆啦!
2. unref()
unref
函数的作用就是帮你安全地获取参数的实际值,它的逻辑非常简单:如果传入的参数是个 ref(包括 computed)
,它会自动返回 .value
的值;如果参数本身不是 ref
,它就原封不动地把参数还给你。用大白话说,unref
就是一句简洁版的 val = isRef(val) ? val.value : val
,但它把这种常见的判断逻辑封装成了一个超级方便的语法糖,让你写代码时既省事又优雅!
示例
在示例中, 参数count
是ref
类型, 则unref
函数返回count.value
属性值, 值为0
而参数state
不是ref
类型, 因此返回参数本身, 即Proxy {a:true}
代理对象.
2.1. 类型推断
接下来我们关注一下unref
的类型处理。类型如下:
r
function unref<T>(ref: T | Ref<T>): T
在使用 unref
方法时,泛型 T
的类型会根据参数的不同而动态推断,具体规则如下:
如果参数是 Ref<T>
类型,T
就是 .value
属性的类型,也就是 unref
方法返回值的类型。比如 ref<string>('hello')
,T
就是 string
,unref
返回的也是 string
类型。
如果参数是非 Ref
的对象类型,但这个对象刚好有一个 value
属性,T 会推断为 value
属性的类型。不过要注意,这种情况可能会导致类型校验报错,因为 unref
的设计初衷并不是用来处理这种对象的。比如 { value: 123 }
,T 会被推断为 number
,但直接传这种对象给 unref
可能会有意想不到的问题。
除了以上两种情况,T
就是参数本身的类型。比如直接传入一个字符串 'hello'
或者一个普通对象 { a: true }
,T
就是 string
或 { a: boolean }
,unref
返回的也是这个类型。
我们通过示例查看以下以上特效:
xml
<script setup lang="ts">
import { ref,reactive,unref } from 'vue'
const count = ref(0)
const state = {
value: true
}
const msg = {
msg: 'hello'
}
const result = unref(count)
// const result: number
const result2 = unref(state)
// const result2: boolean
const result3 = unref(msg)
// const result3: {msg: string;}
</script>
unref
的泛型类型推断非常智能,能够根据参数的特性自动适配,但你在使用时也要注意它的设计边界,避免误用导致类型问题!
2.2. 自定义泛型
当你在使用 unref
时指定了自定义泛型,它的逻辑是这样运作的:
如果参数的类型与指定的泛型一致,比如你定义了 unref<string>('hello')
,那么参数 'hello'
必须是一个 string
类型,unref
会直接返回这个 string
值。
如果参数是 Ref
类型,那么 .value
属性的类型必须与指定的泛型一致。比如 unref<string>(ref('world'))
,这里的 ref
的 .value
是一个 string
,所以与泛型 string
匹配,unref
会返回 'world'
。
简单来说,当你明确指定泛型时,参数要么直接匹配泛型类型,要么是 Ref
类型且 .value
匹配泛型类型。这种设计不仅保证了类型安全,还能让你的代码逻辑更加清晰,避免意外的类型错误!
xml
<script setup lang="ts">
import { ref,reactive,unref } from 'vue'
let str = 'hello'
let str2 = ref('world')
console.log('str', unref<string>(str))
console.log('str', unref<string>(str2))
</script>
3. toRef()
toRef 函数从名称就可以看出, 是一个将参数输出为Ref
数据的工具. 其具体逻辑可以从官网最新的TypeScript可以看出.
从类型定义中可以清晰地看出,toRef 根据传入参数的不同,具备多种灵活的转换逻辑:
- 单参数(非
Ref
):当传入一个普通值
或非 Ref
类型的数据时,toRef
会将其转换为对应的Ref
数据,使其具备响应式特性。
- 单参数(函数类型):当传入一个函数(如 getter)时,
toRef
会返回一个只读的Ref
数据,该Ref
会监听函数的返回值变化,确保数据的响应性。 - 多参数(对象,属性):当传入多个参数,第一个为响应式对象,第二个为对象属性时,
toRef
会提取该属性的值并返回一个与之同步的Ref
数据,修改Ref
会更新对象属性,反之亦然。
示例:
xml
<script setup lang="ts">
import { toRef,ref, reactive } from 'vue'
// 1. 等同于 ref(10)
const count = toRef(10)
// 2. 创建一个只读的 ref,当访问 .value 时会调用此 getter 函数
const count2 = toRef(() => (count.value*2))
// 3. 按原样返回现有的 ref
// count3 就是 ref(30)
const count3 = toRef(ref(30))
// 4. 将普通对象属性转为Ref
const obj = {name:'张三'}
const count3 = toRef(obj,'name')
// 此时修改值, 原对象也会变化
count3.value = '李四'
console.log(obj) // {name:'李四'}
// 5. 响应对象的属性转为Ref
const obj2 = reactive({name:'张三'})
const count4 = toRef(obj2,'name')
</script>
4. toRefs()
toRefs
工具函数是专门用于解构响应式对象并将其所有属性转换为对应的 ref,从而保持响应性与引用关系。主要逻辑如下
- 首先将参数响应式对象转换为一个普通对象
- 对象的每个属性(Prop)都是对应的ref,两者保持引用关系
- 每个单独的 ref 都是使用 toRef() 创建的
类型:
r
function toRefs<T extends object>(
object: T
): {
[K in keyof T]: ToRef<T[K]>
}
type ToRef = T extends Ref ? T : Ref<T>
示例:
xml
<script setup lang="ts">
import { reactive,toRefs} from 'vue'
const user = reactive({
name:'张三',
age:18
})
const user2 = toRefs(user)
console.log('user2', user2)
const {name, age} = user2
const change= () => {
user.name = '李四'
user.age = 20
}
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<h3>name is {{ name }}</h3>
<h3>age is {{ age }}</h3>
<button type="button" @click="change">修改数据</button>
</div>
</template>
5. toValue()
toValue
是一个用于将值
、ref
或 getter
统一规范化为普通值
的工具函数。其功能与 unref()
类似,但更进一步支持对 getter
函数的处理。具体来说:
- 如果传入的是一个
普通值
或ref
,toValue
会返回其具体的值。 - 如果传入的是一个
getter
函数,toValue
会主动调用该函数并返回其计算结果。
类型:
r
function toValue<T>(source: T | Ref<T> | (() => T)): T
根据类型,可知, toValue函数接受的参数有三种形式:
- 传入ref 数据. 返回ref 数据的.value 值
- 传入一个函数, toValue 会自动调用这个函数, 返回函数的返回值
- 除了以上两种情况外, 传入声明值, 返回什么值
示例:
xml
<script setup lang="ts">
import { reactive,toRefs} from 'vue'
// ref数据
const count = ref(10)
const value1 = toValue(count)
console.log('value1', value1) // value1 10
// 函数
const value2 = toValue(() => 50)
console.log('value2', value2) // value2 50
// 除了以上两种, 参数是什么, 返回什么
const value3 = toValue("hello")
console.log('value3', value3) // value2 hello
// 参数是reactive
const user = reactive({ name: '张三', age: 18 })
const value4 = toValue(user)
console.log('value4', value4) // value4 Proxy(Object) {name: '张三', age: 18}
</script>
<template>
<h1>toValue</h1>
</template>
6. isProxy()
检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理。
类型:
sql
function isProxy(value: unknown): boolean
示例:
php
<script setup lang="ts">
import { ref,reactive,isProxy,readonly,shallowReactive, shallowReadonly} from 'vue'
// reactive
const result1 = reactive({
name:'张三',
age:18
})
console.log('result1', isProxy(result1)) // true
// readonly
const result2 = readonly({
name:'张三',
age:18
})
console.log('result2', isProxy(result2)) // true
// shallowReactive
const result3 = shallowReactive({
name:'张三',
age:18
})
console.log('result2', isProxy(result3)) // true
// shallowReadonly
const result4 = shallowReadonly({
name:'张三',
age:18
})
console.log('result4', isProxy(result4)) // true
// ref
const result5 = ref({
name:'张三',
age:18
})
console.log('result5', isProxy(result5)) // false
</script>
7. isReactive()
检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。
示例:
php
<script setup lang="ts">
import { ref,reactive,isReactive,readonly,shallowReactive, shallowReadonly} from 'vue'
// reactive
const result1 = reactive({
name:'张三',
age:18
})
console.log('result1', isReactive(result1)) // true
// readonly
const result2 = readonly({
name:'张三',
age:18
})
console.log('result2', isReactive(result2)) // false
// shallowReactive
const result3 = shallowReactive({
name:'张三',
age:18
})
console.log('result2', isReactive(result3)) // true
// shallowReadonly
const result4 = shallowReadonly({
name:'张三',
age:18
})
console.log('result4', isReactive(result4)) // false
// ref
const result5 = ref({
name:'张三',
age:18
})
console.log('result5', isReactive(result5)) // false
</script>
8. isReadonly()
检查传入的值是否为只读对象。
通过 readonly() 和 shallowReadonly() 创建的代理都是只读的,因为他们是没有 set 函数的 computed() ref。
示例:
xml
<script setup lang="ts">
import { ref,reactive,isReadonly,readonly,shallowReactive, shallowReadonly} from 'vue'
// reactive
const result1 = reactive({
name:'张三',
age:18
})
console.log('result1', isReadonly(result1)) // false
// readonly
const result2 = readonly({
name:'张三',
age:18
})
console.log('result2', isReadonly(result2)) // true
// shallowReactive
const result3 = shallowReactive({
name:'张三',
age:18
})
console.log('result2', isReadonly(result3)) // false
// shallowReadonly
const result4 = shallowReadonly({
name:'张三',
age:18
})
console.log('result4', isReadonly(result4)) // true
// ref
const result5 = ref({
name:'张三',
age:18
})
console.log('result5', isReadonly(result5)) // false
// computed(无set)
const result6 = computed(() => {
return 10
})
console.log('result6', isReadonly(result6)) // true
// computed(有set)
const result7 = computed({
get(){
return 10
},
set(){}
})
console.log('result7', isReadonly(result7)) // false
</script>