第八节: 全面理解vue3: 工具函数的核心作用与使用方法

前言

好了,咱们今天继续聊 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 根据传入参数的不同,具备多种灵活的转换逻辑:

  1. 单参数(非Ref):当传入一个普通值或非 Ref 类型的数据时,toRef 会将其转换为对应的 Ref 数据,使其具备响应式特性。
  1. 单参数(函数类型):当传入一个函数(如 getter)时,toRef 会返回一个只读的 Ref 数据,该 Ref 会监听函数的返回值变化,确保数据的响应性。
  2. 多参数(对象,属性):当传入多个参数,第一个为响应式对象,第二个为对象属性时,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,从而保持响应性与引用关系。主要逻辑如下

  1. 首先将参数响应式对象转换为一个普通对象
  2. 对象的每个属性(Prop)都是对应的ref,两者保持引用关系
  3. 每个单独的 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函数接受的参数有三种形式:

  1. 传入ref 数据. 返回ref 数据的.value 值
  2. 传入一个函数, toValue 会自动调用这个函数, 返回函数的返回值
  3. 除了以上两种情况外, 传入声明值, 返回什么值

示例:

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>
相关推荐
有味道的男人11 分钟前
搜好货平台关键词商品搜索接口:分页容错与商品数据轻量化提取方案
服务器·前端·javascript
lovingsoft1 小时前
Chrome Lighthouse 详解
前端·chrome
Qlly1 小时前
全栈工程师怎么接需求?从一句「加个功能」聊到能开发、能验收
前端·后端
马可家的菠萝1 小时前
你的微信“文件传输助手”,是不是已经成了第二个网盘?
前端·后端·架构
略略略啦1 小时前
Vibe Coding 基于Spec DD和TDD的实践指南
前端
shmily麻瓜小菜鸡1 小时前
前端实现图片与PDF文字识别敏感信息打码方案
前端·pdf·状态模式
打工仔折腾 AI2 小时前
LangChain 工具调用实战:从 Tool 定义到 Agent 执行链路拆解
java·前端·人工智能·后端·python·langchain·ai agent 实战
汉堡大王95272 小时前
单次输出拉到100万Token:拆完 Gemini 4 Argon 的续写协议,才看懂谷歌这7个月在憋什么
前端·人工智能·后端
看浪的路人2 小时前
第9讲:大模型应用监控与可观测性——OpenTelemetry + Prometheus + Grafana 全链路追踪
java·前端·spring
内存过载2 小时前
用表格模拟甘特图:项目排期的土办法
前端·低代码