第八节: 全面理解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,但它把这种常见的判断逻辑封装成了一个超级方便的语法糖,让你写代码时既省事又优雅!

示例

在示例中, 参数countref 类型, 则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 就是 stringunref 返回的也是 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 是一个用于将refgetter 统一规范化为普通值的工具函数。其功能与 unref() 类似,但更进一步支持对 getter 函数的处理。具体来说:

  • 如果传入的是一个普通值 reftoValue 会返回其具体的值。
  • 如果传入的是一个 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>
相关推荐
且白29 分钟前
vsCode使用本地低版本node启动配置文件
前端·vue.js·vscode·编辑器
程序研29 分钟前
一、ES6-let声明变量【解刨分析最详细】
前端·javascript·es6
疯狂的沙粒1 小时前
在uni-app中如何从Options API迁移到Composition API?
javascript·vue.js·uni-app
siwangqishiq21 小时前
Vulkan Tutorial 教程翻译(四) 绘制三角形 2.2 呈现
前端
李三岁_foucsli1 小时前
js中消息队列和事件循环到底是怎么个事,宏任务和微任务还存在吗?
前端·chrome
尽欢i1 小时前
HTML5 拖放 API
前端·html
xiaominlaopodaren1 小时前
Three.js 光影魔法:如何单独点亮你的3D模型
javascript
PasserbyX1 小时前
一句话解释JS链式调用
前端·javascript
1024小神1 小时前
tauri项目,如何在rust端读取电脑环境变量
前端·javascript
Nano1 小时前
前端适配方案深度解析:从响应式到自适应设计
前端