一文搞懂Vue3中的ref和reactive
引言
在 Vue3 中,響應(yīng)式最常用的兩個(gè) API 就是 ref 和 reactive。很多開(kāi)發(fā)者一開(kāi)始對(duì)它們的區(qū)別不夠明確,看到任何狀態(tài)就想用 ref,或者對(duì)對(duì)象也習(xí)慣性用 ref 包一層,導(dǎo)致代碼可讀性、維護(hù)性下降,或者出現(xiàn)解構(gòu)導(dǎo)致響應(yīng)丟失、整體替換麻煩等問(wèn)題。
1. 基本概念
ref:用于包裝一個(gè)獨(dú)立的響應(yīng)式值。創(chuàng)建后會(huì)返回一個(gè)包含
.value的對(duì)象,內(nèi)部對(duì).value做響應(yīng)式跟蹤。模板中 Vue 會(huì)自動(dòng)解包.value,在 JS 邏輯里需顯式用.value訪(fǎng)問(wèn)或修改。import { ref } from 'vue'; const count = ref(0); count.value++; // 觸發(fā)響應(yīng)reactive:用于把一個(gè)對(duì)象或數(shù)組變?yōu)轫憫?yīng)式 Proxy。返回的就是該對(duì)象的代理,訪(fǎng)問(wèn)寫(xiě)法如
state.prop,對(duì)內(nèi)部嵌套對(duì)象/數(shù)組會(huì)遞歸轉(zhuǎn)為響應(yīng)式。import { reactive } from 'vue'; const state = reactive({ a: 1, b: { c: 2 } }); state.a = 3; // 觸發(fā)響應(yīng) state.b.c = 5; // 嵌套也響應(yīng)
關(guān)鍵區(qū)別
ref更適合包裝原始類(lèi)型或需要整體替換的場(chǎng)景;.value代表實(shí)際值。reactive適合包裝多字段對(duì)象/數(shù)組,直接訪(fǎng)問(wèn)屬性更簡(jiǎn)潔。
2. 訪(fǎng)問(wèn)與解包:模板 vs JS 邏輯
模板中:Vue 會(huì)自動(dòng)對(duì)
ref進(jìn)行解包。例如:<template> <div>{{ count }}</div> <!-- 如果 count = ref(0),模板會(huì)顯示 0 --> <div>{{ state.a }}</div> <!-- state = reactive({ a: ... }) --> </template>模板里使用
ref、reactive返回的變量都可直接寫(xiě),不用加.value。JS 邏輯里:
ref:必須用.value訪(fǎng)問(wèn)/賦值。reactive:直接用state.prop、state.prop = newValue。
示例:
import { ref, reactive } from 'vue';
export default {
setup() {
const loading = ref(false);
const filters = reactive({ category: '', minPrice: null, maxPrice: null });
function doSomething() {
loading.value = true; // ref
filters.category = 'electronics'; // reactive
}
return { loading, filters, doSomething };
}
};3. 場(chǎng)景對(duì)比:何時(shí)用 ref,何時(shí)用 reactive
下面以電商常見(jiàn)需求為例,了解它們的使用差異。
3.1 單值狀態(tài) vs 多字段狀態(tài)
單值、標(biāo)志位、計(jì)數(shù)器、頁(yè)碼、布爾 loading、是否收藏 等,典型用
ref。語(yǔ)義上就是一個(gè)變量,訪(fǎng)問(wèn)/修改都集中在.value,且若需要整體替換該值(如重置、切換)也很方便。const page = ref(1); const loading = ref(false); const isFavorite = ref(false); // 切換時(shí): page.value = 1; isFavorite.value = true/false
多個(gè)相關(guān)字段聚合成一個(gè)對(duì)象,如搜索篩選條件、表單數(shù)據(jù)、購(gòu)物車(chē)項(xiàng)集合和詳情對(duì)象等,用
reactive更直觀:const filters = reactive({ keyword: '', category: '', price: { min: null, max: null } }); // 修改時(shí): filters.keyword = 'xxx'; filters.price.min = 10若拆成多個(gè) ref:
const keyword = ref(''),const category = ref(''), …,當(dāng)字段較多時(shí)不易管理;也無(wú)法一次性傳遞或傳入 API。
3.2 整體替換 vs 逐字段更新
需要整體替換狀態(tài)對(duì)象:如“加載遠(yuǎn)程購(gòu)物車(chē)數(shù)據(jù)并直接賦予當(dāng)前狀態(tài)”“一鍵清空并恢復(fù)初始對(duì)象”等。用
ref包對(duì)象更簡(jiǎn)單:const cartRef = ref({ items: [], couponCode: '', total: 0 }); // 加載后整體替換 cartRef.value = newCartObj;若用
reactive:const cart = reactive({ items: [], couponCode: '', total: 0 });
// 替換時(shí)不能直接 cart = newCartObj,否則不會(huì)觸發(fā)響應(yīng);需要:
Object.assign(cart, newCartObj);
// 或手動(dòng)清空 items: cart.items.splice(0), 重置其他字段
`Object.assign` 寫(xiě)法較繁瑣,且當(dāng)對(duì)象屬性更新邏輯復(fù)雜時(shí)需注意字段對(duì)齊。
- **只修改某些字段/數(shù)組操作**:如在購(gòu)物車(chē)中增加、減少某項(xiàng)數(shù)量、移除某項(xiàng)、更新優(yōu)惠券、增加瀏覽次數(shù)等,多是逐字段操作,更適合 `reactive`,寫(xiě)法簡(jiǎn)潔:
const cart = reactive({ items: [], couponCode: '', total: 0 });
function increase(idx) {
cart.items[idx].quantity++;
recalcTotal();
}如果用 ref,寫(xiě)成 cartRef.value.items[idx].quantity++,多了 .value,可讀性略差。
3.3 組合場(chǎng)景示例
以搜索篩選和分頁(yè)為例,結(jié)合 ref/reactive:
<template>
<input v-model="searchQuery" @keyup.enter="doSearch" placeholder="搜索商品" />
<select v-model="filters.category">
<option value="">全部</option>
<option value="electronics">電子</option>
</select>
<div>
<button @click="prevPage" :disabled="page <= 1">上一頁(yè)</button>
<span>第 {{ page }} 頁(yè)</span>
<button @click="nextPage">下一頁(yè)</button>
</div>
<div v-if="loading">加載中...</div>
<ul v-else>
<li v-for="item in list" :key="item.id">{{ item.name }}</li>
</ul>
</template>
<script setup>
import { ref, reactive } from 'vue';
const searchQuery = ref('');
const page = ref(1);
const loading = ref(false);
const filters = reactive({ category: '', priceRange: { min: null, max: null } });
const list = ref([]);
async function fetchData() {
loading.value = true;
// 構(gòu)造參數(shù)
const params = {
q: searchQuery.value,
category: filters.category,
min: filters.priceRange.min,
max: filters.priceRange.max,
page: page.value
};
// 假設(shè)調(diào)用接口返回 items、hasMore
const res = await fetchProducts(params);
list.value = res.items;
loading.value = false;
}
function doSearch() {
page.value = 1;
fetchData();
}
function prevPage() {
if (page.value > 1) {
page.value--;
fetchData();
}
}
function nextPage() {
page.value++;
fetchData();
}
</script>searchQuery,page,loading,list獨(dú)立值用ref;filters多字段用reactive。兩者結(jié)合,寫(xiě)法語(yǔ)義清晰、邏輯分明。
4. 解構(gòu)與響應(yīng)丟失:常見(jiàn)陷阱
reactive 解構(gòu)后喪失響應(yīng)
const state = reactive({ a: 1, b: 2 }); const { a, b } = state; // 這里的 a、b 都是普通值,不再是響應(yīng)式。模板或 watch 無(wú)法再跟蹤它們。解決:若想解構(gòu)并保持響應(yīng),可用
toRefs:import { reactive, toRefs } from 'vue'; const state = reactive({ a: 1, b: 2 }); const { a, b } = toRefs(state); // a、b 都是 ref,保持響應(yīng)
ref 解構(gòu)注意
對(duì)于const count = ref(0),一般直接用count;不應(yīng)做const { value } = count,因?yàn)槟玫降?value是初始值,后續(xù)對(duì)count.value修改不會(huì)更新這個(gè)解構(gòu)后的value變量,也不會(huì)觸發(fā)響應(yīng)。整體替換 vs 解構(gòu)
如果本來(lái)想整體替換 reactive 對(duì)象,解構(gòu)后再合并新對(duì)象會(huì)更復(fù)雜。一般用 ref 包對(duì)象來(lái)明確表示整體替換。
5. 深度 vs 淺層響應(yīng)
- reactive 默認(rèn)深度遞歸:內(nèi)部嵌套對(duì)象/數(shù)組會(huì)在訪(fǎng)問(wèn)時(shí)或初始化時(shí)轉(zhuǎn)為 Proxy。
- ref 包對(duì)象:當(dāng)值是對(duì)象或數(shù)組時(shí),Vue 內(nèi)部會(huì)對(duì)其做 reactive 處理,達(dá)到深度響應(yīng)。但訪(fǎng)問(wèn)時(shí)仍需
.value。 - shallowReactive / shallowRef:在特殊場(chǎng)景下,如果不想對(duì)深層嵌套做自動(dòng)響應(yīng),可使用淺響應(yīng) API。但多數(shù)情況下默認(rèn)深度足夠。
示例:若想對(duì)頂層字段變化跟蹤,但不關(guān)心內(nèi)部深層變化,可用 shallowReactive({ nested: { ... } })。
6. 團(tuán)隊(duì)實(shí)踐與語(yǔ)義統(tǒng)一
保持一致性:團(tuán)隊(duì)可制定簡(jiǎn)單約定,比如:
- 單值用
ref;多字段狀態(tài)用reactive。 - 若有大量整體替換場(chǎng)景,將對(duì)應(yīng)對(duì)象用
ref包裹,并在注釋或文檔中標(biāo)明“此狀態(tài)將整體賦值替換”。 - 避免隨意把對(duì)象都用
ref包一層或把所有狀態(tài)都放到一個(gè)大 reactive 對(duì)象,導(dǎo)致解構(gòu)、替換、類(lèi)型推斷等復(fù)雜。
- 單值用
TypeScript 友好:無(wú)論 ref 還是 reactive,都有相應(yīng)類(lèi)型推斷??山Y(jié)合接口定義:
interface Cart { items: CartItem[]; couponCode: string; total: number; } const cartRef = ref<Cart>({ items: [], couponCode: '', total: 0 }); // 或 reactive: const cart = reactive<Cart>({ items: [], couponCode: '', total: 0 });- 如果用 reactive,類(lèi)型推斷里訪(fǎng)問(wèn)
cart.items等一目了然;用 ref 包對(duì)象時(shí)訪(fǎng)問(wèn)需.value.items,類(lèi)型也清晰,但習(xí)慣上需注意。
- 如果用 reactive,類(lèi)型推斷里訪(fǎng)問(wèn)
可讀性與維護(hù):若某狀態(tài)對(duì)象很大且只在特定場(chǎng)景整體替換,ref 包對(duì)象能讓代碼一眼看出“這是一個(gè)整體”;若多個(gè)位置需單字段修改,用 reactive 寫(xiě)法更簡(jiǎn)潔。結(jié)合團(tuán)隊(duì)項(xiàng)目實(shí)際需求,選擇更貼近業(yè)務(wù)意圖的方式。
7. 性能與底層實(shí)現(xiàn)簡(jiǎn)述
- 底層都是 Proxy & effect:
reactive底層用 Proxy 攔截 get/set;ref底層實(shí)現(xiàn)封裝了對(duì).value的 track/trigger,如果值是對(duì)象內(nèi)部也會(huì)遞歸轉(zhuǎn) reactive。 - 性能差異極小:兩者在常規(guī)使用下性能相當(dāng)。區(qū)別主要在語(yǔ)義和寫(xiě)法。
- 初始化開(kāi)銷(xiāo):reactive 在訪(fǎng)問(wèn)深層屬性時(shí)會(huì)懶遞歸(或初始化時(shí)深度遍歷,取決內(nèi)部實(shí)現(xiàn)策略),而 ref 包基本類(lèi)型開(kāi)銷(xiāo)更??;但在對(duì)象場(chǎng)景,開(kāi)銷(xiāo)差異在可接受范圍。通常不必為性能過(guò)度擔(dān)心,而是根據(jù)使用場(chǎng)景選擇更清晰易維護(hù)的方式。
8. 常見(jiàn)誤區(qū)糾正
“所有狀態(tài)都用 ref”
- 雖然把對(duì)象用
ref(obj)也能響應(yīng),但會(huì)導(dǎo)致 JS 邏輯里到處出現(xiàn).value,可讀性差;解構(gòu)/傳參麻煩;整體替換與逐字段更新語(yǔ)義不夠明確。 - 正確做法應(yīng)先判斷:如果只是想更新字段,推薦 reactive;若經(jīng)常整體賦新值,ref 包對(duì)象可考慮。
- 雖然把對(duì)象用
“所有狀態(tài)都用 reactive”
- 當(dāng)只需管理單一值時(shí),用 reactive 會(huì)將其包在對(duì)象里(如
reactive({ count: 0 })),這寫(xiě)法冗余;并且整體替換需要 Object.assign,不夠直觀。 - 對(duì)于標(biāo)志位、計(jì)數(shù)、單值 API 返回?cái)?shù)據(jù)、Boolean 開(kāi)關(guān)等,推薦用 ref。
- 當(dāng)只需管理單一值時(shí),用 reactive 會(huì)將其包在對(duì)象里(如
忽視解構(gòu)導(dǎo)致響應(yīng)丟失
- 解構(gòu) reactive 對(duì)象字段時(shí)要用
toRefs;否則解構(gòu)后的變量再修改無(wú)法觸發(fā)視圖更新。面試中若提到解構(gòu),需說(shuō)明解決方案。
- 解構(gòu) reactive 對(duì)象字段時(shí)要用
混用場(chǎng)景未做區(qū)分
- 例如把整個(gè)表單數(shù)據(jù)既用 reactive 管理,也在某些地方把它賦值給 ref,再修改一半字段時(shí)容易混淆響應(yīng);要在代碼風(fēng)格上統(tǒng)一,明確何時(shí)整體替換、何時(shí)字段更新。
9. 總結(jié)
核心認(rèn)識(shí):
ref與reactive并非完全可互換,而是分別針對(duì)“獨(dú)立值/整體替換”和“復(fù)合對(duì)象/逐字段更新”場(chǎng)景設(shè)計(jì)。理解兩者語(yǔ)義、訪(fǎng)問(wèn)方式和解構(gòu)注意。建議:
- 先分析業(yè)務(wù)需求:狀態(tài)是單一值還是多字段聚合?是否需要整體替換或只是字段更新?是否會(huì)在多個(gè)地方解構(gòu)或傳遞?
- 按需選擇:簡(jiǎn)單標(biāo)志、計(jì)數(shù)、頁(yè)碼、loading、Boolean、ID 等用
ref;多個(gè)字段組合成配置、表單、購(gòu)物車(chē)列表、復(fù)雜詳情對(duì)象等用reactive。 - 若有整體替換需求,也可用
ref包對(duì)象。若用 reactive,注意用Object.assign或手動(dòng)清理以觸發(fā)響應(yīng)。 - 謹(jǐn)慎解構(gòu):了解
toRefs用法;在 Composition API 里盡量直接操作 ref/reactive,避免無(wú)謂解構(gòu)。 - 團(tuán)隊(duì)約定:統(tǒng)一風(fēng)格,避免不同開(kāi)發(fā)者隨意選擇導(dǎo)致代碼風(fēng)格混亂。
一句話(huà)概括:
“Vue3 里
ref是給單一值或需要整體替換的狀態(tài)包裝響應(yīng)式,reactive是給對(duì)象/數(shù)組做深度響應(yīng),用在多字段狀態(tài)更新更直觀,兩者語(yǔ)義不同、寫(xiě)法不同,應(yīng)根據(jù)需求選用。”
到此這篇關(guān)于玩懂Vue3的ref和reactive的文章就介紹到這了,更多相關(guān)vue ref和reactive內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Vue3中ref和reactive的區(qū)別及說(shuō)明
- Vue3中shallowRef和shallowReactive的性能優(yōu)化
- 前端vue3中的ref與reactive用法及區(qū)別總結(jié)
- Vue3 的ref和reactive的用法和區(qū)別示例解析
- vue3中的ref和reactive定義數(shù)組方式
- vue3的api解讀之ref和reactive示例詳解
- vue3+ts數(shù)組去重方及reactive/ref響應(yīng)式顯示流程分析
- vue3?中ref和reactive的區(qū)別講解
- vue3中如何使用ref和reactive定義和修改響應(yīng)式數(shù)據(jù)(最新推薦)
相關(guān)文章
vue3項(xiàng)目中代碼出現(xiàn)紅色波浪線(xiàn)的問(wèn)題及解決
這篇文章主要介紹了vue3項(xiàng)目中代碼出現(xiàn)紅色波浪線(xiàn)的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
基于vue3開(kāi)發(fā)mobile-table適用于移動(dòng)端表格
這篇文章主要給大家介紹了關(guān)于如何基于vue3開(kāi)發(fā)mobile-table適用于移動(dòng)端表格的相關(guān)資料,需要的朋友可以參考下2024-03-03
vue單頁(yè)應(yīng)用加百度統(tǒng)計(jì)代碼(親測(cè)有效)
這篇文章主要介紹了vue單頁(yè)應(yīng)用加百度統(tǒng)計(jì)代碼的解決方法,需要的朋友參考下吧2018-01-01
vue項(xiàng)目打包清除console.log的四種方法總結(jié)
大家在項(xiàng)目開(kāi)發(fā)的時(shí)候,需要看看一些后端接口返回的結(jié)果,會(huì)多次使用console.log項(xiàng)目開(kāi)發(fā)完成打包的時(shí)候,發(fā)現(xiàn)控制臺(tái)一堆的console.log,非常頭疼,下面這篇文章主要給大家介紹了關(guān)于vue項(xiàng)目打包清除console.log的四種方法,需要的朋友可以參考下2023-04-04
vue+render+jsx實(shí)現(xiàn)可編輯動(dòng)態(tài)多級(jí)表頭table的實(shí)例代碼
這篇文章主要介紹了vue+render+jsx實(shí)現(xiàn)可編輯動(dòng)態(tài)多級(jí)表頭table的實(shí)例代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的工作或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04

