目錄
Ⅰ.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貫穿始終...)
參考文章