Vue3 Hooks封装实战:告别重复代码,写出高复用、高质量的自定义Hook
🚀Vue3 Hooks封装实战:告别重复代码,写出高复用、高质量的自定义Hook
在Vue3 Composition API时代,自定义Hook(简称Composables)是提升代码复用性、解耦业务逻辑的核心利器。但很多开发者用Hook时要么写成“伪代码”(逻辑混乱、复用性差),要么只会简单封装,没发挥出Hook的真正价值。
今天就带大家彻底搞懂Vue3 Hooks封装:从基础实现、使用方法,到高质量封装的核心原则,手把手教你写出“干净、可复用、易维护”的自定义Hook,告别重复造轮子!
📌 先搞懂:什么是Vue3自定义Hook?
Hook本质是抽离出来的、可复用的组合式函数,以useXXX命名(约定俗成),把组件中通用的逻辑(如请求、防抖、监听、状态管理)封装成独立函数,让组件只专注于渲染逻辑。
✅ 核心优势:
-
复用性:一处封装,多处调用,告别复制粘贴;
-
解耦:把业务逻辑从组件中抽离,组件代码更简洁;
-
可维护:逻辑集中管理,修改只需改Hook,无需改所有使用的组件;
-
可测试:独立函数更容易编写单元测试。
🛠 基础篇:从零实现第一个自定义Hook
先从最简单的场景入手,比如封装一个“防抖Hook”(开发中高频使用),理解Hook的基本实现和使用方式。
1. 目录规范(第一步:做好结构)
首先规划Hook的目录结构,避免零散存放:
src/
├── hooks/ # 所有自定义Hook统一存放
│ ├── index.js # Hook导出入口(按需聚合)
│ ├── useDebounce.js # 防抖Hook
│ ├── useRequest.js # 请求Hook(后续实战)
│ └── useEventListener.js # 事件监听Hook
└── components/ # 组件目录(使用Hook)
2. 实现第一个Hook:useDebounce
需求:封装防抖逻辑,传入函数和延迟时间,返回防抖后的函数。
// src/hooks/useDebounce.js
import { ref } from 'vue'
/**
* 防抖Hook
* @param {Function} fn - 需要防抖的函数
* @param {number} delay - 防抖延迟时间(默认300ms)
* @returns {Function} 防抖后的函数
*/
export function useDebounce(fn, delay = 300) {
// 定义定时器ref(响应式,避免丢失)
const timer = ref(null)
// 封装防抖函数
const debouncedFn = (...args) => {
// 清除上一次定时器
if (timer.value) clearTimeout(timer.value)
// 重新设置定时器
timer.value = setTimeout(() => {
// 执行原函数,绑定this并传递参数
fn.apply(this, args)
}, delay)
}
// 提供清除定时器的方法(避免内存泄漏)
const cancel = () => {
if (timer.value) clearTimeout(timer.value)
timer.value = null
}
// 返回防抖函数和取消方法
return { debouncedFn, cancel }
}
3. 在组件中使用useDebounce
<!-- src/components/SearchInput.vue -->
<template>
<input v-model="searchValue" @input="handleInput" placeholder="请输入搜索内容" />
</template>
<script setup>
import { ref } from 'vue'
import { useDebounce } from '@/hooks/useDebounce'
const searchValue = ref('')
// 定义原始搜索函数
const search = (value) => {
console.log('发起搜索:', value)
// 实际开发中这里调用接口
}
// 使用防抖Hook,延迟500ms
const { debouncedFn: debouncedSearch } = useDebounce((value) => {
search(value)
}, 500)
// 输入事件绑定防抖函数
const handleInput = (e) => {
searchValue.value = e.target.value
debouncedSearch(e.target.value)
}
</script>
�进阶篇:封装高频实战Hook——useRequest
防抖只是基础,接下来封装开发中最常用的“请求Hook”,覆盖加载状态、错误处理、取消请求等核心场景,体现高质量Hook的封装思路。
1. 需求分析(先明确要解决的问题)
一个高质量的请求Hook需要包含:
-
加载状态(loading):用于页面loading展示;
-
数据返回(data):请求成功的结果;
-
错误信息(error):请求失败的提示;
-
主动触发(run):手动调用请求;
-
取消请求(cancel):避免无用请求;
-
刷新(refresh):重新发起请求;
-
防抖/节流(可选):防止重复请求。
2. 实现useRequest Hook
// src/hooks/useRequest.js
import { ref, unref, onUnmounted } from 'vue'
/**
* 通用请求Hook
* @param {Function} requestFn - 异步请求函数(需返回Promise)
* @param {Object} options - 配置项
* @param {boolean} options.manual - 是否手动触发(默认false,初始化自动执行)
* @param {number} options.debounceDelay - 防抖延迟(0表示不防抖)
* @param {Function} options.onSuccess - 请求成功回调
* @param {Function} options.onError - 请求失败回调
* @returns {Object} { loading, data, error, run, cancel, refresh }
*/
export function useRequest(requestFn, options = {}) {
// 默认配置
const {
manual = false,
debounceDelay = 0,
onSuccess = () => {},
onError = () => {}
} = options
// 响应式状态
const loading = ref(false) // 加载状态
const data = ref(null) // 请求结果
const error = ref(null) // 错误信息
let abortController = null // 用于取消fetch请求(axios用cancelToken)
let debounceTimer = null // 防抖定时器
// 取消请求方法
const cancel = () => {
if (abortController) abortController.abort()
loading.value = false
error.value = null
if (debounceTimer) clearTimeout(debounceTimer)
}
// 核心请求逻辑
const run = async (...args) => {
// 防抖处理
if (debounceDelay > 0) {
if (debounceTimer) clearTimeout(debounceTimer)
return new Promise((resolve) => {
debounceTimer = setTimeout(async () => {
const res = await executeRequest(...args)
resolve(res)
}, debounceDelay)
})
}
// 无防抖直接执行
return executeRequest(...args)
}
// 实际执行请求的函数
const executeRequest = async (...args) => {
// 重置状态
loading.value = true
error.value = null
try {
// 创建取消控制器(适配fetch)
abortController = new AbortController()
// 执行请求函数,传递参数和取消信号
const result = await requestFn(...args, { signal: abortController.signal })
data.value = result
onSuccess(result) // 成功回调
return result
} catch (err) {
// 排除手动取消的情况
if (err.name !== 'AbortError') {
error.value = err
onError(err) // 失败回调
}
throw err
} finally {
loading.value = false
abortController = null
}
}
// 刷新请求(复用最后一次参数)
const refresh = () => run(...lastArgs)
let lastArgs = [] // 缓存最后一次请求参数
// 重写run,缓存参数
const originRun = run
run = (...args) => {
lastArgs = args
return originRun(...args)
}
// 组件卸载时取消请求(防止内存泄漏)
onUnmounted(() => {
cancel()
})
// 非手动模式:初始化自动执行
if (!manual) {
run()
}
// 返回核心状态和方法
return {
loading: unref(loading), // 转为非响应式(可选,根据需求)
data,
error,
run,
cancel,
refresh
}
}
3. 在组件中使用useRequest
<!-- src/components/UserList.vue -->
<template>
<div>
<!-- 加载状态 -->
<div v-if="loading">加载中...</div>
<!-- 错误提示 -->
<div v-if="error" class="error">{{ error.message }}</div>
<!-- 数据展示 -->
<ul v-if="data">
<li v-for="user in data" :key="user.id">{{ user.name }}</li>
</ul>
<!-- 手动触发/刷新 -->
<button @click="refresh">刷新列表</button>
<button @click="cancel">取消请求</button>
</div>
</template>
<script setup>
import { useRequest } from '@/hooks/useRequest'
// 模拟异步请求函数(实际项目中是axios/fetch请求)
const fetchUserList = async (page = 1) => {
const res = await fetch(`/api/users?page=${page}`)
return res.json()
}
// 使用请求Hook
const { loading, data, error, run, cancel, refresh } = useRequest(
fetchUserList,
{
manual: false, // 初始化自动执行
debounceDelay: 300, // 防抖300ms
onSuccess: (res) => {
console.log('用户列表请求成功:', res)
},
onError: (err) => {
console.error('用户列表请求失败:', err)
}
}
)
// 手动触发请求(比如分页切换)
const changePage = (page) => {
run(page)
}
</script>
🎯 高质量Hook封装的核心原则(避坑+提效)
很多开发者写的Hook看似能用,实则是“伪代码”(复用性低、易出bug),遵循以下原则,让你的Hook质量翻倍:
1. 单一职责:一个Hook只做一件事
❌ 错误示例:一个Hook既处理请求,又处理防抖,还处理表单验证;
✅ 正确示例:useRequest只处理请求逻辑,防抖作为可选配置,表单验证单独封装useFormValidate。
2. 规范命名:统一useXXX格式
-
命名必须以
use开头,比如useDebounce、useRequest、useEventListener; -
名称清晰表达功能,避免模糊命名(如不用
useUtil,要用useLocalStorage)。
3. 暴露清晰的API:只返回必要的状态/方法
❌ 错误示例:返回所有内部变量(如定时器、临时参数);
✅ 正确示例:只返回组件需要的状态(loading/data)和方法(run/cancel),内部逻辑封装。
4. 处理边界情况:避免“伪代码”陷阱
-
内存泄漏:组件卸载时清理定时器、取消请求、移除事件监听;
-
参数校验:对入参做基础校验(如
useDebounce检查fn是否为函数); -
异常捕获:Hook内部捕获异常,避免组件崩溃;
-
默认值:给可选参数设置合理默认值,降低使用成本。
5. 响应式兼容:支持ref和原始值
使用unref处理入参,让Hook既支持原始值,也支持响应式ref:
// 示例:兼容ref和原始值
import { unref } from 'vue'
export function useExample(value) {
// 无论传入的是ref(10)还是10,都能获取到值
const realValue = unref(value)
// ...
}
6. 避免耦合:Hook不依赖具体业务
❌ 错误示例:Hook中硬编码接口地址、业务状态;
✅ 正确示例:通过参数传递业务相关内容(如useRequest接收请求函数,不硬编码接口)。
7. 文档注释:让别人能看懂、会使用
每个Hook都要写清晰的注释:
-
功能描述;
-
参数说明(类型、默认值、作用);
-
返回值说明;
-
使用示例(可选)。
8. 高质量Hook vs 伪代码Hook对比
| 维度 | 高质量Hook | 伪代码Hook |
|---|---|---|
| 复用性 | 多处调用,适配不同场景 | 仅适配当前组件,无法复用 |
| 边界处理 | 清理定时器、捕获异常 | 无清理逻辑,易内存泄漏 |
| 接口清晰 | 只暴露必要API | 返回所有内部变量,混乱 |
| 兼容性 | 支持ref和原始值 | 仅支持固定类型参数 |
📝 常用Hook封装清单(直接复用)
给大家整理了开发中高频使用的自定义Hook,可直接封装使用:
-
基础工具类:
useDebounce(防抖)、useThrottle(节流)、useEventListener(事件监听)、useLocalStorage(本地存储); -
业务通用类:
useRequest(请求)、useFormValidate(表单验证)、usePagination(分页)、usePermission(权限控制); -
交互类:
useModal(弹窗管理)、useScroll(滚动监听)、useCopy(复制粘贴)。
🎯 总结
核心要点回顾
-
Hook本质:以
useXXX命名的组合式函数,抽离通用逻辑,提升复用性; -
封装原则:单一职责、暴露清晰API、处理边界情况、避免业务耦合;
-
质量关键:清理内存泄漏、兼容响应式、做好参数校验和异常捕获;
-
使用技巧:统一目录结构,做好注释,让Hook“即插即用”。
新手避坑提醒
-
❌ 不要把Hook写成“组件逻辑的复制粘贴”,要抽象通用逻辑;
-
❌ 不要在Hook中操作DOM(除非专门封装DOM相关Hook);
-
❌ 不要过度封装:简单逻辑无需强行拆分成Hook,避免增加复杂度。
自定义Hook是Vue3中最能体现“组合式”思想的特性,掌握好Hook封装,能让你的代码从“能用”变成“好用”,大幅提升开发效率和项目可维护性。
如果你有更多Hook封装的实战场景或问题,欢迎在评论区交流~ 后续分享更多Vue3实战技巧!
🚀🚀 往期项目搭建,可前往
![]()
往期项目搭建,可前往[ Vue3路由实战:优雅封装+灵活拦截,解锁路由配置新]👆
![]()
往期项目搭建,可前往[ vue3 + TS + vite 搭建中后台管理系统(完整项目)]👆
![]()
往期项目搭建,可前往[ vue3 + Electron + vite 搭建桌面端应用(完整项目)]👆
![]()
往期项目搭建,可前往[ vue3 + vite + ts + element-ui搭建后台管理框架 ]👆

总结:
前端路上 | 所知甚少,唯善学。
各位小伙伴有什么疑问,欢迎留言探讨。
— 关注我:前端路上不迷路 —
更多推荐
往期项目搭建,可前往
所有评论(0)