vue中的?watchEffect、watchAsyncEffect、watchPostEffect的區(qū)別對(duì)比分析
在 Vue 中,watchEffect、watchAsyncEffect 和 watchPostEffect 都是用于響應(yīng)式數(shù)據(jù)監(jiān)聽(tīng)的 API,但它們?cè)趫?zhí)行時(shí)機(jī)和行為上存在重要區(qū)別:
- watchEffect
- 立即執(zhí)行傳入的函數(shù),并在函數(shù)中使用的響應(yīng)式數(shù)據(jù)變化時(shí)重新執(zhí)行
- 默認(rèn)在DOM 更新前執(zhí)行(前置執(zhí)行)
- 適用于大多數(shù)需要立即響應(yīng)數(shù)據(jù)變化的場(chǎng)景
- watchPostEffect
- 行為與
watchEffect類(lèi)似,但回調(diào)函數(shù)會(huì)在DOM 更新后執(zhí)行(后置執(zhí)行) - 當(dāng)需要在數(shù)據(jù)變化并完成 DOM 更新后再執(zhí)行操作時(shí)使用(如獲取更新后的 DOM 尺寸)
- 相當(dāng)于
watchEffect加{ flush: 'post' }配置
- 行為與
- watchAsyncEffect
- 與
watchEffect類(lèi)似,但允許回調(diào)函數(shù)是異步的(返回 Promise) - Vue 會(huì)等待異步操作完成后再處理下一次觸發(fā),避免競(jìng)態(tài)條件
- 適用于需要在響應(yīng)式數(shù)據(jù)變化時(shí)執(zhí)行異步操作的場(chǎng)景
- 與
示例代碼對(duì)比:
// 立即執(zhí)行,DOM更新前觸發(fā)
watchEffect(() => {
console.log('數(shù)據(jù)變化了,DOM更新前執(zhí)行')
})
// 立即執(zhí)行,DOM更新后觸發(fā)
watchPostEffect(() => {
console.log('數(shù)據(jù)變化了,DOM更新后執(zhí)行')
// 可以安全獲取更新后的DOM信息
})
// 處理異步操作
watchAsyncEffect(async () => {
console.log('開(kāi)始異步操作')
await fetchData() // 異步操作
console.log('異步操作完成')
})簡(jiǎn)單來(lái)說(shuō),選擇哪個(gè) API 主要取決于:
- 是否需要在 DOM 更新前還是后執(zhí)行
- 回調(diào)函數(shù)是否包含異步操作
下面通過(guò)一個(gè)具體的示例來(lái)展示 watchEffect、watchAsyncEffect 和 watchPostEffect 的不同使用場(chǎng)景。這個(gè)示例包含一個(gè)計(jì)數(shù)器和一個(gè)列表,通過(guò)不同的監(jiān)聽(tīng)方式展示它們的行為差異。
<template>
<div class="container">
<h3>計(jì)數(shù)器: {{ count }}</h3>
<button @click="count++">增加</button>
<div class="list-container">
<p>列表項(xiàng)數(shù)量: {{ items.length }}</p>
<ul ref="itemList">
<li v-for="item in items" :key="item.id">{{ item.text }}</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, watchEffect, watchPostEffect, watchAsyncEffect } from 'vue'
// 響應(yīng)式數(shù)據(jù)
const count = ref(0)
const items = ref([])
const itemList = ref(null)
// 1. watchEffect: DOM更新前執(zhí)行 - 適合數(shù)據(jù)處理
watchEffect(() => {
// 當(dāng)count變化時(shí),添加新的列表項(xiàng)
if (count.value > 0) {
items.value.push({
id: count.value,
text: `項(xiàng)目 ${count.value}`
})
}
console.log('watchEffect: 處理數(shù)據(jù),當(dāng)前列表長(zhǎng)度:', items.value.length)
// 此時(shí)獲取的DOM高度可能不準(zhǔn)確(DOM尚未更新)
if (itemList.value) {
console.log('watchEffect: 列表高度(可能不準(zhǔn)確):', itemList.value.offsetHeight)
}
})
// 2. watchPostEffect: DOM更新后執(zhí)行 - 適合操作DOM
watchPostEffect(() => {
// 確保在DOM更新后獲取列表高度
if (itemList.value) {
console.log('watchPostEffect: 列表高度(準(zhǔn)確):', itemList.value.offsetHeight)
}
})
// 3. watchAsyncEffect: 處理異步操作 - 適合API調(diào)用等異步任務(wù)
watchAsyncEffect(async () => {
if (count.value > 0) {
console.log('watchAsyncEffect: 開(kāi)始異步處理...')
// 模擬API請(qǐng)求
await new Promise(resolve => setTimeout(resolve, 500))
console.log(`watchAsyncEffect: 異步處理完成,當(dāng)前計(jì)數(shù): ${count.value}`)
}
})
</script>
<style>
.container {
padding: 20px;
}
.list-container {
margin-top: 20px;
padding: 10px;
border: 1px solid #ccc;
}
button {
padding: 8px 16px;
margin-top: 10px;
cursor: pointer;
}
</style>各API的使用場(chǎng)景解析:
- watchEffect
- 使用場(chǎng)景:數(shù)據(jù)處理、狀態(tài)轉(zhuǎn)換等不需要等待DOM更新的操作
- 示例中:當(dāng)計(jì)數(shù)器變化時(shí),立即更新列表數(shù)據(jù)
- 特點(diǎn):在DOM更新前執(zhí)行,此時(shí)如果嘗試獲取DOM屬性(如高度)可能得到舊值
- watchPostEffect
- 使用場(chǎng)景:需要操作DOM、獲取DOM尺寸或位置的場(chǎng)景
- 示例中:在列表更新后準(zhǔn)確獲取其高度
- 特點(diǎn):在DOM更新后執(zhí)行,確保能獲取到最新的DOM狀態(tài)
- watchAsyncEffect
- 使用場(chǎng)景:需要執(zhí)行異步操作(如API請(qǐng)求、定時(shí)器等)的場(chǎng)景
- 示例中:模擬在計(jì)數(shù)器變化后執(zhí)行異步任務(wù)
- 特點(diǎn):支持異步函數(shù),Vue會(huì)等待前一次異步操作完成后再處理下一次觸發(fā),避免競(jìng)態(tài)問(wèn)題
運(yùn)行效果:
點(diǎn)擊"增加"按鈕后,控制臺(tái)會(huì)輸出類(lèi)似以下內(nèi)容:
watchEffect: 處理數(shù)據(jù),當(dāng)前列表長(zhǎng)度: 1 watchEffect: 列表高度(可能不準(zhǔn)確): 0 watchPostEffect: 列表高度(準(zhǔn)確): 21 watchAsyncEffect: 開(kāi)始異步處理... watchAsyncEffect: 異步處理完成,當(dāng)前計(jì)數(shù): 1
在 Vue 中,watchEffect、watchAsyncEffect 和 watchPostEffect 都會(huì)返回一個(gè) 停止函數(shù),通過(guò)調(diào)用這個(gè)函數(shù)可以手動(dòng)停止監(jiān)聽(tīng)。這是停止這些響應(yīng)式監(jiān)聽(tīng)的統(tǒng)一方式,適用于所有這三個(gè) API。
基本用法
調(diào)用監(jiān)聽(tīng) API 時(shí),會(huì)得到一個(gè)函數(shù),執(zhí)行該函數(shù)即可停止監(jiān)聽(tīng):
import { watchEffect, watchAsyncEffect, watchPostEffect, ref } from 'vue'
const count = ref(0)
// 1. 停止 watchEffect
const stopEffect = watchEffect(() => {
console.log('count 變化:', count.value)
})
// 2. 停止 watchAsyncEffect
const stopAsyncEffect = watchAsyncEffect(async () => {
await someAsyncOperation()
console.log('異步處理 count:', count.value)
})
// 3. 停止 watchPostEffect
const stopPostEffect = watchPostEffect(() => {
console.log('DOM 更新后處理 count:', count.value)
})
// 需要停止時(shí)調(diào)用
stopEffect() // 停止 watchEffect 監(jiān)聽(tīng)
stopAsyncEffect() // 停止 watchAsyncEffect 監(jiān)聽(tīng)
stopPostEffect() // 停止 watchPostEffect 監(jiān)聽(tīng)實(shí)際場(chǎng)景示例
下面是一個(gè)組件中停止監(jiān)聽(tīng)的完整示例,展示在組件卸載時(shí)自動(dòng)停止監(jiān)聽(tīng)(避免內(nèi)存泄漏):
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="count++">增加</button>
<button @click="stopAll">停止所有監(jiān)聽(tīng)</button>
</div>
</template>
<script setup>
import { ref, watchEffect, watchAsyncEffect, watchPostEffect, onUnmounted } from 'vue'
const count = ref(0)
// 創(chuàng)建監(jiān)聽(tīng)并保存停止函數(shù)
const stopEffect = watchEffect(() => {
console.log('watchEffect: count =', count.value)
})
const stopAsyncEffect = watchAsyncEffect(async () => {
await new Promise(resolve => setTimeout(resolve, 100))
console.log('watchAsyncEffect: 異步處理 count =', count.value)
})
const stopPostEffect = watchPostEffect(() => {
console.log('watchPostEffect: DOM更新后 count =', count.value)
})
// 手動(dòng)停止所有監(jiān)聽(tīng)的方法
const stopAll = () => {
stopEffect()
stopAsyncEffect()
stopPostEffect()
console.log('所有監(jiān)聽(tīng)已停止')
}
// 組件卸載時(shí)自動(dòng)停止(推薦做法)
onUnmounted(() => {
stopEffect()
stopAsyncEffect()
stopPostEffect()
})
</script>關(guān)鍵點(diǎn)說(shuō)明
- 自動(dòng)停止時(shí)機(jī):
- 在組件的
setup或<script setup>中創(chuàng)建的監(jiān)聽(tīng),會(huì)在組件卸載時(shí) 自動(dòng)停止,無(wú)需手動(dòng)處理。 - 但如果是在非組件環(huán)境(如全局狀態(tài)管理)中創(chuàng)建的監(jiān)聽(tīng),則需要 手動(dòng)調(diào)用停止函數(shù) 清理。
- 在組件的
- 停止后的行為:
- 調(diào)用停止函數(shù)后,響應(yīng)式數(shù)據(jù)變化時(shí),對(duì)應(yīng)的監(jiān)聽(tīng)回調(diào) 不會(huì)再執(zhí)行。
- 對(duì)于
watchAsyncEffect,如果異步操作已經(jīng)開(kāi)始,停止監(jiān)聽(tīng)后 不會(huì)取消正在進(jìn)行的異步操作,但后續(xù)的數(shù)據(jù)變化不會(huì)再觸發(fā)新的異步任務(wù)。
- 最佳實(shí)踐:
- 組件內(nèi)的監(jiān)聽(tīng):通常無(wú)需手動(dòng)停止,依賴(lài) Vue 的自動(dòng)清理。
- 長(zhǎng)時(shí)間運(yùn)行的監(jiān)聽(tīng)(如全局?jǐn)?shù)據(jù)):在不需要時(shí)主動(dòng)調(diào)用停止函數(shù),避免內(nèi)存泄漏。
在實(shí)際開(kāi)發(fā)中,選擇 watchEffect、watchAsyncEffect 還是 watchPostEffect 主要取決于 操作的時(shí)機(jī) 和 是否包含異步邏輯。以下是基于具體場(chǎng)景的選擇指南:
1. 優(yōu)先用watchEffect的場(chǎng)景
- 核心特點(diǎn):同步執(zhí)行,在 DOM 更新前觸發(fā)。
- 適用于 不需要等待 DOM 更新 且 無(wú)異步操作 的響應(yīng)式處理。
- 典型場(chǎng)景:
- 數(shù)據(jù)轉(zhuǎn)換/過(guò)濾:基于響應(yīng)式數(shù)據(jù)生成衍生數(shù)據(jù)
- 例如:根據(jù)搜索關(guān)鍵詞過(guò)濾列表(只需處理數(shù)據(jù),無(wú)需操作 DOM)
- 數(shù)據(jù)轉(zhuǎn)換/過(guò)濾:基于響應(yīng)式數(shù)據(jù)生成衍生數(shù)據(jù)
const keywords = ref('')
const list = ref([...])
const filteredList = ref([])
watchEffect(() => {
// 同步過(guò)濾數(shù)據(jù),DOM 更新前執(zhí)行
filteredList.value = list.value.filter(item =>
item.name.includes(keywords.value)
)
})狀態(tài)聯(lián)動(dòng):一個(gè)狀態(tài)變化觸發(fā)另一個(gè)狀態(tài)更新
例如:表單字段聯(lián)動(dòng)(如“確認(rèn)密碼”隨“密碼”字段變化而校驗(yàn))
const password = ref('')
const confirmPwd = ref('')
const pwdError = ref('')
watchEffect(() => {
// 實(shí)時(shí)校驗(yàn),無(wú)需等待 DOM
if (confirmPwd.value && confirmPwd.value !== password.value) {
pwdError.value = '兩次密碼不一致'
} else {
pwdError.value = ''
}
})日志/調(diào)試:記錄數(shù)據(jù)變化(無(wú)需關(guān)心 DOM 狀態(tài))
2. 必須用watchPostEffect的場(chǎng)景
核心特點(diǎn):同步執(zhí)行,在 DOM 更新后觸發(fā)。
適用于 需要操作更新后的 DOM 的場(chǎng)景(依賴(lài) DOM 最新?tīng)顟B(tài))。
典型場(chǎng)景:
獲取 DOM 尺寸/位置:如計(jì)算元素寬高、滾動(dòng)位置
例如:列表渲染后自動(dòng)滾動(dòng)到底部
const messages = ref([])
const messageList = ref(null)
// 新增消息時(shí),滾動(dòng)到底部(依賴(lài)更新后的 DOM)
watchPostEffect(() => {
if (messageList.value) {
messageList.value.scrollTop = messageList.value.scrollHeight
}
})基于 DOM 狀態(tài)的樣式調(diào)整:如根據(jù)元素位置動(dòng)態(tài)修改樣式
const activeEl = ref(null)
const elPosition = ref({ top: 0, left: 0 })
watchPostEffect(() => {
if (activeEl.value) {
// 獲取元素實(shí)際位置(DOM 更新后才準(zhǔn)確)
const rect = activeEl.value.getBoundingClientRect()
elPosition.value = { top: rect.top, left: rect.left }
}
})第三方庫(kù) DOM 交互:如初始化依賴(lài) DOM 結(jié)構(gòu)的插件(圖表、編輯器等)
const chartData = ref([])
const chartContainer = ref(null)
watchPostEffect(() => {
// 確保容器 DOM 已更新,再初始化圖表
if (chartContainer.value) {
initChart(chartContainer.value, chartData.value)
}
})3. 必須用watchAsyncEffect的場(chǎng)景
- 核心特點(diǎn):支持異步函數(shù)(返回 Promise),會(huì)等待前一次異步完成后再處理下一次觸發(fā)(避免競(jìng)態(tài)問(wèn)題)。
- 適用于 響應(yīng)式數(shù)據(jù)變化時(shí)需要執(zhí)行異步操作 的場(chǎng)景。
- 典型場(chǎng)景:
- API 請(qǐng)求:根據(jù)參數(shù)變化發(fā)起請(qǐng)求(自動(dòng)處理競(jìng)態(tài))
- 例如:搜索框輸入變化時(shí)請(qǐng)求接口
const searchQuery = ref('')
const searchResult = ref([])
watchAsyncEffect(async () => {
if (!searchQuery.value) {
searchResult.value = []
return
}
// 異步請(qǐng)求,Vue 會(huì)自動(dòng)處理競(jìng)態(tài)(后一次請(qǐng)求會(huì)等待前一次完成)
const res = await fetch(`/api/search?query=${searchQuery.value}`)
searchResult.value = await res.json()
})帶延遲的異步操作:如防抖處理、定時(shí)器任務(wù)
例如:輸入停止 500ms 后執(zhí)行保存
const inputValue = ref('')
watchAsyncEffect(async () => {
// 延遲 500ms 執(zhí)行,避免頻繁觸發(fā)
await new Promise(resolve => setTimeout(resolve, 500))
await saveToServer(inputValue.value) // 異步保存
})依賴(lài)其他異步結(jié)果的操作:如多步異步流程
const userId = ref('')
const userPosts = ref([])
watchAsyncEffect(async () => {
if (!userId.value) return
// 先獲取用戶(hù)信息,再獲取用戶(hù)文章(依賴(lài)前一步異步結(jié)果)
const user = await fetchUser(userId.value)
const posts = await fetchPosts(user.id)
userPosts.value = posts
})總結(jié):選擇決策樹(shù)
- 是否有異步操作?
- 是 → 用
watchAsyncEffect - 否 → 看是否依賴(lài) DOM 更新
- 是 → 用
- 是否依賴(lài) DOM 更新后的狀態(tài)?
- 是 → 用
watchPostEffect - 否 → 用
watchEffect
- 是 → 用
通過(guò)遵循這個(gè)邏輯,可以準(zhǔn)確匹配 API 特性與實(shí)際需求,避免因時(shí)機(jī)錯(cuò)誤導(dǎo)致的 BUG(如獲取到舊的 DOM 狀態(tài)、異步競(jìng)態(tài)等)。
到此這篇關(guān)于vue中的 watchEffect、watchAsyncEffect、watchPostEffect的區(qū)別的文章就介紹到這了,更多相關(guān)vue watchEffect、watchAsyncEffect、watchPostEffect區(qū)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue3+echarts5踩坑以及resize方法報(bào)錯(cuò)的解決
這篇文章主要介紹了Vue3+echarts5踩坑以及resize方法報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
解決vue的 v-for 循環(huán)中圖片加載路徑問(wèn)題
今天小編就為大家分享一篇解決vue的 v-for 循環(huán)中圖片加載路徑問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09
Vue Router4與Router3路由配置與區(qū)別說(shuō)明
這篇文章主要介紹了Vue Router4與Router3路由配置與區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-12-12
vue-quill-editor+plupload富文本編輯器實(shí)例詳解
這篇文章主要介紹了vue-quill-editor+plupload富文本編輯器實(shí)例詳解,需要的朋友可以參考下2018-10-10
Vue項(xiàng)目webpack打包部署到Tomcat刷新報(bào)404錯(cuò)誤問(wèn)題的解決方案
今天很郁悶,遇到這樣一個(gè)奇葩問(wèn)題,使用webpack打包vue后,將打包好的文件,發(fā)布到Tomcat上,訪(fǎng)問(wèn)成功,但是刷新后頁(yè)面報(bào)404錯(cuò)誤,折騰半天才解決好,下面小編把Vue項(xiàng)目webpack打包部署到Tomcat刷新報(bào)404錯(cuò)誤問(wèn)題的解決方案分享給大家,需要的朋友一起看看吧2018-05-05
解決vue props傳Array/Object類(lèi)型值,子組件報(bào)錯(cuò)的情況
這篇文章主要介紹了解決vue props傳Array/Object類(lèi)型值,子組件報(bào)錯(cuò)的情況,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
vue3?setup語(yǔ)法糖中獲取slot插槽的dom對(duì)象代碼示例
slot元素是一個(gè)插槽出口,標(biāo)示了父元素提供的插槽內(nèi)容將在哪里被渲染,這篇文章主要給大家介紹了關(guān)于vue3?setup語(yǔ)法糖中獲取slot插槽的dom對(duì)象的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04

