EP4 - 反應性基礎

更新於 2024/09/12閱讀時間約 1 分鐘
Reactivity Fundamentals 這啥東西?越看文件頭越大www
每次學新東西都是先吞下去~隔一陣子再來看就會懂了
或者是要隔好幾年....www

在學習和使用 Vue.js 時,官方文件可切換API 風格。這樣的設計有助於開發者靈活地學習和應用不同的 API 風格,根據具體情況選擇最合適的方法來構建應用。
目前先專心研究Composition API,之後在來切換Option API來看差異~

聲明響應式狀態 - Declaring Reactive State

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>

在 Playground 中試試

<script setup> 中聲明的頂層導入、變量和函數可以自動在同一組件的模板中使用。可以將模板視為在相同作用域內聲明的 JavaScript 函數 - 它自然可以訪問與其一起聲明的所有內容。

提示:在文件其餘部分,將主要使用 SFC + <script setup> 語法來編寫 Composition API 的代碼示例,因為這是 Vue 開發者最常用的用法。如果您不使用 SFC,仍然可以使用 setup() 選項來使用 Composition API。

為什麼使用 Refs?

您可能會想知道為什麼我們需要具有 .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()
}
}

深度響應性 - Deep Reactivity

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 Update Timing

當您修改響應狀態時,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 可以選擇不進行深度響應。

響應代理 vs. 原始對象 (Reactive Proxy vs. Original)

需要注意的是,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 有一些限制:

  1. 受限的值類型:它僅適用於對象類型(對象、數組和集合類型,如 Map 和 Set)。不能包含基本類型,如字符串、數字或布爾值。
  2. 不能替換整個對象:由於 Vue 的響應性追蹤是基於屬性訪問的,我們必須始終保持對響應對象的相同引用。這意味著我們不能輕易“替換”一個響應對象,因為對第一個引用的響應連接將丟失:
    let state = reactive({ count: 0 })

    // 上面的引用({ count: 0 })不再被追蹤
    // (響應連接丟失!)
    state = reactive({ count: 1 })
  3. 不適合解構:當我們將響應對象的基本類型屬性解構為本地變量,或將該屬性傳遞給函數時,我們會丟失響應連接:
const state = reactive({ count: 0 })

// 當解構時,count 與 state.count 斷開連接
let { count } = state
// 不影響原始狀態
count++

// 函數接收一個普通數字,無法追蹤 state.count 的變化
// 我們必須傳入整個對象以保留響應性
callSomeFunction(state.count)

由於這些限制,我們建議使用 ref() 作為聲明響應狀態的主要 API。ref 更靈活且更易於處理,特別是當涉及到基本類型和對象解構時。

refreactive 的比較

  1. 簡單值 vs. 複雜結構
    • ref 更適合用來處理簡單的基本類型值(如數字、字符串、布爾值)。
    • reactive 更適合用來處理包含多個屬性的複雜對象,因為它會自動對對象內部的所有屬性進行深度響應。
  2. 使用方式
    • ref 需要通過 .value 屬性來訪問和修改其包含的值,這在處理複雜對象時可能會變得繁瑣。
    • reactive 可以`直接通過屬性訪問和修改其內容,這使得代碼更簡潔和直觀。
  3. 可讀性和代碼風格
    • 當需要處理多個響應式屬性時,使用 reactive 可使代碼更具結構性和可讀性。
    • 對於單一的響應式屬性,使用 ref 更直觀。

為什麼需要 reactive

  1. 深度響應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 }) // 自動響應
  2. 更直觀的語法: 當處理多個屬性時,reactive 提供了更直觀的語法,無需頻繁使用 .value
import { reactive } from 'vue'

const state = reactive({
count: 0,
message: 'Hello'
})

state.count++
state.message = 'World'
  1. 一致性和風格偏好: 對於某些開發者來說,使用 reactive 使得狀態管理更具一致性,尤其在大型應用中,使用 reactive 可以提供更清晰的結構和狀態管理方式。

Ref解包細節 - Additional Ref Unwrapping Details

作為響應式對象屬性

當 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 的解包僅在嵌套在深度響應式對象內時發生。當作為淺層響應式對象的屬性訪問時不適用。

在數組和集合中的注意事項 - Caveat in Arrays and Collections

與響應式對象不同,當 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)

模板中的解包注意事項 - Caveat when Unwrapping in Templates

在模板中,ref 的解包僅在它是模板渲染上下文中的頂級屬性時適用。

在下面的例子中,countobject 是頂級屬性,但 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
avatar-img
2會員
71內容數
分享生活趣事~
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
卡關的人生 的其他內容
本文介紹了 Vue 的模板語法及虛擬 DOM 概念。學習如何使用 Vue 進行聲明式渲染,以及模板如何轉換為高效的 JavaScript 代碼。探討各種綁定方法、指令及安全注意事項,幫助開發者構建高效動態用戶界面並提升應用性能。文章深入淺出,適合想進一步瞭解 Vue 的開發者。
這篇文章將介紹如何創建和管理 Vue 應用程式的基本概念,包括根組件的概念、如何掛載應用程式及應用程式的配置選項。將探討多個應用實例的使用情境,並解釋如何在同一頁面上共存不同的 Vue 應用程式。透過範例和深入的說明,讀者將能夠理解 Vue 應用程式的組織結構和應用範圍內的資產管理。
隨著AI工具的快速發展,學習新的程式語言變得更為簡便。這篇文章帶你瞭解如何使用Vue.js來建立單頁應用(SPA),並提供環境建置的詳細步驟,包括Node.js的安裝、使用NPM管理套件以及如何透過CDN簡化Vue的應用設置。
Vue是一個前端框架,發音如View,幫助你有效率地開發任何複雜性的使用者介面 由前Google工程師 尤雨溪(Evan You) 邊工作邊開發出來的開源框架,好厲害! 還在耍廢嗎?一起來學習吧~ 期待有朝一日能應徵上遠端工作 www ~ 下面的範例是什麼? // main.js 或 main.
本文介紹了 Vue 的模板語法及虛擬 DOM 概念。學習如何使用 Vue 進行聲明式渲染,以及模板如何轉換為高效的 JavaScript 代碼。探討各種綁定方法、指令及安全注意事項,幫助開發者構建高效動態用戶界面並提升應用性能。文章深入淺出,適合想進一步瞭解 Vue 的開發者。
這篇文章將介紹如何創建和管理 Vue 應用程式的基本概念,包括根組件的概念、如何掛載應用程式及應用程式的配置選項。將探討多個應用實例的使用情境,並解釋如何在同一頁面上共存不同的 Vue 應用程式。透過範例和深入的說明,讀者將能夠理解 Vue 應用程式的組織結構和應用範圍內的資產管理。
隨著AI工具的快速發展,學習新的程式語言變得更為簡便。這篇文章帶你瞭解如何使用Vue.js來建立單頁應用(SPA),並提供環境建置的詳細步驟,包括Node.js的安裝、使用NPM管理套件以及如何透過CDN簡化Vue的應用設置。
Vue是一個前端框架,發音如View,幫助你有效率地開發任何複雜性的使用者介面 由前Google工程師 尤雨溪(Evan You) 邊工作邊開發出來的開源框架,好厲害! 還在耍廢嗎?一起來學習吧~ 期待有朝一日能應徵上遠端工作 www ~ 下面的範例是什麼? // main.js 或 main.
你可能也想看
Google News 追蹤
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
Quasar Dialog 的 Invoking custom component 很好用,但是有些困擾的地方,一起來看看有甚麼辦法吧。
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
先從react開始: 其實市面上有許多前端框架像是react,angular,vue... 至於為什麼我會選擇react…
Thumbnail
本文介紹了在網站開發中如何運用狀態機的原則和設計方法。通過具體案例分析,以及狀態和數據的區分,詳細介紹了狀態機的設計原則和應用。讀者可以通過本文瞭解如何將狀態機應用於實際的網站開發中。
Thumbnail
到底要用 ref 還是 reactive 是一個很常見的問題,不過現在官方文檔推薦使用 ref 就行,所以也不是甚麼大問題就是了。( •̀ ω •́ )✧ 所以 reactive 真的沒用用途了嗎?這篇文章來記錄一下 reactive 的用法。
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
我們在實作中,難免會遇到在不同組件中,卻有需求相同的資料格式,因此 mixins 可以達到我們的需求,除了 data 以外也包含了 methods 可以共用,舉例來說,學生資料可能會在,班級跟社團內被使用,當我們要撰寫元件時,就可以省略多餘的 data 定義。
Thumbnail
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
Quasar Dialog 的 Invoking custom component 很好用,但是有些困擾的地方,一起來看看有甚麼辦法吧。
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
先從react開始: 其實市面上有許多前端框架像是react,angular,vue... 至於為什麼我會選擇react…
Thumbnail
本文介紹了在網站開發中如何運用狀態機的原則和設計方法。通過具體案例分析,以及狀態和數據的區分,詳細介紹了狀態機的設計原則和應用。讀者可以通過本文瞭解如何將狀態機應用於實際的網站開發中。
Thumbnail
到底要用 ref 還是 reactive 是一個很常見的問題,不過現在官方文檔推薦使用 ref 就行,所以也不是甚麼大問題就是了。( •̀ ω •́ )✧ 所以 reactive 真的沒用用途了嗎?這篇文章來記錄一下 reactive 的用法。
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
我們在實作中,難免會遇到在不同組件中,卻有需求相同的資料格式,因此 mixins 可以達到我們的需求,除了 data 以外也包含了 methods 可以共用,舉例來說,學生資料可能會在,班級跟社團內被使用,當我們要撰寫元件時,就可以省略多餘的 data 定義。
Thumbnail
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。