相信各位开发者看到这里时,应该已经对 ref 和 reactive API 都有所了解了,为了方便开发者使用, Vue 3 还推出了两个与之相关的 API : toRef 和 toRefs ,都是用于 reactive 向 ref 转换。
各自的作用
这两个 API 在拼写上非常接近,顾名思义,一个是只转换一个字段,一个是转换所有字段,转换后将得到新的变量,并且新变量和原来的变量可以保持同步更新。

光看概念可能不容易理解,来看下面的例子,先声明一个 reactive 变量:

interface Member {id: numbername: string
}const userInfo: Member = reactive({id: 1,name: 'Petter',
})

然后分别看看这两个 API 应该怎么使用。
使用 toRef
先看这个转换单个字段的 toRef API ,了解了它的用法之后,再去看 toRefs 就很容易理解了。
API 类型和基本用法
toRef API 的 TS 类型如下:

// `toRef` API 的 TS 类型
function toRef<T extends object, K extends keyof T>(object: T,key: K,defaultValue?: T[K]
): ToRef<T[K]>// `toRef` API 的返回值的 TS 类型
type ToRef<T> = T extends Ref ? T : Ref<T>

通过接收两个必传的参数(第一个是 reactive 对象, 第二个是要转换的 key ),返回一个 Ref 变量,在适当的时候也可以传递第三个参数,为该变量设置默认值。
以上文声明好的 userInfo 为例,如果想转换 name 这个字段为 Ref 变量,只需要这样操作:

const name = toRef(userInfo, 'name')
console.log(name.value) // Petter

等号左侧的 name 变量此时是一个 Ref 变量,这里因为 TypeScript 可以对其自动推导,因此声明时可以省略 TS 类型的显式指定,实际上该变量的类型是 Ref 。

所以之后在读取和赋值时,就需要使用 name.value 来操作,在重新赋值时会同时更新 name 和 userInfo.name 的值:

// 修改前先查看初始值
const name = toRef(userInfo, 'name')
console.log(name.value) // Petter
console.log(userInfo.name) // Petter// 修改 Ref 变量的值,两者同步更新
name.value = 'Tom'
console.log(name.value) // Tom
console.log(userInfo.name) // Tom// 修改 Reactive 对象上该属性的值,两者也是同步更新
userInfo.name = 'Jerry'
console.log(name.value) // Jerry
console.log(userInfo.name) // Jerry

这个 API 也可以接收一个 Reactive 数组,此时第二个参数应该传入数组的下标:

// 这一次声明的是数组
const words = reactive(['a', 'b', 'c'])// 通过下标 `0` 转换第一个 item
const a = toRef(words, 0)
console.log(a.value) // a
console.log(words[0]) // a// 通过下标 `2` 转换第三个 item
const c = toRef(words, 2)
console.log(c.value) // c
console.log(words[2]) // c

设置默认值
如果 Reactive 对象上有一个属性本身没有初始值,也可以传递第三个参数进行设置(默认值仅对 Ref 变量有效):

interface Member {id: numbername: string// 类型上新增一个属性,因为是可选的,因此默认值会是 `undefined`age?: number
}// 声明变量时省略 `age` 属性
const userInfo: Member = reactive({id: 1,name: 'Petter',
})// 此时为了避免程序运行错误,可以指定一个初始值
// 但初始值仅对 Ref 变量有效,不会影响 Reactive 字段的值
const age = toRef(userInfo, 'age', 18)
console.log(age.value)  // 18
console.log(userInfo.age) // undefined// 除非重新赋值,才会使两者同时更新
age.value = 25
console.log(age.value)  // 25
console.log(userInfo.age) // 25

数组也是同理,对于可能不存在的下标,可以传入默认值避免项目的逻辑代码出现问题:

const words = reactive(['a', 'b', 'c'])// 当下标对应的值不存在时,也是返回 `undefined`
const d = toRef(words, 3)
console.log(d.value) // undefined
console.log(words[3]) // undefined// 设置了默认值之后,就会对 Ref 变量使用默认值, Reactive 数组此时不影响
const e = toRef(words, 4, 'e')
console.log(e.value) // e
console.log(words[4]) // undefined

其他用法
这个 API 还有一个特殊用法,但不建议在 TypeScript 里使用。

在 toRef 的过程中,如果使用了原对象上面不存在的 key ,那么定义出来的 Ref 变量的 .value 值将会是 undefined 。

// 众所周知, Petter 是没有女朋友的
const girlfriend = toRef(userInfo, 'girlfriend')
console.log(girlfriend.value) // undefined
console.log(userInfo.girlfriend) // undefined// 此时 Reactive 对象上只有两个 Key
console.log(Object.keys(userInfo)) // ['id', 'name']

如果对这个不存在的 key 的 Ref 变量进行赋值,那么原来的 Reactive 对象也会同步增加这个 key,其值也会同步更新。

// 赋值后,不仅 Ref 变量得到了 `Marry` , Reactive 对象也得到了 `Marry`
girlfriend.value = 'Marry'
console.log(girlfriend.value) // 'Marry'
console.log(userInfo.girlfriend) // 'Marry'// 此时 Reactive 对象上有了三个 Key
console.log(Object.keys(userInfo)) // ['id', 'name', 'girlfriend']

为什么强调不要在 TypeScript 里使用呢?因为在编译时,无法通过 TypeScript 的类型检查:

❯ npm run buildhello-vue3@0.0.0 build
vue-tsc --noEmit && vite buildsrc/views/home.vue:37:40 - error TS2345: Argument of type '"girlfriend"'
is not assignable to parameter of type 'keyof Member'.37     const girlfriend = toRef(userInfo, 'girlfriend')~~~~~~~~~~~~src/views/home.vue:39:26 - error TS2339: Property 'girlfriend' does not exist
on type 'Member'.39     console.log(userInfo.girlfriend) // undefined~~~~~~~~~~src/views/home.vue:45:26 - error TS2339: Property 'girlfriend' does not exist
on type 'Member'.45     console.log(userInfo.girlfriend) // 'Marry'~~~~~~~~~~Found 3 errors in the same file, starting at: src/views/home.vue:37

如果不得不使用这种情况,可以考虑使用 any 类型:

// 将该类型直接指定为 `any`
type Member = any
// 当然一般都是 `const userInfo: any`// 或者保持接口类型的情况下,允许任意键值
interface Member {[key: string]: any
}// 使用 `Record` 也是同理
type Member = Record<string, any>

使用 toRefs
在了解了 toRef API 之后,来看看 toRefs 的用法。
API 类型和基本用法
先看看它的 TS 类型:

function toRefs<T extends object>(object: T
): {[K in keyof T]: ToRef<T[K]>
}type ToRef = T extends Ref ? T : Ref<T>

与 toRef 不同, toRefs 只接收了一个参数,是一个 reactive 变量。

interface Member {id: numbername: string
}// 声明一个 Reactive 变量
const userInfo: Member = reactive({id: 1,name: 'Petter',
})// 传给 `toRefs` 作为入参
const userInfoRefs = toRefs(userInfo)

此时这个新的 userInfoRefs 变量,它的 TS 类型就不再是 Member 了,而应该是:

// 导入 `toRefs` API 的类型
import type { ToRefs } from 'vue'// 上下文代码省略...// 将原来的类型传给 API 的类型
const userInfoRefs: ToRefs<Member> = toRefs(userInfo)

也可以重新编写一个新的类型来指定它,因为每个字段都是与原来关联的 Ref 变量,所以也可以这样声明:

// 导入 `ref` API 的类型
import type { Ref } from 'vue'// 上下文代码省略...// 新声明的类型每个字段都是一个 Ref 变量的类型
interface MemberRefs {id: Ref<number>name: Ref<string>
}// 使用新的类型进行声明
const userInfoRefs: MemberRefs = toRefs(userInfo)

当然实际上日常使用时并不需要手动指定其类型, TypeScript 会自动推导,可以节约非常多的开发工作量。

和 toRef API 一样,这个 API 也是可以对数组进行转换:

const words = reactive(['a', 'b', 'c'])
const wordsRefs = toRefs(words)

此时新数组的类型是 Ref[] ,不再是原来的 string[] 类型。
解构与赋值
转换后的 Reactive 对象或数组支持 ES6 的解构,并且不会失去响应性,因为解构后的每一个变量都具备响应性。

// 为了提高开发效率,可以直接将 Ref 变量直接解构出来使用
const { name } = toRefs(userInfo)
console.log(name.value) // Petter// 此时对解构出来的变量重新赋值,原来的变量也可以同步更新
name.value = 'Tom'
console.log(name.value) // Tom
console.log(userInfo.name) // Tom

这一点和直接解构 Reactive 变量有非常大的不同,直接解构 Reactive 变量,得到的是一个普通的变量,不再具备响应性。

这个功能在使用 Hooks 函数非常好用(在 Vue 3 里也叫可组合函数, Composable Functions ),还是以一个计算器函数为例,这一次将其修改为内部有一个 Reactive 的数据状态中心,在函数返回时解构为多个 Ref 变量:

import { reactive, toRefs } from 'vue'// 声明 `useCalculator` 数据状态类型
interface CalculatorState {// 这是要用来计算操作的数据num: number// 这是每次计算时要增加的幅度step: number
}// 声明一个 “使用计算器” 的函数
function useCalculator() {// 通过数据状态中心的形式,集中管理内部变量const state: CalculatorState = reactive({num: 0,step: 10,})// 功能函数也是通过数据中心变量去调用function add() {state.num += state.step}return {...toRefs(state),add,}
}

这样在调用 useCalculator 函数时,可以通过解构直接获取到 Ref 变量,不需要再进行额外的转换工作。

// 解构出来的 `num` 和 `step` 都是 Ref 变量
const { num, step, add } = useCalculator()
console.log(num.value) // 0
console.log(step.value) // 10// 调用计算器的方法,数据也是会得到响应式更新
add()
console.log(num.value) // 10

为什么要进行转换
关于为什么要出这么两个 API ,官方文档没有特别说明,不过经过笔者在业务中的一些实际使用感受,以及在写上一节 reactive 的 特别注意,可能知道一些使用理由。

关于 ref 和 reactive 这两个 API 的好处就不重复了,但是在使用的过程中,各自都有不方便的地方:

ref API 虽然在 里使用起来方便,但是在

那么有没有办法,既可以在编写

于是, toRef 和 toRefs 因此诞生。
什么场景下比较适合使用它们
从便利性和可维护性来说,最好只在功能单一、代码量少的组件里使用,比如一个表单组件,通常表单的数据都放在一个对象里。
当然也可以把所有的数据都定义到一个 data 里,再去 data 里面取值,但是没有必要为了转换而转换,否则不如使用 Options API 风格。

在业务中的具体运用

继续使用上文一直在使用的 userInfo 来当案例,以一个用户信息表的小 demo 做个演示。

1.先用 reactive 定义一个源数据,所有的数据更新,都是修改这个对象对应的值,按照对象的写法维护数据

2.再通过 toRefs 定义一个给 使用的对象,这样可以得到一个每个字段都是 Ref 变量的新对象

3.在 return 的时候,对步骤 2 里的 toRefs 对象进行解构,这样导出去就是各个字段对应的 Ref 变量,而不是一整个对象

import { defineComponent, reactive, toRefs } from 'vue'interface Member {id: numbername: stringage: numbergender: string
}export default defineComponent({setup() {// 定义一个 reactive 对象const userInfo = reactive({id: 1,name: 'Petter',age: 18,gender: 'male',})// 定义一个新的对象,它本身不具备响应性,但是它的字段全部是 Ref 变量const userInfoRefs = toRefs(userInfo)// 在 2s 后更新 `userInfo`setTimeout(() => {userInfo.id = 2userInfo.name = 'Tom'userInfo.age = 20}, 2000)// 在这里结构 `toRefs` 对象才能继续保持响应性return {...userInfoRefs,}},
})

在 部分:
由于 return 出来的都是 Ref 变量,所以在模板里可以直接使用 userInfo 各个字段的 key ,不再需要写很长的 userInfo.name 了。

<template><ul class="user-info"><li class="item"><span class="key">ID:</span><span class="value">{{ id }}</span></li><li class="item"><span class="key">name:</span><span class="value">{{ name }}</span></li><li class="item"><span class="key">age:</span><span class="value">{{ age }}</span></li><li class="item"><span class="key">gender:</span><span class="value">{{ gender }}</span></li></ul>
</template>

需要注意的问题
请注意是否有相同命名的变量存在,比如上面在 return 给 使用时,在解构 userInfoRefs 的时候已经包含了一个 name 字段,此时如果还有一个单独的变量也叫 name ,就会出现渲染上的数据显示问题。

此时它们在 里哪个会生效,取决于谁排在后面,因为 return 出去的其实是一个对象,在对象里,如果存在相同的 key ,则后面的会覆盖前面的。
下面这种情况,会以单独的 name 为渲染数据:

return {...userInfoRefs,name,
}

而下面这种情况,则是以 userInfoRefs 里的 name 为渲染数据:

return {name,...userInfoRefs,
}

所以当决定使用 toRef 和 toRefs API 的时候,请注意这个特殊情况!

VUE3 响应式 API 之 toRef 与 toRefs相关推荐

  1. vue3 响应式 API 之 ref

    ref 是最常用的一个响应式 API,它可以用来定义所有类型的数据,包括 Node 节点和组件. 没错,在 Vue 2 常用的 this.$refs.xxx 来取代 document.querySel ...

  2. Vue3响应式API ref和reactive

    在vue3中,有两个重要的api分别是ref 和reactive 使用方法如下 import { reactive, ref } from 'vue';setup(){let student = re ...

  3. Vue.js 3.0 响应式 API 比 2.x 好在哪儿?

    Hello,各位小伙伴,接下来的一段时间里,我会把我的课程<Vue.js 3.0 核心源码解析>中问题的答案陆续在我的公众号发布,由于课程的问题大多数都是开放性的问题,所以我的答案也不一定 ...

  4. 响应式API的设计、实现和应用

    \ 本文要点: \\ 在进行响应式设计之前,确保你的项目的确适合使用响应式编程\\t 响应式方法总会返回一些什么,因为它们构建了一个执行框架,但是不是开始去执行\\t 响应式编程允许你声明操作之间的状 ...

  5. Vue3 响应式原理

    如何实现响应式 作为一个高阶的概述,我们需要做到以下几点: 当一个值被读取时进行追踪 当某个值改变时进行检测 重新运行代码来读取原始值 Vue如何知道哪些代码在执行 为了能够在数值变化时,随时运行我们 ...

  6. vue3基础-响应式 API 之 unref、toRef、toRefs、isRef

    unref 如果参数是一个 ref,则返回内部值,否则返回参数本身. toRef 将对象的某个属性转为响应式,修改值时原始值也会改变,但是值改变不会更新视图 使用示例: <template> ...

  7. vue3基础-响应式 API 之 ref 和 reactive

    背景 我们知道ref函数和reactive函数用于实现数据的响应性. ref 在 Vue 3中,我们可以通过一个新的 ref 函数使任何响应式变量在任何地方起作用,如下所示: import { ref ...

  8. vue3中的ref,toRef,toRefs三个的作用

    1. ref的使用 ref 接受一个原始值,返回一个具有响应式的对象,对象有一个value属性,其值就是所传递的原始值. ref是做的一个拷贝关系,修改对象msg的值,不会影响对象obj,视图会发生变 ...

  9. 都知道vue3响应式是Proxy实现的,进来把proxy与Reflect吃透

    前言 众所周知,vue2.x版本实现双向绑定是利用的Object.defineproperty实现的,它有不少的缺点,例如无法检测到对象属性的新增或删除,无非监听数组变化等.所以在vue3版本,对其进 ...

最新文章

  1. 洛谷 1281 书的复制
  2. Verilog全新语法认识--Xilinx language template
  3. CLR类型设计之属性
  4. android 定制ui,AndroidSDK-UI定制
  5. 最长公共子序列求序列模板提_最长公共子序列
  6. 2021年图灵奖公布!72岁的美国科学家 Jack Dongarra 获奖
  7. spring 监听器简介
  8. First of all, let’s talk about the richest man in Japan
  9. Python内置函数(17)——chr
  10. eviews建立时间序列模型_Eviews系列12|时间序列模型常见问题解答
  11. SQL Cookbook(读书笔记)No.2
  12. Java实现图片任意角度旋转
  13. 计算器与计算机小键盘的使用,会计神器!用上Cherry轴的计算器还能当小键盘用...
  14. 浅谈视频编解码器的工作原理和应用领域
  15. 中国雅虎邮箱将寿终正寝 8月19日停止服务
  16. 操作系统与网络实现 之二十三(丙)
  17. 苹果手机开不了机怎么办
  18. Python实验报告 实验15 - 体育竞技分析
  19. BZOJ4887:[TJOI2017]可乐(矩阵乘法)
  20. 电脑上有哪些剪辑音乐的软件

热门文章

  1. 【心理咨询师考试笔记】操作技能(二)——心理评估
  2. Unicode和Python的中文处理(收藏)
  3. xml使用外部DTD加载验证
  4. 移动端下拉刷新,兼容ios,Android及微信浏览器
  5. Hadoop 集群在WebUI界面不能下载文件
  6. 轻风送暖写诗意,梅雨传情送祝福
  7. python可视化迷宫求解_如何用 Python 制作一个迷宫游戏
  8. 全息网御上榜《CCSIP 2022中国网络安全产业全景图》
  9. 实体门店为什么要做共享股东模式
  10. Mongodb高级查询Aggregation聚合组件分页