1. Vue 3.4+ 异步副作用管理革命:onCleanup 深度解析
在构建现代前端应用时,异步操作无处不在。从简单的数据获取到复杂的实时通信,我们经常需要在组件中处理各种异步任务。然而,这些异步操作带来的副作用管理问题一直困扰着开发者。Vue 3.4 引入的 onCleanup 功能彻底改变了这一局面,它为我们提供了一种优雅的方式来处理异步副作用的清理工作。
onCleanup 是 Vue 响应式系统的一个重要补充,它允许我们在 watch 和 watchEffect 中注册清理函数,当依赖变化或组件卸载时自动执行这些清理操作。这个特性特别适合处理以下几种常见场景:
- 取消进行中的网络请求
- 清除定时器和轮询任务
- 关闭 WebSocket 和事件订阅
- 清理任何可能产生内存泄漏的资源
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. onCleanup 核心机制解析
2.1 基础使用模式
onCleanup 的基本用法非常简单,它作为第三个参数传递给 watch 回调函数,或者作为唯一参数传递给 watchEffect 的回调函数。让我们看一个最基本的示例:
javascript复制import { ref, watch } from 'vue'
const searchQuery = ref('')
watch(searchQuery, async (newValue, oldValue, onCleanup) => {
let cancelled = false
// 注册清理函数
onCleanup(() => {
cancelled = true
})
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 1000))
// 检查是否已被取消
if (!cancelled) {
console.log('处理结果:', newValue)
}
})
在这个例子中,每当 searchQuery 发生变化时,Vue 会先执行上一次注册的清理函数(如果有的话),然后再执行新的回调。这确保了旧的操作不会干扰新的操作。
2.2 与 AbortController 集成
在实际应用中,我们经常需要取消网络请求。AbortController 是现代浏览器提供的原生 API,正好可以与 onCleanup 完美配合:
javascript复制watch(searchQuery, async (newValue, oldValue, onCleanup) => {
const controller = new AbortController()
// 注册清理函数
onCleanup(() => {
controller.abort()
})
try {
const response = await fetch(`/api/search?q=${newValue}`, {
signal: controller.signal
})
const data = await response.json()
// 处理数据...
} catch (error) {
if (error.name !== 'AbortError') {
console.error('请求失败:', error)
}
}
})
这种模式解决了前端开发中常见的竞态条件问题——当用户快速输入时,确保只有最后一次搜索请求的结果会被处理。
3. 实战应用场景
3.1 搜索功能与防抖优化
搜索功能是 onCleanup 最典型的应用场景之一。结合防抖技术,我们可以创建出用户体验极佳的搜索组件:
javascript复制import { ref, watch } from 'vue'
export function useSearch() {
const searchQuery = ref('')
const results = ref([])
const isLoading = ref(false)
const error = ref(null)
watch(searchQuery, async (newValue, oldValue, onCleanup) => {
if (newValue.trim().length < 2) {
results.value = []
return
}
let cancelled = false
const controller = new AbortController()
// 注册清理函数
onCleanup(() => {
cancelled = true
controller.abort()
isLoading.value = false
})
// 添加防抖延迟
await new Promise(resolve => setTimeout(resolve, 300))
if (cancelled) return
isLoading.value = true
error.value = null
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(newValue)}`, {
signal: controller.signal
})
if (cancelled) return
const data = await response.json()
if (!cancelled) {
results.value = data
}
} catch (err) {
if (err.name !== 'AbortError' && !cancelled) {
error.value = err
results.value = []
}
} finally {
if (!cancelled) {
isLoading.value = false
}
}
})
return {
searchQuery,
results,
isLoading,
error
}
}
这个实现有几个关键点值得注意:
- 防抖处理:通过 300ms 的延迟减少不必要的 API 调用
- 取消机制:使用
AbortController确保只有最后一次请求会被处理 - 状态管理:正确维护加载状态和错误状态
- 最小查询长度:避免对空或过短查询的无效请求
3.2 数据轮询模式
另一个常见场景是实现数据的定期轮询。onCleanup 让这种模式的实现变得异常简单:
javascript复制import { ref, watch } from 'vue'
export function usePollingData() {
const isPolling = ref(false)
const data = ref(null)
const error = ref(null)
watch(isPolling, (shouldPoll, _, onCleanup) =>
