為什么要使用 ref?
你可能會好奇:為什么我們需要使用帶有?.value
?的 ref,而不是普通的變量?為了解釋這一點(diǎn),我們需要簡單地討論一下 Vue 的響應(yīng)式系統(tǒng)是如何工作的。
當(dāng)你在模板中使用了一個 ref,然后改變了這個 ref 的值時(shí),Vue 會自動檢測到這個變化,并且相應(yīng)地更新 DOM。這是通過一個基于依賴追蹤的響應(yīng)式系統(tǒng)實(shí)現(xiàn)的。當(dāng)一個組件首次渲染時(shí),Vue 會追蹤在渲染過程中使用的每一個 ref。然后,當(dāng)一個 ref 被修改時(shí),它會觸發(fā)追蹤它的組件的一次重新渲染。
在標(biāo)準(zhǔn)的 JavaScript 中,檢測普通變量的訪問或修改是行不通的。然而,我們可以通過 getter 和 setter 方法來攔截對象屬性的 get 和 set 操作。
該?.value
?屬性給予了 Vue 一個機(jī)會來檢測 ref 何時(shí)被訪問或修改。在其內(nèi)部,Vue 在它的 getter 中執(zhí)行追蹤,在它的 setter 中執(zhí)行觸發(fā)。從概念上講,你可以將 ref 看作是一個像這樣的對象:
// 偽代碼,不是真正的實(shí)現(xiàn)
const myRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(newValue) {
this._value = newValue
trigger()
}
}
另一個 ref 的好處是,與普通變量不同,你可以將 ref 傳遞給函數(shù),同時(shí)保留對最新值和響應(yīng)式連接的訪問。當(dāng)將復(fù)雜的邏輯重構(gòu)為可重用的代碼時(shí),這將非常有用。
該響應(yīng)性系統(tǒng)在vue官網(wǎng)深入響應(yīng)式原理章節(jié)中有更詳細(xì)的討論。
深層響應(yīng)性
Ref 可以持有任何類型的值,包括深層嵌套的對象、數(shù)組或者 JavaScript 內(nèi)置的數(shù)據(jù)結(jié)構(gòu),比如?Map
。
Ref 會使它的值具有深層響應(yīng)性。這意味著即使改變嵌套對象或數(shù)組時(shí),變化也會被檢測到:
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// 以下都會按照期望工作
obj.value.nested.count++
obj.value.arr.push('baz')
}
非原始值將通過?reactive()?轉(zhuǎn)換為響應(yīng)式代理,該函數(shù)將在后面討論。
也可以通過shallow ref來放棄深層響應(yīng)性。對于淺層 ref,只有?.value
?的訪問會被追蹤。淺層 ref 可以用于避免對大型數(shù)據(jù)的響應(yīng)性開銷來優(yōu)化性能、或者有外部庫管理其內(nèi)部狀態(tài)的情況。
DOM 更新時(shí)機(jī)?
當(dāng)你修改了響應(yīng)式狀態(tài)時(shí),DOM 會被自動更新。但是需要注意的是,DOM 更新不是同步的。Vue 會在“next tick”更新周期中緩沖所有狀態(tài)的修改,以確保不管你進(jìn)行了多少次狀態(tài)修改,每個組件都只會被更新一次。
要等待 DOM 更新完成后再執(zhí)行額外的代碼,可以使用nextTick()全局 API:
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick()
// 現(xiàn)在 DOM 已經(jīng)更新了
}
reactive()
還有另一種聲明響應(yīng)式狀態(tài)的方式,即使用?reactive()
?API。與將內(nèi)部值包裝在特殊對象中的 ref 不同,reactive()
?將使對象本身具有響應(yīng)性:
import { reactive } from 'vue'
const state = reactive({ count: 0 })
值得注意的是,reactive()
?返回的是一個原始對象的?Proxy,它和原始對象是不相等的:
const raw = {}
const proxy = reactive(raw)
// 代理對象和原始對象不是全等的
console.log(proxy === raw) // false
只有代理對象是響應(yīng)式的,更改原始對象不會觸發(fā)更新。因此,使用 Vue 的響應(yīng)式系統(tǒng)的最佳實(shí)踐是?僅使用你聲明對象的代理版本。
為保證訪問代理的一致性,對同一個原始對象調(diào)用?reactive()
?會總是返回同樣的代理對象,而對一個已存在的代理對象調(diào)用?reactive()
?會返回其本身:
// 在同一個對象上調(diào)用 reactive() 會返回相同的代理
console.log(reactive(raw) === proxy) // true
// 在一個代理上調(diào)用 reactive() 會返回它自己
console.log(reactive(proxy) === proxy) // true
這個規(guī)則對嵌套對象也適用。依靠深層響應(yīng)性,響應(yīng)式對象內(nèi)的嵌套對象依然是代理:
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
reactive()?的局限性
-
有限的值類型:它只能用于對象類型 (對象、數(shù)組和如?
Map
、Set
?這樣的集合類型)。它不能持有如?string
、number
?或?boolean
?這樣的原始類型。 -
不能替換整個對象:由于 Vue 的響應(yīng)式跟蹤是通過屬性訪問實(shí)現(xiàn)的,因此我們必須始終保持對響應(yīng)式對象的相同引用。這意味著我們不能輕易地“替換”響應(yīng)式對象,因?yàn)檫@樣的話與第一個引用的響應(yīng)性連接將丟失:
let state = reactive({ count: 0 }) // 上面的 ({ count: 0 }) 引用將不再被追蹤 // (響應(yīng)性連接已丟失!) state = reactive({ count: 1 })
-
對解構(gòu)操作不友好:當(dāng)我們將響應(yīng)式對象的原始類型屬性解構(gòu)為本地變量時(shí),或者將該屬性傳遞給函數(shù)時(shí),我們將丟失響應(yīng)性連接:
?const state = reactive({ count: 0 }) // 當(dāng)解構(gòu)時(shí),count 已經(jīng)與 state.count 斷開連接 let { count } = state // 不會影響原始的 state count++ // 該函數(shù)接收到的是一個普通的數(shù)字 // 并且無法追蹤 state.count 的變化 // 我們必須傳入整個對象以保持響應(yīng)性 callSomeFunction(state.count)
由于這些限制,我們建議使用?ref()
?作為聲明響應(yīng)式狀態(tài)的主要 API。
為什么推薦使用ref而不是reactive呢?
-
局限性問題:?
reactive
本身存在一些局限性,可能會在開發(fā)過程中引發(fā)一些問題。這需要額外的注意力和處理,否則可能對開發(fā)造成麻煩。 -
數(shù)據(jù)類型限制:?
reactive
聲明的數(shù)據(jù)類型僅限于對象,而ref
則更加靈活,可以容納任何數(shù)據(jù)類型。這使得ref
更適合一般的響應(yīng)式狀態(tài)的聲明。 -
官方推薦:?官方文檔強(qiáng)烈建議使用
ref()
作為聲明響應(yīng)式狀態(tài)的首選。這是因?yàn)?code>ref更簡單、更直觀,同時(shí)避免了reactive
可能引發(fā)的一些問題。
總的來說:除非有特定的需求需要使用reactive
,否則在大多數(shù)情況下更推薦使用ref()
。
reactive和?ref?對比
reactive |
ref |
---|---|
? 只支持對象和數(shù)組(引用數(shù)據(jù)類型) | ? 支持基本數(shù)據(jù)類型 + 引用數(shù)據(jù)類型 |
? 在?<script> ?和?<template> ?中無差別使用 |
? 在?<script> ?和?<template> ?使用方式不同(在?<script> ?中要使用?.value ) |
? 重新分配一個新對象會丟失響應(yīng)性 | ? 重新分配一個新對象不會失去響應(yīng) |
能直接訪問屬性 | 需要使用?.value ?訪問屬性 |
? 將對象傳入函數(shù)時(shí),失去響應(yīng) | ? 傳入函數(shù)時(shí),不會失去響應(yīng) |
? 解構(gòu)時(shí)會丟失響應(yīng)性,需使用?toRefs
|
? 解構(gòu)對象時(shí)會丟失響應(yīng)性,需使用?toRefs
|
即:
-
ref
?用于將基本類型的數(shù)據(jù)和引用數(shù)據(jù)類型(對象)轉(zhuǎn)換為響應(yīng)式數(shù)據(jù),通過?.value
?訪問和修改。 -
reactive
?用于將對象轉(zhuǎn)換為響應(yīng)式數(shù)據(jù),可以直接訪問和修改屬性,適用于復(fù)雜的嵌套對象和數(shù)組。
01:?reactive?有限的值類型
reactive
?只能聲明引用數(shù)據(jù)類型(對象)
let?obj?=?reactive({
??name:?'小明',
??age:?18
})
ref
?既能聲明基本數(shù)據(jù)類型,也能聲明對象和數(shù)組
Vue 提供了?ref()
?方法,允許我們創(chuàng)建可以使用任何值類型的響應(yīng)式?ref
。
//?對象
const?state?=?ref({})
//?數(shù)組
const?state2?=?ref([])
使用?ref
,你可以靈活地聲明基本數(shù)據(jù)類型、對象或數(shù)組,而不受像?reactive
?那樣只能處理引用數(shù)據(jù)類型的限制。這為開發(fā)提供了更大的靈活性,尤其是在處理不同類型的數(shù)據(jù)時(shí)。
02:?reactive?使用不當(dāng)會失去響應(yīng)
使用?reactive
?時(shí),如果不當(dāng)使用,可能導(dǎo)致響應(yīng)性失效,帶來一些困擾。這可能讓開發(fā)者在愉快編碼的同時(shí),突然發(fā)現(xiàn)某些操作失去了響應(yīng)性,不明所以。因此,建議在不了解?reactive
?失去響應(yīng)的情況下慎用,而更推薦使用?ref
。
1. 賦值給?reactive
?一個整個對象或?reactive
?對象
賦值一個普通對象
let?state?=?reactive({?count:?0?})
//?這個賦值將導(dǎo)致?state?失去響應(yīng)
state?=?{?count:?1?}
賦值一個?reactive
?對象
<template>
??{{?state?}}
</template>????
<script?setup>
const?state?=?reactive({?count:?0?})
//?在?nextTick?異步方法中修改?state?的值
nextTick(()?=>?{
??//?并不會觸發(fā)修改?DOM?,說明失去響應(yīng)了
??state?=?reactive({?count:?11?});
});
</script>
在?nextTick
?中給?state
?賦值一個?reactive
?的響應(yīng)式對象,但是 DOM 并沒有更新。
解決方法:
-
不要直接整個對象替換,一個個屬性賦值
let?state?=?reactive({?count:?0?})
//?state?=?{?count:?1?}
state.count?=?1
-
使用?
Object.assign
let?state?=?reactive({?count:?0?})
//?state?=?{?count:?1?},state?不會失去響應(yīng)
state?=?Object.assign(state,?{?count:?1?})
-
使用?
ref
?定義對象
let?state?=?ref({?count:?0?})
state.value?=?{?count:?1?}
2. 將?reactive
?對象的屬性賦值給變量(斷開連接/深拷貝)
這種操作類似于深拷貝,不再共享同一內(nèi)存地址,而是只是字面量的賦值,對該變量的賦值不會影響原來對象的屬性值。
let?state?=?reactive({?count:?0?})
//?賦值給?n,n?和?state.count?不再共享響應(yīng)性連接
let?n?=?state.count
//?不影響原始的?state
n++
console.log(state.count)?//?0
解決方案:
-
避免將?
reactive
?對象的屬性賦值給變量。
3. 直接?reactive
?對象解構(gòu)時(shí)
直接解構(gòu)會失去響應(yīng)。
let?state?=?reactive({?count:?0?})
//?普通解構(gòu),count?和?state.count?失去了響應(yīng)性連接
let?{?count?}?=?state
count++?//?state.count?值依舊是?0
解決方案:
使用?toRefs
?解構(gòu),解構(gòu)后的屬性是?ref
?的響應(yīng)式變量。
const?state?=?reactive({?count:?0?})
//?使用?toRefs?解構(gòu),后的屬性為?ref?的響應(yīng)式變量
let?{?count?}?=?toRefs(state)
count.value++?//?state.count?值改變?yōu)?1
建議:ref?一把梭
推薦使用?ref
,總結(jié)原因如下:
-
reactive
?有限的值類型:只能聲明引用數(shù)據(jù)類型(對象/數(shù)組)。 -
reactive
?在一些情況下會失去響應(yīng),這可能導(dǎo)致數(shù)據(jù)回顯失去響應(yīng)(數(shù)據(jù)改了,DOM 沒更新)。
???<template>
?????{{?state.a?}}
?????{{?state.b?}}
?????{{?state.c?}}
???</template>
???
???<script>
???let?state?=?reactive({?a:?1,?b:?2,?c:?3?})
???onMounted(()?=>?{
?????//?通過?AJAX?請求獲取的數(shù)據(jù),回顯到?reactive,如果處理不好將導(dǎo)致變量失去響應(yīng)
?????//?回顯失敗,給響應(yīng)式數(shù)據(jù)賦值一個普通對象
?????state?=?{?a:?11,?b:?22,?c:?333?}
?????//?回顯成功,一個個屬性賦值
?????state.a?=?11
?????state.b?=?22
?????state.c?=?33
???})
???</script>
上面這個例子如果是使用?ref
?進(jìn)行聲明,直接賦值即可,不需要將屬性拆分一個個賦值。
使用?ref
?替代?reactive
:
???<template>
?????{{?state.a?}}
?????{{?state.b?}}
?????{{?state.c?}}
???</template>
???
???<script>
???let?state?=?ref({?a:?1,?b:?2,?c:?3?})
???onMounted(()?=>?{
?????//?回顯成功
?????state.value?=?{?a:?11,?b:?22,?c:?333?}
???})
???</script>
-
-
給響應(yīng)式對象的字面量賦一整個普通對象或?
reactive
?對象將導(dǎo)致?reactive
?聲明的響應(yīng)式數(shù)據(jù)失去響應(yīng)。
-
-
ref
?適用范圍更廣,可聲明基本數(shù)據(jù)類型和引用數(shù)據(jù)類型。
雖然使用?ref
?聲明的變量在讀取和修改時(shí)都需要加?.value
?小尾巴,但正因?yàn)橛羞@個小尾巴,我們在 review 代碼的時(shí)候就很清楚知道這是一個?ref
?聲明的響應(yīng)式數(shù)據(jù)。
ref
?的?.value
?好麻煩!
ref
?聲明的響應(yīng)式變量攜帶迷人的?.value
?小尾巴,讓我們一眼就能確定它是一個響應(yīng)式變量。雖然使用?ref
?聲明的變量在讀取和修改時(shí)都需要加?.value
?小尾巴,但是正因?yàn)橛羞@個小尾巴,我們在 review 代碼的時(shí)候就很清楚知道這是一個?ref
?聲明的響應(yīng)式數(shù)據(jù)。
Volar 插件能自動補(bǔ)全?.value
推薦?ref
?一把梭,但是?ref
?又得到處?.value
,那就交給插件來完成吧!
-
Volar
?自動補(bǔ)全?.value
(不是默認(rèn)開啟,需要手動開啟)
reactive
?重新賦值丟失響應(yīng)是因?yàn)橐玫刂纷兞?,?proxy
?代理的對象已經(jīng)不是原來的那個,所以丟失響應(yīng)了。其實(shí)?ref
?也是一樣的,當(dāng)把?.value
?那一層替換成另外一個有著?.value
?的對象也會丟失響應(yīng)。ref
?定義的屬性等價(jià)于?reactive({ value: xxx })
。另外,說使用?
Object.assign
?為什么可以更新模板:
Object.assign
?解釋是這樣的:如果目標(biāo)對象與源對象具有相同的鍵(屬性名),則目標(biāo)對象中的屬性將被源對象中的屬性覆蓋,后面的源對象的屬性將類似地覆蓋前面的源對象的同名屬性。那個解決方法里不用重新賦值,直接?
Object.assign(state, { count: 1 })
?即可,所以只要?proxy
?代理的引用地址沒變,就會一直存在響應(yīng)性文章來源:http://www.zghlxwxcb.cn/news/detail-830278.html
?文章來源地址http://www.zghlxwxcb.cn/news/detail-830278.html
到了這里,關(guān)于Vue3 中應(yīng)該使用 Ref 還是 Reactive?的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!