Reactivity Fundamentals 這啥東西?越看文件頭越大www
每次學新東西都是先吞下去~隔一陣子再來看就會懂了
或者是要隔好幾年....www
在學習和使用 Vue.js 時,官方文件可切換API 風格。這樣的設計有助於開發者靈活地學習和應用不同的 API 風格,根據具體情況選擇最合適的方法來構建應用。
目前先專心研究Composition API,之後在來切換Option API來看差異~
ref()
在 Composition API 中,聲明響應式狀態的推薦方式是使用 ref()
函數:
import { ref } from 'vue'
const count = ref(0)
ref()
接收一個參數並將其包裝在一個帶有 .value
屬性的 ref
對象中:
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
參見: Typing Refs
要在組件的模板中訪問 ref
,需要在組件的 setup()
函數中聲明並返回它們:
import { ref } from 'vue'
export default {
// `setup` 是一個專門用於 Composition API 的特殊鉤子。
setup() {
const count = ref(0)
// 將 ref 暴露給模板
return {
count
}
}
}
模板:
<div>{{ count }}</div>
注意,在模板中使用 ref
時不需要附加 .value
。為了方便起見,在模板中使用時,ref
會自動解包(有一些注意事項)。
你也可以在事件處理器中直接修改 ref
:
模板:
<button @click="count++">
{{ count }}
</button>
對於更複雜的邏輯,我們可以在同一範圍內聲明修改 ref
的函數,並將它們作為方法與狀態一起暴露出來:
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
// 在 JavaScript 中需要使用 .value
count.value++
}
// 別忘了也要暴露這個函數。
return {
count,
increment
}
}
}
暴露的方法可以作為事件處理器使用:
模板:
<button @click="increment">
{{ count }}
</button>
這裡有一個在 Codepen 上的實例,沒有使用任何構建工具。
<script setup>
使用 setup()
手動暴露狀態和方法可能會很繁瑣。幸運的是,當使用單文件組件(SFC)時,可以避免這種情況。我們可以使用 <script setup>
簡化:
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }}
</button>
</template>
在 <script setup>
中聲明的頂層導入、變量和函數可以自動在同一組件的模板中使用。可以將模板視為在相同作用域內聲明的 JavaScript 函數 - 它自然可以訪問與其一起聲明的所有內容。
提示:在文件其餘部分,將主要使用 SFC +<script setup>
語法來編寫 Composition API 的代碼示例,因為這是 Vue 開發者最常用的用法。如果您不使用 SFC,仍然可以使用setup()
選項來使用 Composition API。
您可能會想知道為什麼我們需要具有 .value
的 refs,而不是普通變量。要解釋這一點,我們需要簡要討論 Vue 的響應系統是如何工作的。
當您在模板中使用 ref,並且稍後更改 ref 的值時,Vue 會自動檢測到變化並相應地更新 DOM。這是通過依賴關係追踪的響應系統實現
的。當組件第一次渲染時,Vue 會追踪渲染過程中使用的每個 ref。稍後,當 ref 被修改時,它會觸發跟踪它的組件重新渲染。
在標準 JavaScript 中,無法檢測普通變量的訪問或修改。然而,我們可以通過 getter 和 setter 方法攔截對象屬性的 get 和 set 操作。
.value
屬性為 Vue 提供了檢測 ref 被訪問或修改的機會。在內部,Vue 在 getter 中執行追踪,在 setter 中執行觸發
。概念上,可以將 ref 視為如下所示的對象:
// 假設代碼,非實際實現
const myRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(newValue) {
this._value = newValue
trigger()
}
}
Refs 可以包含任何值類型,包括深層嵌套的對象、數組或 JavaScript 內建的數據結構如 Map。一個 ref 將使其值變得深度響應。這意味著即使您修改嵌套對象或數組,變化也會被檢測到:
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()
變成響應代理,如下所述。
還可以通過淺層 refs 選擇不進行深度響應。對於淺層 refs,只有 .value
訪問會被追踪以實現響應。淺層 refs 可用於通過避免大對象的觀察成本來優化性能,或者在內部狀態由外部庫管理的情況下使用。
深入閱讀:
當您修改響應狀態時,DOM 會自動更新。然而,應注意,DOM 更新不是同步應用的。相反,Vue 將它們緩衝到更新循環中的“下一個刻度”,以確保每個組件無論進行了多少狀態更改都只更新一次。
要在狀態更改後等待 DOM 更新完成,可以使用 nextTick()
全局 API:
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick()
// 現在 DOM 已更新
}
reactive()
另一種聲明響應狀態的方法是使用 reactive()
API。與 ref
將內部值包裝在特殊對象中不同,reactive()
使對象本身變得響應:
import { reactive } from 'vue'
const state = reactive({ count: 0 })
在模板中的使用方式:
<button @click="state.count++">
{{ state.count }}
</button>
響應對象是 JavaScript 的代理(Proxy),其行為與普通對象相同。不同之處在於,Vue 能夠攔截響應對象的所有屬性的訪問和修改,以進行響應性追蹤和觸發。
reactive()
會將對象進行深度轉換:嵌套對象在訪問時也會被 reactive()
包裝。當 ref
的值是一個對象時,它內部也會調用 reactive()
。與淺層 refs 類似,也有 shallowReactive()
API 可以選擇不進行深度響應。
需要注意的是,reactive()
返回的值是原始對象的代理,這與原始對象不相等:
const raw = {}
const proxy = reactive(raw)
// 代理與原始對象不相等
console.log(proxy === raw) // false
只有代理是響應的 - 修改原始對象不會觸發更新。因此,在使用 Vue 的響應系統時,最佳實踐是僅使用狀態的代理版本。
為了確保一致地訪問代理,對同一對象調用 reactive()
總是返回相同的代理,對已存在的代理調用 reactive()
也返回相同的代理:
// 對同一對象調用 reactive() 返回相同的代理
console.log(reactive(raw) === proxy) // true
// 對代理調用 reactive() 返回自身
console.log(reactive(proxy) === proxy) // true
這一規則也適用於嵌套對象。由於深度響應,響應對象內的嵌套對象也是代理:
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
reactive()
的限制reactive()
API 有一些限制:
let state = reactive({ count: 0 })
// 上面的引用({ count: 0 })不再被追蹤
// (響應連接丟失!)
state = reactive({ count: 1 })
const state = reactive({ count: 0 })
// 當解構時,count 與 state.count 斷開連接
let { count } = state
// 不影響原始狀態
count++
// 函數接收一個普通數字,無法追蹤 state.count 的變化
// 我們必須傳入整個對象以保留響應性
callSomeFunction(state.count)
由於這些限制,我們建議使用 ref()
作為聲明響應狀態的主要 API。ref
更靈活且更易於處理,特別是當涉及到基本類型和對象解構時。
ref
和 reactive
的比較ref
更適合用來處理簡單的基本類型值(如數字、字符串、布爾值)。reactive
更適合用來處理包含多個屬性的複雜對象,因為它會自動對對象內部的所有屬性進行深度響應。ref
需要通過 .value
屬性來訪問和修改其包含的值,這在處理複雜對象時可能會變得繁瑣。reactive
可以`直接通過屬性訪問和修改其內容,這使得代碼更簡潔和直觀。reactive
可使代碼更具結構性和可讀性。ref
更直觀。reactive
?reactive
自動對其內部的所有屬性進行深度響應,這對於處理嵌套結構的對象非常方便。例如:import { reactive } from 'vue'
const state = reactive({
user: {
name: 'John',
age: 30
},
tasks: [
{ title: 'Task 1', completed: false },
{ title: 'Task 2', completed: true }
]
})
state.user.name = 'Jane' // 自動響應
state.tasks.push({ title: 'Task 3', completed: false }) // 自動響應
reactive
提供了更直觀的語法,無需頻繁使用 .value
:import { reactive } from 'vue'
const state = reactive({
count: 0,
message: 'Hello'
})
state.count++
state.message = 'World'
reactive
使得狀態管理更具一致性,尤其在大型應用中,使用 reactive
可以提供更清晰的結構和狀態管理方式。當 ref 被作為響應式對象的屬性訪問或修改時,會自動解包,它的行為像普通屬性
:
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
如果一個新的 ref 被分配給一個已經鏈接到現有 ref 的屬性,它會替換舊的 ref:
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// 原來的 ref 現在與 state.count 斷開連接
console.log(count.value) // 1
ref 的解包僅在嵌套在深度響應式對象內時發生。當作為淺層響應式對象的屬性訪問時不適用。
與響應式對象不同,當 ref 被作為響應式數組或原生集合類型(如 Map)的元素訪問時,不會進行解包:
const books = reactive([ref('Vue 3 Guide')])
// 需要 .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// 需要 .value
console.log(map.get('count').value)
在模板中,ref 的解包僅在它是模板渲染上下文中的頂級屬性時適用。
在下面的例子中,count
和 object
是頂級屬性,但 object.id
不是:
const count = ref(0)
const object = { id: ref(1) }
因此,這個表達式正常工作:
{{ count + 1 }}
而這個則不行:
{{ object.id + 1 }}
渲染的結果會是 [object Object]1
,因為 object.id
在評估表達式時未被解包並且仍然是 ref 對象。要修復這個問題,我們可以將 id
解構到頂級屬性:
const { id } = object
模板中:
{{ id + 1 }}
現在渲染結果將是 2
。
需要注意的是,ref 在作為文本插值(即 {{ }}
標籤)的最終評估值時會被解包,因此以下內容將渲染 1
:
{{ object.id }}
這只是一個文本插值的便利特性,等同於 {{ object.id.value }}
。
這段看不懂耶!一查原來是JS的語法 www ~
補充說明:const { id } = object
是 JavaScript 中的解構賦值語法,它從object
對象中提取id
屬性並將其分配給變量id
。
寫文件的人為什麼都寫這麼難懂~原來是我們太嫩 www