EP4 - 反應性基礎

閱讀時間約 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
2會員
67內容數
分享生活趣事~
留言0
查看全部
發表第一個留言支持創作者!
卡關的人生 的其他內容
本文介紹了 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
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
美國總統大選只剩下三天, 我們觀察一整週民調與金融市場的變化(包含賭局), 到本週五下午3:00前為止, 誰是美國總統幾乎大概可以猜到60-70%的機率, 本篇文章就是以大選結局為主軸來討論近期甚至到未來四年美股可能的改變
Thumbnail
Faker昨天真的太扯了,中國主播王多多點評的話更是精妙,分享給各位 王多多的點評 「Faker是我們的處境,他是LPL永遠繞不開的一個人和話題,所以我們特別渴望在決賽跟他相遇,去直面我們的處境。 我們曾經稱他為最高的山,最長的河,以為山海就是盡頭,可是Faker用他28歲的年齡...
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
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
美國總統大選只剩下三天, 我們觀察一整週民調與金融市場的變化(包含賭局), 到本週五下午3:00前為止, 誰是美國總統幾乎大概可以猜到60-70%的機率, 本篇文章就是以大選結局為主軸來討論近期甚至到未來四年美股可能的改變
Thumbnail
Faker昨天真的太扯了,中國主播王多多點評的話更是精妙,分享給各位 王多多的點評 「Faker是我們的處境,他是LPL永遠繞不開的一個人和話題,所以我們特別渴望在決賽跟他相遇,去直面我們的處境。 我們曾經稱他為最高的山,最長的河,以為山海就是盡頭,可是Faker用他28歲的年齡...
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)。