目录
Ⅰ.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 = elconsole.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 // 获取账号输入框的 DOMconsole.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.loginVueconsole.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的使用场景和区别
- 如果需要一个响应式原始值,那么使用 ref() 是正确的选择,要注意是原始值
- 如果需要一个响应式对象,层级不深,那么使用 ref 也可以
- 如果需要一个响应式可变对象,并且对象层级较深,需要深度跟踪,那么使用 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贯穿始终...)
参考文章