vue3中ref和reactive响应式数据、ref模板引用(组合式和选项式区别)、组件ref的使用

目录

Ⅰ.ref

1.基本用法:ref响应式数据

2.ref模板引用

3.ref在v-for中的模板引用

​4.ref在组件上使用

​5.TS中ref数据标注类型

Ⅱ.reactive 

 1.基本用法:reactive响应式数据

 2.TS中reactive标注类型

Ⅲ.ref和reactive的使用场景和区别

Ⅳ.小结&常见疑问解答



Ⅰ.ref

1.基本用法:ref响应式数据

ref 可以对原始类型(基本数据类型)和对象(引用类型)的数据进行响应式处理。
ref 的核心作用是将一个普通的 JavaScript 数据转换成响应式的。它返回一个特殊的对象,这个对象包含一个名为 .value 的属性,我们在JS中就需要通过操作 .value 访问和修改这个ref的响应式数据,但是在模板中不需要加.value来访问这个ref响应式数据的。

基本概念解析
基本数据类型:number, string, boolean, undefined, null
响应式数据:Vue的响应式数据(Reactivity)是Vue.js框架最重要的特性之一,它是Vue实现数据绑定的核心机制。 在Vue中,当数据发生变化时,相关的DOM元素会自动更新,而不需要手动操作DOM。 这种自动更新的机制使得开发者在编写代码时可以更加专注于数据的状态和业务逻辑,而不必担心如何去更新视图。

 如果我们想让一个 名为count的变量 变成响应式的,就可以这样写:

import { ref } from 'vue';

//count是基本数据类型,变为响应式
const count = ref(0);    

现在,count变量就不再是一个普通的数字了,而是一个包含 .value 属性的对象,它的值可以通过 count.value 来访问和修改。当我们修改 count.value 的值时,任何依赖于 count 的页面元素都会自动更新。
我们在JS中访问或修改该 count变量的值的时候,需要加上 .value来进行访问,但是在模板中访问 count 的时候,不需要加上 .value,直接写变量名count即可访问count的值

//在模板中,我们可以不加..value来访问count变量的值:
<template>
  <div>{{ count }}</div>
</template>

//在JS中,我们要加上.value来访问/修改count变量的值:
console.log(count.value); // 0
count.value = 1; 

我们还可以让 包含count属性的对象 变成一个响应式的数据:

//我们可以直接用 ref 包裹整个 对象

//count是对象的属性
const data = ref({ count: 0 })    //对象也可以变成响应式的

这样一来,data 就变成了一个响应式对象,它的所有属性,包括 count,都会变成响应式的。我们在JS中访问或修改该 data对象的属性的时候,需要加上 .value来进行访问,并且在模板中访问 data.count 的时候,也需要加上 .value

//在模板中访问data对象属性count
<template>
<div>{{ data.value.count }}</div>
</template>

//在JS中访问data对象对象count
console.log(data.value.count); // 0

假如我们直接将一个 ref 对象赋值给一个对象的属性,而 ref没有使用.value进行赋值,那么这个属性不会变成响应式的。

有一个名为 data 的对象,我们想让它的 name 属性变成响应式的,下面这种写法是错误的:

因为 data.name 现在存储的是 nameRef 对象本身,而不是它的值。
当我们修改改 nameRef.value 时,data.name 并不会随之改变,页面也不会更新。

const data = { name: 'John' };
const nameRef = ref('John');
data.name = nameRef;   //ref没有使用.value进行赋值,data.name是不会变成响应式的

正确的做法是将 nameRef.value 赋值给 data.name 或者可以直接用 ref 包裹整个 data 对象,以下是正确示例:

//使用nameRef.value进行赋值
data.name = nameRef.value;

//或者直接用ref包括整个data对象
const data = ref({ name: 'John' });

ref 小结:ref 是 Vue 3 中创建响应式数据的基本方法,但它并不会直接将一个普通的 JavaScript 对象变成响应式的。我们需要通过 .value 来访问和修改响应式数据,或者直接用 ref 包裹整个对象,才能实现真正的响应式更新。

2.ref模板引用

如果我们需要直接访问组件中的底层DOM元素,可使用vue提供特殊的ref属性来进行访问

我们可以在元素上使用ref属性来设置需要访问的DOM元素

  • ref属性值是 字符串 的形式;
  • ref属性值还可以是用v-bind::ref形式绑定的函数,该函数的第一个参数是该元素;

如果元素的 ref属性值 采用的是字符串形式:

  • 在组合式API的JS中,我们需要声明一个同名的ref变量,来获得该模板的引用;
  • 在选项式API的JS中,可通过this.$refs来访问模板的引用;

 组合式API:

<template>

  <!-- 字符串形式的 ref -->
  账号输入框:<input type="text" ref="account">
  <button @click="accountInputStyle">改变账号输入框的样式</button>

  <!-- 函数形式的 ref ,必须采用 v-bind 或 : 的形式来给ref绑定属性值 -->
  密码输入框:<input type="password" :ref="passwordFn">
  <button @click="passwordInputStyle">改变密码输入框的样式</button>

</template>

<script setup>
import { ref, reactive, computed, onMounted, nextTick } from 'vue'

//定义响应式数据(同名ref变量)
// ref 变量名 和 对应DOM元素的ref属性值 相等
let account = ref(null)
let password = ref(null)

const accountInputStyle = () => {
  //account是获取的输入框DOM元素
  account.value.style.padding = '15px'
  account.value.style.caretColor = 'red'
  account.value.className = 'rounded'
  account.value.focus()
}

// 使用函数给ref绑定属性值,该函数的第一个参数为该元素
// 在页面渲染的时候会自动执行
// 函数式生命的 ref,不会在 this.$refs 中获取
const passwordFn = (el) => {
  // el是DOM元素,这里是密码输入框
  password.value = el
  console.log(password.value)
}
const passwordInputStyle = () => {
  //此处设置的 style 均为行内样式
  password.value.style.border = '4px solid green'
  password.value.style.padding = '15px'
  password.value.focus()
}

onMounted(() => {});
</script>

<style scoped>
</style>

选项式API:

<template>

  <!-- 字符串形式的 ref -->
  账号输入框:<input type="text" ref="account">
  <button @click="accountInputStyle">改变账号输入框的样式</button>

  <!-- 函数形式的 ref ,必须采用 v-bind 或 : 的形式来给ref绑定属性值 -->
  密码输入框:<input type="password" :ref="passwordFn">
  <button @click="passwordInputStyle">改变密码输入框的样式</button>

</template>

<script>
export default {
    data: () => ({
        accountEl: null,
        passwordEl: null
    }),
    methods: {
        changeAccountInputStyle() {
            this.accountEl = this.$refs.account // 获取账号输入框的 DOM
            console.log(this.accountEl)
            this.accountEl.style = "padding: 15px"
            this.accountEl.className = "rounded"
            this.accountEl.focus()
        },
        passwordRef(el) { 
            this.passwordEl = el  // el 元素是密码输入框
        },
        changePasswordInputStyle() {
            console.log(this.passwordEl) 
            console.log(this.$refs) // 函数式声明的 ref,不会在this.$refs中获取
            this.passwordEl.style = "padding: 15px"
            this.passwordEl.className = "rounded"
            this.passwordEl.focus()
        },
    }
}
</script>

<style>
</style>

3.ref在v-for中的模板引用

当在v-for中使用模板引用时:(注意:需要 v3.2.25 及以上的版本;)

  • 如果 ref 值是 字符串 形式,在元素被渲染后包含对应整个 列表的所有元素【数组】
  • 如果 ref 值是 函数 形式,则会每渲染一个列表元素就会执行对应的函数【不推荐使用】;

 组合式API:

<template>
  <ul>
    <li v-for="b in books" :key="b.id" ref="bookList">
      {{ b.name }}
    </li>
  </ul>
</template>

<script setup>
import { onMounted, ref } from "vue";

// 书本
let books = ref([
  { id: 1, name: "海底两万里" },
  { id: 2, name: "骆驼祥子" },
  { id: 3, name: "老人与海" },
  { id: 4, name: "安徒生童话" },
]);

let bookList = ref(null);

onMounted(() => {
  console.log(bookList.value); // 获取引用的 DOM 对象并打印,发现是数组
  bookList.value[2].className = "error";
});
</script>

<style>
.error {
  border: 1px solid red;
}
</style>

选项式API:

<template>
  <ul>
    <!-- 如果 ref 值是字符串形式,在元素被渲染后包含对应整个列表的所有元素【数组】 -->
    <li v-for="b in books" :key="b.id" ref="bookList">
      {{ b.name }}
    </li>
  </ul>
  <button @click="changeBookListStyle">点我查看 bookList</button>

  <hr />
  <!-- 如果ref值是函数形式,则会每渲染一个列表元素则会执行对应的函数【不推荐使用】 -->
  <ul>
    <li v-for="s in students" :key="s.id" :ref="studentsRef">
      {{ s.name }}
    </li>
  </ul>
</template>

<script>
export default {
  data: () => ({
    books: [
      { id: 1, name: "红楼梦" },
      { id: 2, name: "三国演义" },
      { id: 3, name: "水浒传" },
      { id: 4, name: "西游记" },
    ],
    students: [
      { id: 1, name: "Jack" },
      { id: 2, name: "Annie" },
      { id: 3, name: "Tom" },
    ],
  }),
  methods: {
    changeBookListStyle() {
      console.log(this.$refs.bookList);
      this.$refs.bookList[2].style = "color: red";
    },
    studentsRef(el) {
      console.log(el);
    },
  },
};
</script>

两种运行效果:


4.ref在组件上使用

模板引用也可以被用在一个子组件上:这种情况下引用中获得的值是组件实例

  • 如果子组件使用的是组合式API<script setup>,那么该子组件默认是私有的,则父组件无法访问该子组件,除非子组件在其中通过defineExpose宏采用对象形式显示暴露特定的数据或函数
  • 如果子组件使用的选项式API默认情况下父组件可以随意访问该子组件的数据和函数,除非在子组件使用expose选项来暴露特定的数据或函数,那么父组件就只能访问在expose种暴露的数据或函数,没有暴露的就不能访问了。expose值为字符串数组

 组合式API:

  • 父组件:
    <template>
      <h3>登录页面</h3>
      <hr>
      <!-- 组件上的 ref 的值为该组件的实例 -->
      <Vue1 ref="login_vue"></Vue1>
      <hr>
      <button @click="showSonData">查看子组件的信息</button>
    </template>
    
    <script setup>
    //组合式API中,默认情况下,子组件中的数据、函数等等都是私有的,不能访问
    //如果 子组件 通过 defineExpose 宏采用对象形式显式暴露特定的数据或函数等等
    
    import Vue1 from '@/components/src/test.vue'    //导入子组件
    import { ref, onMounted } from 'vue'
    const login_vue = ref(null)
    const showSonData = () => {
      console.log(login_vue.value.account)
      console.log(login_vue.value.password)
      login_vue.value.toLogin()
    }
    onMounted(() => {});
    </script>
    
    
  • 子组件:
    <template>
      账号:<input type="text" v-model="account">
      <br>
      密码:<input type="text" v-model="password">
      <hr>
      <button @click="toLogin">登录</button>
    </template>
    
    <script setup>
    import { ref } from 'vue'
    const account = ref('admin')
    const password = ref('123456')
    const toLogin = () => {
      alert('登录中……')
    }
    // 使用 defineExpose 将指定数据、函数等暴露出去
    defineExpose({
      account,
      toLogin
    });
    </script>

 效果展示:


选项式API:

  • 父组件:
<template>
  <h3>登录页面</h3>
  <hr>
  <!-- 组件上的 ref 的值为该组件的实例 -->
  <Vue1 ref="loginVue"></Vue1>
  <hr>
  <button @click="showSonData">查看子组件的信息</button>
</template>

<script>
//选项式API中,默认情况下,父组件可以随意访问子组件的数据和函数、计算属性等等
//如果 子组件 增加 expose 选项之后,就只能访问 expose 暴露的属性和函数等等

import Vue1 from '@/components/src/test1.vue'
export default {
  name: 'App',
  components: { Vue1 },
  data: () => ({
    login_vue: null
  }),
  methods: {
    showSonData () {
      console.log(this.login_vue.account)
      console.log(this.login_vue.password)
      this.login_vue.toLogin()
    }
  },
  mounted () {
    // 打印出来的式子组件的ref对象
    this.login_vue = this.$refs.loginVue
    console.log(this.login_vue)
  }
}
</script>

子组件:

<template>
  账号:<input type="text" v-model="account">
  <br>
  密码:<input type="text" v-model="password">
  <hr>
  <button @click="toLogin">登录</button>
</template>

<script>
export default {
  name: 'Vue1',
  data: () => ({
    account: 'admin',
    password: '123456'
  }),
  methods: {
    toLogin () {
      alert('登录中……')
    }
  },
  //向外暴露属性函数,增加这个选项之后,父组件只能访问该组件暴露的属性或方法等等
  expose: ['account', 'password']
}
</script>

<style scoped lang='scss'>
</style>

效果展示:

5.TS中ref数据标注类型

import { ref, Ref } from 'vue'
const ref1: Ref<number> = ref(0);

Ⅱ.reactive 

 1.基本用法:reactive响应式数据

reactive 不能基本类型的数据变为响应式,只适用于引用类型的数据(对象)。

reactive响应式数据由于是 proxy 代理的对象数据,可以直接获取到数据,不必添加 .value,即不论是在模板中还是JS中不需要加.value即可访问或修改reactive响应式数据的值。

const data = reactive({ num: 0});    //只能是引用类型,不能是基本类型
console.log(data.num); // 0

 2.TS中reactive标注类型

传给 reactive 函数的对象类型是什么,就给返回值对应的什么类型即可。

注意:如果这个对象当中又包含了 ref,这个时候 ref 是不需要添加对应的类型的,vue 会自动将其解包。

import { reactive } from 'vue'
const data: { num: number } = reactive({ num: 0});

//如果对象中本身就包含了ref时:
import { reactive } from 'vue'
const reactive1: { num: number } = reactive({ num: ref(0)});

Ⅲ.ref和reactive的使用场景和区别

  1. 如果需要一个响应式原始值,那么使用 ref() 是正确的选择,要注意是原始值
  2. 如果需要一个响应式对象,层级不深,那么使用 ref 也可以
  3. 如果需要一个响应式可变对象,并且对象层级较深,需要深度跟踪,那么使用 reactive

Ⅳ.小结&常见疑问解答

1. 为什么在模板中访问 ref 数据不需要加上 .value
在模板中,Vue 会自动解包 ref 数据,所以我们可以直接使用 count 而不是 count.value,但是在 JavaScript 代码中,我们需要使用 .value 来访问 ref 数据的值。

2. 如何在组件之间传递 ref 数据?
可以通过 props 传递 ref 数据。在子组件中,可以使用 defineProps 来接收 ref 数据,并使用 .value 来访问它的值。

3. 如何监听 ref 数据的变化?
可以使用 watch 函数来监听 ref 数据的变化。watch 函数接收两个参数:第一个参数是要监听的数据,第二个参数是一个回调函数,当数据发生变化时,回调函数会被执行。

4. ref 可以用于哪些类型的数据?
ref 可以用于任何类型的 JavaScript 数据,包括基本类型(例如数字、字符串、布尔值)、对象、数组等等。(所以也可以使用ref贯穿始终...)


参考文章

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机毕业设计SpringBoot+Vue.js视频网站系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

LVS+Keepalived 高可用集群搭建

一、高可用集群&#xff1a; 1.什么是高可用集群&#xff1a; 高可用集群&#xff08;High Availability Cluster&#xff09;是以减少服务中断时间为目地的服务器集群技术它通过保护用户的业务程序对外不间断提供的服务&#xff0c;把因软件、硬件、人为造成的故障对业务的影响…

macos下myslq图形化工具之Sequel Ace

什么是Sequel Ace 官方github&#xff1a;https://github.com/Sequel-Ace/Sequel-Ace Sequel Ace 是一款快速、易于使用的 Mac 数据库管理应用程序&#xff0c;用于处理 MySQL 和 MariaDB 数据库。 Sequel Ace 是一款开源项目&#xff0c;采用 MIT 许可证。用户可以通过 Ope…

lvgl运行机制分析

lv_timer_handler() 是 LVGL 的“心脏”&#xff1a;这个函数会依次做以下事情&#xff1a; 处理定时器&#xff08;如动画、延迟回调&#xff09;。 读取输入设备&#xff08;如触摸屏、按键的状态&#xff09;。 刷新脏区域&#xff08;仅重绘屏幕上发生变化的区域&#xf…

C++ | 高级教程 | 文件和流

&#x1f47b; 概念 文件流输出使用标准库 fstream&#xff0c;定义三个新的数据类型&#xff1a; 数据类型描述ofstream输出文件流&#xff0c;用于创建文件并向文件写入信息。ifstream输入文件流&#xff0c;用于从文件读取信息。fstream文件流&#xff0c;且同时具有 ofst…

Linux:Shell环境变量与命令行参数

目录 Shell的变量功能 什么是变量 变数的可变性与方便性 影响bash环境操作的变量 脚本程序设计&#xff08;shell script&#xff09;的好帮手 变量的使用&#xff1a;echo 变量的使用&#xff1a;HOME 环境变量相关命令 获取环境变量 环境变量和本地变量 命令行…

Halcon 学习之路 set_grayval 算子

gen_imag_const 创建灰度图像 gen_image_const(Image&#xff0c;Type&#xff0c;Width&#xff0c;Height) 算子gen_image_const创建指定大小的图像&#xff0c;图像的宽度和高度由Width和Height决定 Type 像素类型 byte :每像素1字节&#xff0c;无符号&#xff08;0-255&…

基于springboot学生管理系统

目录 项目介绍 图片展示 运行环境 项目介绍 管理员 学生信息管理&#xff1a;查询、添加、删除、修改学生信息 班级信息管理&#xff1a;查询、添加、删除、修改班级信息 教师信息管理&#xff1a;查询、添加、删除、修改教师信息 课程信息管理&…

wav格式的音频压缩,WAV 转 MP3 VBR 体积缩减比为 13.5%、多个 MP3 格式音频合并为一个、文件夹存在则删除重建,不存在则直接建立

&#x1f947; 版权: 本文由【墨理学AI】原创首发、各位读者大大、敬请查阅、感谢三连 &#x1f389; 声明: 作为全网 AI 领域 干货最多的博主之一&#xff0c;❤️ 不负光阴不负卿 ❤️ 文章目录 问题一&#xff1a;wav格式的音频压缩为哪些格式&#xff0c;网络传输给用户播放…

JavaWeb-Servlet对象生命周期

文章目录 关于Servlet对象的生命周期创建和销毁Servlet对象的流程测试先后顺序在服务器启动时就创建实例tip: init和无参构造的作用差不多, 为什么定义的规范是init() 关于Servlet对象的生命周期 我们都知道, 我们开发一个Servlet程序的时候, 每一个类都要实现Servlet接口, 然…

在docker容器中运行Ollama部署deepseek-r1大模型

# 启动ollama容器 docker run -itd --gpusall -v /app/ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama:0.5.12# 进入容器 docker exec -it ollama bash ## 拉取大模型&#xff08;7B为例&#xff09; ollama pull deepseek-r1:7b## 修改监听地址和端口 expo…

git - study

文章目录 git - study概述可以用 git gui工具来添加快捷命令工具如果要在提交日志中搜索&#xff0c;可以用gitk的view编辑功能实验环境直接用git自带环境进行git操作的好处查看git所有配置配置全局数据配置项目专用的数据查询配置数据的原始值配置git使用的文本编辑器获取某个…

光谱相机的市场发展趋势

市场规模增长 整体市场稳步扩张&#xff1a;据贝哲斯咨询预测&#xff0c;高光谱相机市场在未来几年将保持稳步增长&#xff0c;2022 年市场规模约为 20 亿美元&#xff0c;预计到 2027 年将达到 30 亿美元&#xff0c;年均复合增长率约为 8%&#xff0c;到 2030 年市场规模将…

编写一个程序,输入两个数字并输出它们的和(Python版)

编写一个程序&#xff0c;输入两个数字并输出它们的和 以下是一个简单的 Python 程序&#xff0c;它会从用户输入中读取两个数字&#xff0c;并输出它们的和&#xff1a; # 获取用户输入的两个数字 num1 float(input("请输入第一个数字: ")) num2 float(input(&qu…

STM32——HAL库开发笔记21(定时器2—输出比较)(参考来源:b站铁头山羊)

本文主要讲述输出比较及PWM信号相关知识。 一、概念 所谓输出比较&#xff0c;就是通过单片机的定时器向外输出精确定时的方波信号。 1.1 PWM信号 PWM信号即脉冲宽度调制信号。PWM信号的占空比 &#xff08;高电压 所占周期 / 整个周期&#xff09; * 100% 。所以PWM信号…

1.2 Kaggle大白话:Eedi竞赛Transformer框架解决方案02-GPT_4o生成训练集缺失数据

目录 0. 本栏目竞赛汇总表1. 本文主旨2. AI工程架构3. 数据预处理模块3.1 配置数据路径和处理参数3.2 配置API参数3.3 配置输出路径 4. AI并行处理模块4.1 定义LLM客户端类4.2 定义数据处理函数4.3 定义JSON保存函数4.4 定义数据分片函数4.5 定义分片处理函数4.5 定义文件名排序…

从零开始自主「起身站立」,上海AI Lab发布最新控制算法,机器人:起猛了

来源 | 机器之心 近日&#xff0c;上海 AI Lab 具身智能中心研究团队在机器人控制领域取得了最新突破&#xff0c;提出的 HoST&#xff08;Humanoid Standing-up Control&#xff09;算法&#xff0c;成功让人形机器人在多种复杂环境中实现了自主站起&#xff0c;并展现出强大…

C#贪心算法

贪心算法&#xff1a;生活与代码中的 “最优选择大师” 在生活里&#xff0c;我们常常面临各种选择&#xff0c;都希望能做出最有利的决策。比如在超市大促销时&#xff0c;面对琳琅满目的商品&#xff0c;你总想用有限的预算买到价值最高的东西。贪心算法&#xff0c;就像是一…

登录次数限制

文章目录 一、应用场景与设计目的1. 应用场景2. 设计目的 二、功能设计1. 登录限制规则2. 解锁机制3. 适用维度 三、技术实现1. 数据存储2. 逻辑流程3. 实现代码示例4. 动态锁定时间 四、安全增强与扩展1. 防止用户名枚举2. 加入验证码3. 监控与报警4. 分布式支持 五、设计思考…

Java从根上理解 ConcurrentHashMap:缓存机制与性能优化

目录 一、ConcurrentHashMap 的核心原理1. 数据结构2. 锁机制3. 扩容机制二、ConcurrentHashMap 的缓存机制1. 缓存的实现2. 缓存的更新策略三、ConcurrentHashMap 的性能优化1. 减少锁竞争2. 优化数据结构3. 合理设置容量和负载因子四、具体代码示例1. 创建 ConcurrentHashMap…