《Vue3實戰教程》13:Vue3偵聽器

如果您有疑問,請觀看視頻教程《Vue3實戰教程》

偵聽器?

基本示例?

計算屬性允許我們聲明性地計算衍生值。然而在有些情況下,我們需要在狀態變化時執行一些“副作用”:例如更改 DOM,或是根據異步操作的結果去修改另一處的狀態。

在組合式 API 中,我們可以使用?watch?函數在每次響應式狀態發生變化時觸發回調函數:

vue

<script setup>
import { ref, watch } from 'vue'const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
const loading = ref(false)// 可以直接偵聽一個 ref
watch(question, async (newQuestion, oldQuestion) => {if (newQuestion.includes('?')) {loading.value = trueanswer.value = 'Thinking...'try {const res = await fetch('https://yesno.wtf/api')answer.value = (await res.json()).answer} catch (error) {answer.value = 'Error! Could not reach the API. ' + error} finally {loading.value = false}}
})
</script><template><p>Ask a yes/no question:<input v-model="question" :disabled="loading" /></p><p>{{ answer }}</p>
</template>

在演練場中嘗試一下

偵聽數據源類型?

watch?的第一個參數可以是不同形式的“數據源”:它可以是一個 ref (包括計算屬性)、一個響應式對象、一個?getter 函數、或多個數據源組成的數組:

js

const x = ref(0)
const y = ref(0)// 單個 ref
watch(x, (newX) => {console.log(`x is ${newX}`)
})// getter 函數
watch(() => x.value + y.value,(sum) => {console.log(`sum of x + y is: ${sum}`)}
)// 多個來源組成的數組
watch([x, () => y.value], ([newX, newY]) => {console.log(`x is ${newX} and y is ${newY}`)
})

注意,你不能直接偵聽響應式對象的屬性值,例如:

js

const obj = reactive({ count: 0 })// 錯誤,因為 watch() 得到的參數是一個 number
watch(obj.count, (count) => {console.log(`Count is: ${count}`)
})

這里需要用一個返回該屬性的 getter 函數:

js

// 提供一個 getter 函數
watch(() => obj.count,(count) => {console.log(`Count is: ${count}`)}
)

深層偵聽器?

直接給?watch()?傳入一個響應式對象,會隱式地創建一個深層偵聽器——該回調函數在所有嵌套的變更時都會被觸發:

js

const obj = reactive({ count: 0 })watch(obj, (newValue, oldValue) => {// 在嵌套的屬性變更時觸發// 注意:`newValue` 此處和 `oldValue` 是相等的// 因為它們是同一個對象!
})obj.count++

相比之下,一個返回響應式對象的 getter 函數,只有在返回不同的對象時,才會觸發回調:

js

watch(() => state.someObject,() => {// 僅當 state.someObject 被替換時觸發}
)

你也可以給上面這個例子顯式地加上?deep?選項,強制轉成深層偵聽器:

js

watch(() => state.someObject,(newValue, oldValue) => {// 注意:`newValue` 此處和 `oldValue` 是相等的// *除非* state.someObject 被整個替換了},{ deep: true }
)

在 Vue 3.5+ 中,deep?選項還可以是一個數字,表示最大遍歷深度——即 Vue 應該遍歷對象嵌套屬性的級數。

謹慎使用

深度偵聽需要遍歷被偵聽對象中的所有嵌套的屬性,當用于大型數據結構時,開銷很大。因此請只在必要時才使用它,并且要留意性能。

即時回調的偵聽器?

watch?默認是懶執行的:僅當數據源變化時,才會執行回調。但在某些場景中,我們希望在創建偵聽器時,立即執行一遍回調。舉例來說,我們想請求一些初始數據,然后在相關狀態更改時重新請求數據。

我們可以通過傳入?immediate: true?選項來強制偵聽器的回調立即執行:

js

watch(source,(newValue, oldValue) => {// 立即執行,且當 `source` 改變時再次執行},{ immediate: true }
)

一次性偵聽器?

  • 僅支持 3.4 及以上版本

每當被偵聽源發生變化時,偵聽器的回調就會執行。如果希望回調只在源變化時觸發一次,請使用?once: true?選項。

js

watch(source,(newValue, oldValue) => {// 當 `source` 變化時,僅觸發一次},{ once: true }
)

watchEffect()?

偵聽器的回調使用與源完全相同的響應式狀態是很常見的。例如下面的代碼,在每當?todoId?的引用發生變化時使用偵聽器來加載一個遠程資源:

js

const todoId = ref(1)
const data = ref(null)watch(todoId,async () => {const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)data.value = await response.json()},{ immediate: true }
)

特別是注意偵聽器是如何兩次使用?todoId?的,一次是作為源,另一次是在回調中。

我們可以用?watchEffect?函數?來簡化上面的代碼。watchEffect()?允許我們自動跟蹤回調的響應式依賴。上面的偵聽器可以重寫為:

js

watchEffect(async () => {const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)data.value = await response.json()
})

這個例子中,回調會立即執行,不需要指定?immediate: true。在執行期間,它會自動追蹤?todoId.value?作為依賴(和計算屬性類似)。每當?todoId.value?變化時,回調會再次執行。有了?watchEffect(),我們不再需要明確傳遞?todoId?作為源值。

你可以參考一下這個例子的?watchEffect?和響應式的數據請求的操作。

對于這種只有一個依賴項的例子來說,watchEffect()?的好處相對較小。但是對于有多個依賴項的偵聽器來說,使用?watchEffect()?可以消除手動維護依賴列表的負擔。此外,如果你需要偵聽一個嵌套數據結構中的幾個屬性,watchEffect()?可能會比深度偵聽器更有效,因為它將只跟蹤回調中被使用到的屬性,而不是遞歸地跟蹤所有的屬性。

TIP

watchEffect?僅會在其同步執行期間,才追蹤依賴。在使用異步回調時,只有在第一個?await?正常工作前訪問到的屬性才會被追蹤。

watch?vs.?watchEffect?

watch?和?watchEffect?都能響應式地執行有副作用的回調。它們之間的主要區別是追蹤響應式依賴的方式:

  • watch?只追蹤明確偵聽的數據源。它不會追蹤任何在回調中訪問到的東西。另外,僅在數據源確實改變時才會觸發回調。watch?會避免在發生副作用時追蹤依賴,因此,我們能更加精確地控制回調函數的觸發時機。

  • watchEffect,則會在副作用發生期間追蹤依賴。它會在同步執行過程中,自動追蹤所有能訪問到的響應式屬性。這更方便,而且代碼往往更簡潔,但有時其響應性依賴關系會不那么明確。

副作用清理?

有時我們可能會在偵聽器中執行副作用,例如異步請求:

js

watch(id, (newId) => {fetch(`/api/${newId}`).then(() => {// 回調邏輯})
})

但是如果在請求完成之前?id?發生了變化怎么辦?當上一個請求完成時,它仍會使用已經過時的 ID 值觸發回調。理想情況下,我們希望能夠在?id?變為新值時取消過時的請求。

我們可以使用?onWatcherCleanup()??API 來注冊一個清理函數,當偵聽器失效并準備重新運行時會被調用:

js

import { watch, onWatcherCleanup } from 'vue'watch(id, (newId) => {const controller = new AbortController()fetch(`/api/${newId}`, { signal: controller.signal }).then(() => {// 回調邏輯})onWatcherCleanup(() => {// 終止過期請求controller.abort()})
})

請注意,onWatcherCleanup?僅在 Vue 3.5+ 中支持,并且必須在?watchEffect?效果函數或?watch?回調函數的同步執行期間調用:你不能在異步函數的?await?語句之后調用它。

作為替代,onCleanup?函數還作為第三個參數傳遞給偵聽器回調,以及?watchEffect?作用函數的第一個參數:

js

watch(id, (newId, oldId, onCleanup) => {// ...onCleanup(() => {// 清理邏輯})
})watchEffect((onCleanup) => {// ...onCleanup(() => {// 清理邏輯})
})

這在 3.5 之前的版本有效。此外,通過函數參數傳遞的?onCleanup?與偵聽器實例相綁定,因此不受?onWatcherCleanup?的同步限制。

回調的觸發時機?

當你更改了響應式狀態,它可能會同時觸發 Vue 組件更新和偵聽器回調。

類似于組件更新,用戶創建的偵聽器回調函數也會被批量處理以避免重復調用。例如,如果我們同步將一千個項目推入被偵聽的數組中,我們可能不希望偵聽器觸發一千次。

默認情況下,偵聽器回調會在父組件更新 (如有)?之后、所屬組件的 DOM 更新之前被調用。這意味著如果你嘗試在偵聽器回調中訪問所屬組件的 DOM,那么 DOM 將處于更新前的狀態。

Post Watchers?

如果想在偵聽器回調中能訪問被 Vue 更新之后的所屬組件的 DOM,你需要指明?flush: 'post'?選項:

js

watch(source, callback, {flush: 'post'
})watchEffect(callback, {flush: 'post'
})

后置刷新的?watchEffect()?有個更方便的別名?watchPostEffect()

js

import { watchPostEffect } from 'vue'watchPostEffect(() => {/* 在 Vue 更新后執行 */
})

同步偵聽器?

你還可以創建一個同步觸發的偵聽器,它會在 Vue 進行任何更新之前觸發:

js

watch(source, callback, {flush: 'sync'
})watchEffect(callback, {flush: 'sync'
})

同步觸發的?watchEffect()?有個更方便的別名?watchSyncEffect()

js

import { watchSyncEffect } from 'vue'watchSyncEffect(() => {/* 在響應式數據變化時同步執行 */
})

謹慎使用

同步偵聽器不會進行批處理,每當檢測到響應式數據發生變化時就會觸發。可以使用它來監視簡單的布爾值,但應避免在可能多次同步修改的數據源 (如數組) 上使用。

停止偵聽器?

在?setup()?或?<script setup>?中用同步語句創建的偵聽器,會自動綁定到宿主組件實例上,并且會在宿主組件卸載時自動停止。因此,在大多數情況下,你無需關心怎么停止一個偵聽器。

一個關鍵點是,偵聽器必須用同步語句創建:如果用異步回調創建一個偵聽器,那么它不會綁定到當前組件上,你必須手動停止它,以防內存泄漏。如下方這個例子:

vue

<script setup>
import { watchEffect } from 'vue'// 它會自動停止
watchEffect(() => {})// ...這個則不會!
setTimeout(() => {watchEffect(() => {})
}, 100)
</script>

要手動停止一個偵聽器,請調用?watch?或?watchEffect?返回的函數:

js

const unwatch = watchEffect(() => {})// ...當該偵聽器不再需要時
unwatch()

注意,需要異步創建偵聽器的情況很少,請盡可能選擇同步創建。如果需要等待一些異步數據,你可以使用條件式的偵聽邏輯:

js

// 需要異步請求得到的數據
const data = ref(null)watchEffect(() => {if (data.value) {// 數據加載后執行某些操作...}
})

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/63484.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/63484.shtml
英文地址,請注明出處:http://en.pswp.cn/web/63484.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

uboot 打開log 的 方法

uboot 版本 commit f919c3a889f0ec7d63a48b5d0ed064386b0980bd (HEAD -> v2024.10, tag: v2024.10) Author: Tom Rini <trinikonsulko.com> Date: Mon Oct 7 08:54:35 2024 -0600 Prepare v2024.10 Signed-off-by: Tom Rini <trinikonsulko.com> 開啟的選項…

VSCode 搭建Python編程環境 2024新版圖文安裝教程(Python環境搭建+VSCode安裝+運行測試+背景圖設置)

名人說&#xff1a;一點浩然氣&#xff0c;千里快哉風。—— 蘇軾《水調歌頭》 創作者&#xff1a;Code_流蘇(CSDN) 目錄 一、Python環境安裝二、VScode下載及安裝三、VSCode配置Python環境四、運行測試五、背景圖設置 很高興你打開了這篇博客&#xff0c;更多詳細的安裝教程&…

Unity常用面試問題

GC針對的誰 new對象的時候&#xff0c;產生新對象 GC是發生在什么時候 主動調collect接口以及內存分配不足的時候 如何避免gc 別new對象 GC的過程&#xff0c;為什么耗時 每一次GC會經歷以下過程&#xff0c;堆上的對象越多&#xff0c;對象的引用越多&#xff0c;意味著…

在Linux上將 `.sh` 腳本、`.jar` 包或其他腳本文件添加到開機自啟動

在Linux上將 .sh 腳本、.jar 包或其他腳本文件添加到開機自啟動 在Linux環境中&#xff0c;有時需要將一些程序、腳本或應用程序設置為開機時自動啟動。這對于那些需要在系統啟動時啟動的服務或應用非常有用。本文將介紹如何將 .sh 腳本、.jar 包或其他腳本文件添加到Linux系統…

Git使用步驟

Git 是一個分布式版本控制系統&#xff0c;廣泛用于軟件開發和其他需要跟蹤文件變更的項目。以下是 Git 的基本使用方法和一些常用命令的詳細說明。 安裝 Git 在大多數操作系統上&#xff0c;你可以通過包管理器安裝 Git&#xff1a; Windows: 下載并安裝 Git for Windows。…

詳細指南:在Ubuntu 20.04上安裝和配置Orbbec SDK及USB設備權限

詳細指南&#xff1a;在Ubuntu 20.04上安裝和配置Orbbec SDK及USB設備權限 在Ubuntu 20.04上安裝和配置Orbbec SDK以及進行USB設備的權限配置和調整USBFS緩存大小&#xff0c;涉及到一系列系統配置和環境準備步驟。以下是詳細的步驟說明&#xff0c;以確保準確和高效地設置開發…

【GCC】2015: draft-alvestrand-rmcat-congestion-03 機器翻譯

騰訊云的一個分析,明顯是看了這個論文和草案的 : 最新的是應該是這個 A Google Congestion Control Algorithm for Real-Time Communication draft-ietf-rmcat-gcc-02 下面的這個應該過期了: draft-alvestrand-rmcat-congestion-03

計算機網絡技術基礎:5.數據通信系統

一、數據通信的基本概念 1.信息 信息是對客觀事物的運動狀態和存在形式的反映&#xff0c;可以是客觀事實的形態、大小、結構、性能等描述&#xff0c;也可以是客觀事物與外部之間的聯系。信息的載體可以是數字、文字、語音、圖形和圖像等。計算機及其外圍設備產生和交換的信息…

STM32中ADC模數轉換器

一、ADC簡介 ADC模擬-數字轉換器 ADC可以將引腳連續變化的模擬電壓轉換為內存中存儲的數字變量&#xff0c;建立模擬電路到數字電路的橋梁 12位逐次逼近型ADC&#xff0c;1us轉換時間 輸入電壓范圍&#xff1a; 0~3.3V&#xff0c;轉換結果范圍&#xff1a;0~4095 18個輸入…

醫療領域的網絡安全預防:保障患者隱私與醫療數據安全

醫療領域的網絡安全預防&#xff1a;保障患者隱私與醫療數據安全 隨著信息技術的不斷發展和醫療行業的數字化轉型&#xff0c;網絡安全在醫療領域變得愈加重要。醫療行業處理著大量的敏感數據&#xff0c;包括患者的個人信息、醫療記錄、診療方案等&#xff0c;這些數據一旦被…

【數字圖像處理】期末綜合知識點總結 ver1,灰度圖像,圖像增強,平滑濾波,銳化濾波,圖像復原,圖像壓縮

關注作者了解更多 我的其他CSDN專欄 過程控制系統 工程測試技術 虛擬儀器技術 可編程控制器 工業現場總線 數字圖像處理 智能控制 傳感器技術 嵌入式系統 復變函數與積分變換 單片機原理 線性代數 大學物理 熱工與工程流體力學 數字信號處理 光電融合集成電路…

.NET 技術 | 調用系統API創建Windows服務

01閱讀須知 此文所提供的信息只為網絡安全人員對自己所負責的網站、服務器等&#xff08;包括但不限于&#xff09;進行檢測或維護參考&#xff0c;未經授權請勿利用文章中的技術資料對任何計算機系統進行入侵操作。利用此文所提供的信息而造成的直接或間接后果和損失&#xf…

【Qt】QWidget中的常見屬性及其功能(二)

目錄 六、windowOpacity 例子&#xff1a; 七、cursor 例子&#xff1a; 八、font 九、toolTip 例子&#xff1a; 十、focusPolicy 例子&#xff1a; 十一、styleSheet 計算機中的顏色表示 例子&#xff1a; 六、windowOpacity opacity是不透明度的意思。 用于設…

Elasticsearch02-安裝7.x

零、文章目錄 Elasticsearch02-安裝7.x 1、Windows安裝Elasticsearch &#xff08;1&#xff09;JDK安裝 Elasticsearch是基于java開發的&#xff0c;所以需要安裝JDK。我們安裝的Elasticsearch版本是7.15&#xff0c;對應JDK至少1.8版本以上。也可以不安裝jdk&#xff0c;…

php學習資料分享

php學習資料分享&#xff1a;夸克網盤分享

UWA Gears V1.0.5|新增Thread Load指標

UWA Gears 是UWA最新發布的無SDK性能分析工具。針對移動平臺&#xff0c;提供了實時監測和截幀分析功能&#xff0c;幫助您精準定位性能熱點&#xff0c;提升應用的整體表現。 本次版本更新主要是新增了Thread Load指標&#xff0c;幫助大家更直觀地了解多線程任務的負載分布情…

IAR中如何而將定義的數組放在指定的位置

在keil中可以使用下面的方法將數組定義到指定的位置 uint8_t g_usart_rx_buf[USART_REC_LEN] __attribute__ ((at(0X20001000)));但是這個方法在IAR中是用不了的,通過網上查找各種資料&#xff0c;發現了兩種可用的方法。我這里測試的單片機是stm32f103c8t6&#xff0c;其他單…

共創共建!葡萄城 SpreadJS 完成 HarmonyOS NEXT 操作系統兼容認證

最新技術資源&#xff08;建議收藏&#xff09; https://www.grapecity.com.cn/resources/ 近日&#xff0c;華為“企業工作必備應用鴻蒙化論壇”在北京圓滿落幕&#xff0c;論壇匯聚了眾多行業精英和合作伙伴&#xff0c;聚焦討論企業數字化轉型與原生鴻蒙生態融合等話題。葡萄…

hpe服務器更新陣列卡firmware

背景 操作系統&#xff1a;RHEL7.8 hpe服務器經常出現硬盤斷開&#xff0c;陣列卡重啟問題&#xff0c;導致系統hang住。只能手動硬重啟。 I/O error&#xff0c;dev sda smartpqi 0000:5c:00:0: resettiong scsi 1:1:0:1 smartpqi 0000:5c:00:0: reset of scsi 1:1:0:1:…

websocket的心跳檢測和斷線重連

心跳檢測和斷線重連可以通過WebSocket的事件和屬性來實現。以下是一個簡單的JavaScript示例&#xff0c;使用WebSocket API實現心跳檢測和斷線重連的功能&#xff1a; let ws;function connectWebSocket() {ws new WebSocket(ws://your-websocket-server-url);ws.onopen fun…