84. 取消请求与防抖节流
概述
在现代Web应用中,高效的网络请求管理和事件处理是提升用户体验的重要环节。本集将深入探讨Vue 3项目中的三个关键优化技术:取消请求、防抖和节流。我们将学习如何使用Axios取消不必要的网络请求,以及如何通过防抖和节流技术优化用户交互,减少不必要的计算和网络请求,从而提升应用的性能和响应速度。
核心知识点
1. Axios取消请求
在某些场景下,我们需要取消正在进行的网络请求,例如:用户快速切换标签页、搜索框输入时的连续请求、组件卸载时的未完成请求等。Axios提供了多种取消请求的方式。
1.1 使用AbortController(推荐)
AbortController是现代浏览器提供的API,Axios从v0.22.0开始支持。
// src/utils/axios.ts
import axios from 'axios'
const http = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000
})
export default http
// 导出取消请求工具
export const createCancelToken = () => {
const controller = new AbortController()
return {
signal: controller.signal,
cancel: () => controller.abort()
}
}在组件中使用:
<!-- src/components/SearchComponent.vue -->
<template>
<div class="search-container">
<input
type="text"
v-model="keyword"
placeholder="搜索..."
@input="handleSearch"
/>
<div v-if="loading" class="loading">搜索中...</div>
<ul v-else class="search-results">
<li v-for="result in results" :key="result.id">{{ result.name }}</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { ref, onUnmounted } from 'vue'
import http, { createCancelToken } from '../utils/axios'
const keyword = ref('')
const results = ref([])
const loading = ref(false)
let cancelSearch: (() => void) | null = null
const handleSearch = async () => {
if (!keyword.value.trim()) {
results.value = []
return
}
// 取消之前的搜索请求
if (cancelSearch) {
cancelSearch()
}
const { signal, cancel } = createCancelToken()
cancelSearch = cancel
try {
loading.value = true
const response = await http.get('/api/search', {
params: { keyword: keyword.value },
signal // 传递signal给请求配置
})
results.value = response.data
} catch (error: any) {
if (error.name === 'CanceledError') {
console.log('搜索请求已取消')
} else {
console.error('搜索失败:', error)
}
} finally {
loading.value = false
}
}
// 组件卸载时取消未完成的请求
onUnmounted(() => {
if (cancelSearch) {
cancelSearch()
}
})
</script>1.2 使用CancelToken(旧版方式)
对于旧版Axios,可以使用CancelToken API,但该方式已被废弃,推荐使用AbortController。
// 旧版Axios取消请求方式(不推荐)
import axios, { CancelToken } from 'axios'
const source = CancelToken.source()
axios.get('/api/data', {
cancelToken: source.token
})
// 取消请求
source.cancel('请求已被取消')2. 防抖(Debounce)
防抖是指在事件被触发n秒后再执行回调,如果在这n秒内事件又被触发,则重新计时。常用于搜索框输入、窗口大小调整等场景。
2.1 实现自定义防抖函数
// src/utils/debounce.ts
/**
* 防抖函数
* @param fn 需要防抖的函数
* @param delay 延迟时间(毫秒)
* @returns 防抖后的函数
*/
export function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number
): (...args: Parameters<T>) => void {
let timer: ReturnType<typeof setTimeout> | null = null
return function(...args: Parameters<T>) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay)
}
}在组件中使用:
<!-- src/components/SearchComponent.vue -->
<template>
<input
type="text"
v-model="keyword"
placeholder="搜索..."
@input="debouncedSearch"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { debounce } from '../utils/debounce'
import http from '../utils/axios'
const keyword = ref('')
const results = ref([])
const loading = ref(false)
// 使用防抖函数包装搜索方法
const debouncedSearch = debounce(async (value: string) => {
if (!value.trim()) {
results.value = []
return
}
try {
loading.value = true
const response = await http.get('/api/search', {
params: { keyword: value }
})
results.value = response.data
} catch (error) {
console.error('搜索失败:', error)
} finally {
loading.value = false
}
}, 300) // 300毫秒延迟
// 监听keyword变化,调用防抖函数
const handleInput = (e: Event) => {
const target = e.target as HTMLInputElement
debouncedSearch(target.value)
}
</script>2.2 使用Lodash的防抖函数
如果项目中已经使用了Lodash,可以直接使用其提供的debounce函数:
<script setup lang="ts">
import { ref } from 'vue'
import { debounce } from 'lodash-es'
import http from '../utils/axios'
const keyword = ref('')
const results = ref([])
const loading = ref(false)
const debouncedSearch = debounce(async () => {
if (!keyword.value.trim()) {
results.value = []
return
}
try {
loading.value = true
const response = await http.get('/api/search', {
params: { keyword: keyword.value }
})
results.value = response.data
} catch (error) {
console.error('搜索失败:', error)
} finally {
loading.value = false
}
}, 300)
</script>3. 节流(Throttle)
节流是指在一定时间内只执行一次函数,常用于滚动事件、窗口 resize 事件、鼠标移动事件等场景。
3.1 实现自定义节流函数
// src/utils/throttle.ts
/**
* 节流函数
* @param fn 需要节流的函数
* @param delay 延迟时间(毫秒)
* @returns 节流后的函数
*/
export function throttle<T extends (...args: any[]) => any>(
fn: T,
delay: number
): (...args: Parameters<T>) => void {
let lastCall = 0
let timer: ReturnType<typeof setTimeout> | null = null
return function(...args: Parameters<T>) {
const now = Date.now()
const timeSinceLastCall = now - lastCall
if (timeSinceLastCall >= delay) {
// 如果距离上次调用已经超过delay,直接执行
lastCall = now
fn.apply(this, args)
} else {
// 否则,设置定时器在剩余时间后执行
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
lastCall = Date.now()
fn.apply(this, args)
timer = null
}, delay - timeSinceLastCall)
}
}
}在组件中使用:
<!-- src/components/ScrollComponent.vue -->
<template>
<div class="scroll-container" @scroll="throttledHandleScroll">
<div class="content">
<!-- 长内容 -->
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { throttle } from '../utils/throttle'
const scrollPosition = ref(0)
// 使用节流函数包装滚动处理方法
const throttledHandleScroll = throttle((e: Event) => {
const target = e.target as HTMLElement
scrollPosition.value = target.scrollTop
console.log('滚动位置:', scrollPosition.value)
// 可以在这里实现无限滚动加载等功能
if (target.scrollTop + target.clientHeight >= target.scrollHeight - 100) {
console.log('触底,加载更多数据')
// loadMoreData()
}
}, 200) // 200毫秒执行一次
</script>
<style scoped>
.scroll-container {
height: 400px;
overflow-y: auto;
border: 1px solid #ccc;
}
.content {
height: 2000px;
background: linear-gradient(to bottom, #f0f0f0, #e0e0e0);
}
</style>3.2 使用Lodash的节流函数
<script setup lang="ts">
import { ref } from 'vue'
import { throttle } from 'lodash-es'
const scrollPosition = ref(0)
const throttledHandleScroll = throttle((e: Event) => {
const target = e.target as HTMLElement
scrollPosition.value = target.scrollTop
console.log('滚动位置:', scrollPosition.value)
}, 200)
</script>4. 结合取消请求与防抖节流
在实际开发中,我们经常需要将取消请求与防抖节流结合使用,以优化用户体验。
<!-- src/components/AdvancedSearch.vue -->
<template>
<div class="advanced-search">
<input
type="text"
v-model="keyword"
placeholder="搜索..."
@input="handleSearch"
/>
<div v-if="loading" class="loading">搜索中...</div>
<ul v-else class="results">
<li v-for="result in results" :key="result.id">{{ result.name }}</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { ref, onUnmounted } from 'vue'
import { debounce } from 'lodash-es'
import http, { createCancelToken } from '../utils/axios'
const keyword = ref('')
const results = ref([])
const loading = ref(false)
let cancelSearch: (() => void) | null = null
// 结合防抖和取消请求
const debouncedSearch = debounce(async (searchKeyword: string) => {
if (!searchKeyword.trim()) {
results.value = []
return
}
// 取消之前的请求
if (cancelSearch) {
cancelSearch()
}
const { signal, cancel } = createCancelToken()
cancelSearch = cancel
try {
loading.value = true
const response = await http.get('/api/search', {
params: { keyword: searchKeyword },
signal
})
results.value = response.data
} catch (error: any) {
if (error.name !== 'CanceledError') {
console.error('搜索失败:', error)
}
} finally {
loading.value = false
}
}, 300)
const handleSearch = () => {
debouncedSearch(keyword.value)
}
onUnmounted(() => {
if (cancelSearch) {
cancelSearch()
}
})
</script>5. 在Vue组合式API中使用
我们可以创建自定义组合式函数,将取消请求、防抖和节流逻辑封装起来,以便在多个组件中复用。
// src/composables/useDebouncedSearch.ts
import { ref, onUnmounted } from 'vue'
import { debounce } from 'lodash-es'
import http, { createCancelToken } from '../utils/axios'
export function useDebouncedSearch<T>(url: string, delay = 300) {
const results = ref<T[]>([])
const loading = ref(false)
const error = ref<string | null>(null)
let cancelRequest: (() => void) | null = null
const search = debounce(async (keyword: string) => {
if (!keyword.trim()) {
results.value = []
return
}
// 取消之前的请求
if (cancelRequest) {
cancelRequest()
}
const { signal, cancel } = createCancelToken()
cancelRequest = cancel
try {
loading.value = true
error.value = null
const response = await http.get<T[]>(url, {
params: { keyword },
signal
})
results.value = response.data
} catch (err: any) {
if (err.name !== 'CanceledError') {
error.value = '搜索失败,请稍后重试'
console.error('搜索错误:', err)
}
} finally {
loading.value = false
}
}, delay)
onUnmounted(() => {
if (cancelRequest) {
cancelRequest()
}
})
return {
results,
loading,
error,
search
}
}在组件中使用:
<!-- src/components/ReusableSearch.vue -->
<template>
<div class="reusable-search">
<input
type="text"
v-model="keyword"
placeholder="搜索..."
@input="handleSearch"
/>
<div v-if="loading" class="loading">搜索中...</div>
<div v-else-if="error" class="error">{{ error }}</div>
<ul v-else class="results">
<li v-for="result in results" :key="result.id">{{ result.name }}</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useDebouncedSearch } from '../composables/useDebouncedSearch'
interface SearchResult {
id: number
name: string
}
const keyword = ref('')
const { results, loading, error, search } = useDebouncedSearch<SearchResult>('/api/search', 300)
const handleSearch = () => {
search(keyword.value)
}
</script>最佳实践
1. 合理选择取消请求的时机
- 组件卸载时取消未完成的请求
- 用户快速操作时取消之前的请求
- 搜索框输入时取消之前的搜索请求
- 路由切换时取消当前页面的未完成请求
2. 防抖与节流的使用场景
| 技术 | 适用场景 | 延迟建议 |
|---|---|---|
| 防抖 | 搜索框输入、表单验证、窗口大小调整 | 300-500ms |
| 节流 | 滚动事件、鼠标移动、按钮点击频率限制 | 100-200ms |
3. 结合使用的注意事项
- 防抖和节流可以结合使用,例如:先防抖再节流
- 取消请求应该在防抖/节流函数内部实现
- 注意清理定时器和取消函数,避免内存泄漏
- 在组件卸载时确保清理所有未完成的请求和定时器
4. 使用第三方库的建议
- 如果项目中已经使用了Lodash,建议直接使用其提供的防抖和节流函数
- 否则,可以使用自定义实现,减少依赖
- 对于取消请求,推荐使用AbortController API
常见问题与解决方案
1. 问题:防抖函数没有生效
解决方案:
- 确保防抖函数是在组件初始化时创建的,而不是在事件处理函数中创建
- 检查防抖函数的延迟时间是否设置合理
- 确保正确传递了参数
2. 问题:取消请求后仍然执行了回调
解决方案:
- 确保在catch块中检查错误类型,区分取消请求和其他错误
- 检查取消函数是否正确调用
- 确保使用了正确的取消请求API
3. 问题:防抖/节流函数导致this指向错误
解决方案:
- 使用箭头函数或bind方法确保this指向正确
- 在自定义实现中使用apply方法传递this上下文
4. 问题:组件卸载后仍然执行了异步操作
解决方案:
- 在组件卸载时取消所有未完成的请求
- 清除所有定时器
- 使用Vue的onUnmounted钩子进行清理
进一步学习资源
课后练习
基础练习:
- 实现自定义的防抖和节流函数
- 在Vue组件中使用Axios取消请求
- 为搜索框添加防抖功能
进阶练习:
- 创建一个组合式函数,封装防抖搜索功能
- 实现无限滚动加载,使用节流优化
- 结合取消请求和防抖,优化复杂表单的实时验证
挑战练习:
- 实现一个可配置的防抖/节流组件,支持多种配置选项
- 开发一个请求管理工具,支持批量取消请求
- 设计一个性能监控组件,统计防抖和节流减少的请求数量
通过本集的学习,你应该能够掌握取消请求、防抖和节流的核心概念和实现方式,并能够在Vue 3项目中灵活应用这些技术来优化网络请求和用户交互。这些优化技术虽然简单,但在实际开发中能够显著提升应用的性能和用户体验,是每个前端开发者都应该掌握的重要技能。