toRef 与 toRefs

news2024/11/25 14:59:00

在 ref函数与reactive函数的对比 这一篇博文中,我们从使用角度对比了 ref 与 reactive 的区别,最终得出结论是,

  • 通过 ref 定义的数据,在 js脚本中使用需要 xxx.value ,在模板中会自动解包,可以直接使用
  • 通过reactive定义的数据,分为以下几种情况:
    • 定义的是普通对象、普通数组、通过ref定义的响应式对象,在js和模板中可以直接使用
    • 定义的是通过ref转化的数组、原生集合对象(Map对象),在 js脚本中使用需要 xxx.value ,在模板中会自动解包,可以直接使用

但是,在这里存在一个大家争议比较大的问题,那就是 使用 ref定义,在js中需要 xxx.value。使用 reactive 定义的数据,在模板中需要 xxx.属性。相比较Vue2的直接使用数据的模式,这样写起来都比较麻烦。例如下面的例子:

通过 reactive 定义数据

<template>
  <p>姓名:{{ person.name }}</p>
  <p>年龄:{{ person.name }}</p>
  <p>薪资:{{ person.job.j1.salary }}</p>
  <button @click="person.job.j1.salary++">薪资+1</button>
</template>

<script>
import {reactive} from "vue";

export default {
  name: "TestComponent",
  setup() {
    let person = reactive({
      name:'al',
      age: 28,
      job: {
        j1: {
          work: '前端',
          salary:2
        }
      }
    });

    return {
      person,
    };
  },
};
</script>

我们在 模板中每次都要通过 person.xxx 来使用属性,如果嵌套层级较深,就会像  person.job.j1.salary 一样,写起来太过繁琐。

想法一:直接返回具体数据

可能有人会有想法,那就是我在return 的时候,直接把数据返回出去,而不是返回整个对象,那会是什么情况?

<template>
  <p>姓名:{{ name }}</p>
  <p>年龄:{{ name }}</p>
  <p>薪资:{{ salary }}</p>
  <button @click="salary++">薪资+1</button>
</template>

setup() {
  let person = reactive({
    name:'al',
    age: 28,
    job: {
      j1: {
        work: '前端',
        salary:2
      }
    }
  });

  return {
    name:person.name,
    age:person.age,
    salary:person.job.j1.salary,
  };
},

首先,如果我们直接返回具体数据,那么在模板中初始化肯定是可以直接使用的。

但是如果我点击按钮就会发现,页面完全不更新。

这是因为如果单独返回的是具体数据的话,其实是访问到了响应式对象 person 中的某个具体属性值,相当于返回的是一个具体的字符串,而一个字符串是不具备响应式的。 

也就是说 return 对象中的 name:person.name  其实就是  name:'al'

name:person.name  ===  name:'al'

在这里我们再来一个更加简单易懂的案例说明一下:现在有一个对象 person,我们对其操作

let person = {name:'al',age:28}


// 声明一个变量来接收 person 对象中的 name 属性
let newName = person.name // al


// 然后重新给 name 属性赋值
newName = '汤圆仔'

 你说 源对象person 中的name 属性会发生改变么?那肯定是不可能的,因为这只是值的替换。

newName 更改之后,源对象 person根本没发生改变。

这和上面的例子其实是一个模式,那就是 newName 接收的其实是一个单纯的字符串类型的值,只不过这个值是从 源对象person 中取到的 

想法二:用 ref 包裹具体返回数据

这个时候可能想法又来了,如果返回具体数据的同时,我用 ref 也将它转化为响应式怎么样呢?

return {
  name:ref(person.name),
  age:ref(person.age),
  salary:ref(person.job.j1.salary),
};

可以看到,通过 ref 转化的数据,是可以实现响应式的,但是,真的和我们想的一样么?

按照我们的想法,当我们改变了数据,person 对象也肯定需要同步更改的,不然怎么叫响应式数据。

但是实际上,发现我们 person 对象并没有跟随变化。

这是因为在初始化时,确实是从 person 对象中取值,但是在取值过程中其实和上面的例子一样也只是拿到了一个基础类型的值,通过 ref 转化后,也只是将这个值转化为了响应式,而源对象 person 其实只在初始化时提供了一下值,后续操作毫无关联。

toRef 将数据转化为响应式

概念:可以基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。

语法:toRef(响应式对象, "属性")

const state = reactive({
  foo: 1,
  bar: 2
})

// 双向 ref,会与源属性同步
const fooRef = toRef(state, 'foo')

打印 fooRef 我们可以看到,它其实也是一个 refImpl 响应式数据

同步:

// 更改该 ref 会更新源属性
fooRef.value++
console.log(state.foo) // 2

// 更改源属性也会更新该 ref
state.foo++
console.log(fooRef.value) // 3

所以基于 toRef ,我们可以实现想要的效果。

<template>
  <p>姓名:{{ name }}</p>
  <p>年龄:{{ name }}</p>
  <p>薪资:{{ salary }}</p>
  <button @click="salary++">薪资+1</button>
  <p>{{ person }}</p>
</template>

return {
  name: toRef(person, "name"),
  age: toRef(person, "age"),
  salary: toRef(person.job.j1, "salary"),
};

当我们点击按钮时,salary 会更改,此时 person 对象也会同步更改,实现同源。

相比于 ref(person.name) 是复制了源对象中的数据进行赋值,toRef(person.'name') 则是在访问 value 值时,通过 getter 指向了 源对象 person,本质是引用源对象。

此时,我们在模板上使用数据时,则可以正常使用。但是如果数据太多,每个都单独返回,那也太蠢了,所以 Vue3 提供了批量操作的API-- toRefs

toRefs 批量操作

概念:将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。

语法:toRefs(响应式对象),不需要指定属性,因为转换的是一整个对象

let person = reactive({
  name: "al",
  age: 28,
  job: {
    j1: {
      work: "前端",
      salary: 2,
    },
  },
});

let newPerson = toRefs(person)

打印 newPerson  ,我们发现确实是一个普通对象,但是对象内部的每个属性,都是单独的ref对象

而且对于深度嵌套数据,也实现了响应式。

 因为 toRefs(person) 返回的是一个普通对象,所以在返回时我们需要通过扩展运算符进行解构

return {
  ...toRefs(person),
};

通过  toRefs 转化后,在模板文件中使用时,我们可以省去最外层的一层对象,而直接使用内部属性,例如:省去了 person 对象这一层,而是直接使用内部属性。

<template>
  <p>姓名:{{ name }}</p>
  <p>年龄:{{ age }}</p>
  <p>薪资:{{ job.j1.salary }}</p>
  <button @click="job.j1.salary++">薪资+1</button>
</template>

toRef 和 toRefs 的使用场景

在 setup 函数中,接收的 props 是响应式数据,此时我们只能通过 props.xxx 来使用其中的属性值,如果想要解构之后再使用,则会失去响应式

// 父组件 传递 props 给子组件
<template>
  <Test :a='1' :b='2'></Test>
</template>

// 子组件通过props属性接收,同时在 setup 函数中可以接受到 props 属性接收的 数据
export default {
  name: "TestComponent",
  props: ["a", "b"],
  setup(props, context) {

    console.log(props, "props");    // Proxy(Object) {a: 1, b: 2}

    function test(){
        console.log(props.a); // 只能通过 props.xx使用
    }
    
    let { a } = props // 解构数据

    function test(){
        console.log(a); // 如果解构,那么此时 a 不具有响应式
    }

    return {
      ...toRefs(person),
    };
  },
};

如果你确实需要解构 props 对象,或者需要将某个 prop 传到一个外部函数中并保持响应性,那么你可以使用 toRefs() 和 toRef() 这两个工具函数:

export default {
  name: "TestComponent",
  props: ["a", "b"],
  setup(props, context) {
    
    let { a } = props // 解构数据

    // 将 `props` 的单个属性转为一个 ref
    const title = toRef(props, 'a')

    // 或将 `props` 转为一个其中全是 ref 的对象,然后解构
    const { title } = toRefs(props)

    return {};
  },
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2063521.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

WIN 10 添加右键菜单(VSCode 打开当前目录)

WIN 10 添加右键菜单&#xff08;VSCode 打开当前目录&#xff09; 前言最终效果操作步骤 前言 每次打开代码都需要先打开 VSCode&#xff0c;再选择最近打开的项目或者浏览打开项目&#xff0c;感觉比较难找。所以自己添加了右键命令。 最终效果 操作步骤 cmd 打开注册表 找…

20240821给飞凌OK3588-C的核心板刷Rockchip原厂的Buildroot并启动

20240821给飞凌OK3588-C的核心板刷Rockchip原厂的Buildroot并启动 2024/8/21 15:22 viewproviewpro-ThinkBook-16-G5-IRH:~/repo_RK3588_Buildroot20240508$ viewproviewpro-ThinkBook-16-G5-IRH:~/repo_RK3588_Buildroot20240508$ ./build.sh lunch 3. rockchip_rk3588_evb7_…

C++智能指针的用法(全)

一、智能指针概念 C/C 语言最为人所诟病的特性之一就是存在内存泄露问题&#xff0c;因此后来的大多数语言都提供了内置内存分配与释放功能&#xff0c;有的甚至干脆对语言的使用者屏蔽了内存指针这一概念。这里不置贬褒&#xff0c;手动分配内存与手动释放内存有利也有弊&…

普元EOS-基于CriteriaEntity进行数据查询

1 前言 普元EOS内置了一系列数据库的操作类&#xff0c;本文介绍其中的一个类 CriteriaEntity的使用方法。 CriteriaEntity是进行组织数据库查询条件的类&#xff0c;基于该类配合DataObject&#xff0c;实现对数据库的查询。 2 CriteriaType类的实例化 要利用Criteria进行查…

LlamaIndex 实现 RAG (一)

理解过 LlamaIndex 的功能之后&#xff0c;本文通过 LlamaIndex 快速实现一个简单的 RAG 应用&#xff0c;主要包括以下几个部分&#xff1a; 创建知识库&#xff0c;并进行 Embedding集成本地 Ollama 模型或者 Qwen 模型通过 Streamlit 可视化 RAG 文末提供了源代码地址 创…

HarmonyOS开发实战:应用权限/通知设置跳转方案

场景描述 引导用户跳转到系统设置页进行权限&#xff0c;通知的相关设置&#xff0c;类似android和iOS应用中常见的应用内跳转到设置进行通知开启或权限设置的操作。 应用经常会遇到如下的业务诉求&#xff1a; 场景一&#xff1a;如果应用首次拒绝了消息通知&#xff0c;应…

免费高效:2024年四大视频剪辑软件推荐!

不管是不是专业人士&#xff0c;相信大家多多少少都会有视频剪辑的需求&#xff0c;对于很多新手来说&#xff0c;一款好用且免费的视频剪辑工具十分必要&#xff0c;接下来就为大家推荐几个好用的视频剪辑免费软件&#xff01; 福昕视频剪辑 链接&#xff1a;www.pdf365.cn/…

Linux(CentOS7)虚拟机安装教程

创建虚拟机 自定义高级&#xff0c;就下一步 选择Workstation 17.x,完好后就继续下一步,下面就如图所示 虚拟机内存看情况加 磁盘大小也看情况加 完成&#xff01; 开启此虚拟机 鼠标放进去直接回车 可能有点慢&#xff0c;请耐心等待 一.进入日期时间 二.进入软件选择 三.配置…

[创业之路-138] :产品需求、产品研发、产品生产、库存管理、品控、售后全流程 - 时序图

目录 一、产品研发全流程 1. 客户/市场需求 2. 供应链采购 3. 设计研发 4. 库房管理 5. 品控质检 6. 物流运输 7. 客户现场验证 8. 返修售后 二、产品生产全流程 1. 客户/市场需求 2. 供应链采购 3. 生产加工 4. 库房管理 5. 品控质检 6. 物流运输 7. 客户现场…

物理可微分神经算法:深度学习与物理世界的桥梁

物理可微分神经算法&#xff1a;深度学习与物理世界的桥梁 前言物理可微分神经算法的核心PyTorch中的实现讨论与展望结语 前言 在这个信息爆炸的时代&#xff0c;人工智能&#xff08;AI&#xff09;已成为推动技术革新的关键力量。深度学习&#xff0c;作为AI领域的一个重要分…

CAPL如何实现在网络节点中添加路由Entry

其实不只是CANoe的网络节点,所有设备的应用程序如果要通过Socket套接字发送报文,在网络层都需要根据路由表里配置的路由条目选择发送路径。这个路由条目可以是静态配置,也可以是自动添加。 如果CANoe的网络节点添加一个网络接口,配置IP地址和子网掩码: 说明此网络节点在1…

外挂程序:增强点及辅助

1.关于前几篇介绍的外挂程序,SAP中的业务单据还是要区分具体的操作人员。如建立财务凭证,工号A,B,C使用相同的SAP账号,那就没办法知道是谁操作的了啊,所以sap的业务单据需要细分到具体人员的都要增强实现以下: 如生产工单: 具体的增强点: 2.辅助程序:SAP账号自动锁定功…

【Redis】基本全局命令

Redis的基本全局命令 keysexistsdelexpirettltype Redis 有 5 种数据结构&#xff0c;但它们都是键值对种的值&#xff0c;对于键来说有⼀些通⽤的命令。 keys 返回所有满足样式 &#xff08;pattern&#xff09;的key。支持如下统配样式。 h?llo 匹配 hello , hallo 和 hxl…

D-ID 推出人工智能视频翻译工具,拥有语音克隆和口型同步等功能

D-ID公司以其创新的人工智能技术在视频创作领域取得了突破性进展。这家人工智能视频创作平台最近推出了一项新工具&#xff0c;允许用户将视频翻译成多达30种不同的语言&#xff0c;包括阿拉伯语、普通话、日语、印地语、西班牙语和法语等。这项技术不仅能够自动翻译视频内容&a…

面试题 08.06. 汉诺塔问题(整活版)(不讲武德)

题目具体要求看面试题 08.06. 汉诺塔问题(递归法)-CSDN博客 class Solution { public:void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {CA;A.clear();} };

Blender新手入门笔记收容所(二)

材质篇 学习来源&#xff1a;B站 【Kurt】Blender零基础入门教程 | Blender中文区新手必刷教程(已完结) Blender材质基础 PBR(Physically Based Rendering)&#xff1a;基于物理的渲染BSDFBRDF(反射)BTDF(透射) 原理化BSDF详解 中间部分利用率80% 材质篇第一节课笔记 纹…

健身房预约小程序,提高市场竞争力

随着“全民健身”的风靡&#xff0c;各大健身场所受到了较大的关注&#xff0c;健身市场的发展迎来了爆发期&#xff01;健身房预约系统是一个在线预约管理系统&#xff0c;对于健身房来说&#xff0c;一个操作简单、功能齐全的预约系统至关重要&#xff0c;他不仅可以帮助学员…

代码随想录打卡第六十一天

代码随想录–图论部分 day 62 图论第十一天&#xff08;完结&#xff09; 文章目录 代码随想录–图论部分一、卡码网97--小明逛公园二、卡码网126--骑士的攻击总结 一、卡码网97–小明逛公园 代码随想录题目链接&#xff1a;代码随想录 给定一个公园景点图&#xff0c;图中有…

Flink常用转换(transformation)算子使用教程(DataSTream API)

前言 一个 Flink 程序,其实就是对 DataStream 的各种转换。具体来说,代码基本上都由以下几部分构成,如下图所示: 获取执行环境(execution environment)读取数据源(source)定义基于数据的转换操作(transformations)定义计算结果的输出位置(sink)触发程序执行(exec…

Linux入门——06 基础IO

1.什么是当前路径 exe -> /home/lin/Desktop/Linux_learn/fork_learn/test 当前进程执行是磁盘路径下的哪一个程序 cwd -> /home/lin/Desktop/Linux_learn/fork_learn 当前进程的工作目录------》当前进程 1.1当前路径这个地址能改吗&#xff1f; 可以&#xff0c;使…