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贯穿始终...)


参考文章

<think>我们正在讨论Vue3中的响应式系统,特别是refreactive区别。根据用户的问题,他们特别关注在响应式变量定义上的不同。我们根据提供的引用信息来组织答案。引用[2]提到:reactive用于复杂数据类型,而ref用于基本数据类型。但是引用[3]指出ref可以定义任意类型,通常用于原始数据类型。引用[1]展示了reactive定义的对象,以及使用toRefs来解构保持响应式引用[3]还提到ref返回一个包含.value属性的响应式对象,底层使用Object.defineProperty()(注意:实际上Vue3使用的是Proxy,但ref对基本类型采用类的方式封装,通过.value访问,对于对象类型内部还是转为reactive,所以这里可能有误)。我们需要澄清:1.类型适用:ref可以处理任意类型(基本类型对象),而reactive只能处理对象类型(包括数组、集合等)。2.访问方式:ref需要通过.value访问,而reactive直接访问属性。3.原理:ref内部使用一个对象(RefImpl)来包装值,对于对象类型,内部使用reactive转为深层响应式reactive使用Proxy代理对象。此外,引用[1]还提到了一个常见问题:使用reactive定义的对象在解构时可能丢失响应式,这时需要使用toRefs来保持响应式。因此,我们可以这样组织回答:1.介绍refreactive的基本用途区别(类型、访问方式)。2.解释为什么ref需要.value而reactive不需要。3.提及reactive在解构时的响应式丢失问题以及解决方案(toRefs)。4.深层原理的简要说明(根据引用[3]的内容,但注意修正可能的错误)。注意:回答中引用的末尾添加引用标识,如[^1]。同时,我们需要生成相关问题。注意:用户要求使用中文回答。</think> 好的,我理解您想深入了解 Vue 3 中 `ref` `reactive` 在响应式变量定义上的核心区别。以下是清晰对比,重点突出它们在变量定义时的差异: ### 📌 核心区别对比表 | 特性 | `ref` | `reactive` | |--------------|-------------------------------|--------------------------------| | **适用数据类型** | 基本类型 + 复杂类型 | **仅复杂类型** (对象/数组等) | | **定义方式** | `const var = ref(initialValue)` | `const obj = reactive({...})` | | **响应式结构** | `.value` 包装 (需通过 var.value 读写) | **直接属性访问** (obj.key) | | **解构响应性** | 解构后单个变量仍可维持响应性 | **解构后丢失响应性** | | **原理实现** | 基于 `RefImpl` 对象 + `Object.defineProperty` | 基于 `Proxy` 深度代理对象 | --- ### 🔍 详细解释 1. **数据类型适配性** - `ref`:用于定义任何类型变量。当传入**对象**时内部自动转为 `reactive`[^3] ```javascript const num = ref(0) // ✅ 基本类型 const obj = ref({ count: 1 }) // ✅ 自动转为reactive ``` - `reactive`:**仅支持对象类型**(Object/Array/Map/Set),处理基本类型会抛出警告[^2] ```javascript const state = reactive({ count: 1 }) // ✅ 对象类型 const num = reactive(0) // 🚫 无效 ``` 2. **变量访问方式** - `ref`:必须通过 `.value` 访问(模板内自动解包) ```javascript num.value = 2 // 脚本中必需.value ``` ```html <div>{{ num }}</div> <!-- 模板中自动解包 --> ``` - `reactive`:直接通过属性访问 ```javascript state.count = 2 // 直接修改属性 ``` 3. **解构响应性保持** - `ref`:解构变量仍维持响应性 ```javascript const { value: count } = ref(0) count++ // ✅ 响应式更新 ``` - `reactive`:解构时**必然丢失响应性**[^1],需用 `toRefs` 恢复 ```javascript const state = reactive({ count: 0 }) const { count } = state // 🚫 解构后非响应式 const { count } = toRefs(state) // ✅ 通过toRefs恢复 ``` 4. **原理机制差异** - `ref`:创建 `RefImpl` 对象,通过 `getter/setter` 拦截 `.value` 操作[^3] - `reactive`:使用 `Proxy` 深度代理对象,自动追踪嵌套属性变化[^2] --- ### ❓ 相关问题 1. 为什么 Vue 3 需要同时提供 `ref` `reactive` 两种 API? 2. 如何在 `reactive` 对象中嵌套使用 `ref`?是否需要特殊处理? 3. 解构 `reactive` 对象时使用 `toRefs` 的性能影响是什么? 4.组合式函数中,更推荐使用 `ref` 还是 `reactive` 导出状态? 5. `ref` 的 `.value` 访问在哪些场景下会自动解包? --- [^1]: `reactive` 解构会导致响应式丢失,需通过 `toRefs` 解决 [^2]: `reactive` 适用于复杂数据类型定义 [^3]: `ref` 本质是对值进行对象包装并通过 `.value` 访问
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值