一、为什么 Vue3 必须学自定义 Hooks?(核心价值)
Vue3 的核心升级不仅仅是性能提升 ,更大的优势在于编码体验与代码架构的革新。
Vue2 采用 Options API,代码结构是"碎片化"的:数据写在 data、方法写在 methods、监听写在 watch。
当组件业务复杂、代码量庞大时,会出现致命问题:
- 同一个业务逻辑被拆分在 N 个区域,维护需要来回切换
- 代码高度耦合、逻辑分散、可读性极差
- 复用逻辑困难,只能依赖 Mixin,缺陷极多
而 Vue3 Composition API + 自定义 Hooks 完美解决以上痛点:按功能聚合代码 ,一个功能所有变量、方法、监听统一收拢,实现高内聚、低耦合,让代码整洁、优雅、易复用、易维护。
二、Vue3 开发最大误区:摆脱 Vue2 无脑 this 思维
很多从 Vue2 过渡到 Vue3 的开发者,依然保留旧习惯:疯狂依赖 this。
甚至为了使用 this,刻意引入 getCurrentInstance,这是完全错误的写法!
1. Vue2 Options API 的致命缺陷
Vue2 所有变量、方法全部挂载在组件实例上,通过 this 调用:
代码耦合严重、逻辑混杂、全局污染,方法互相嵌套依赖,形成"蜘蛛网代码"。后期维护时,开发者需要在 template、data、methods、watch 之间反复跳转,阅读和修改成本极高。
2. Vue3 Composition API 的核心思想
彻底抛弃无脑 this!
Vue3 组合式 API 所有变量、函数、监听均为独立作用域变量 ,不再绑定组件实例。配合自定义 Hooks,可将不同业务逻辑按功能分块抽离:
- 修改功能 A,只需要看功能 A 的代码块
- 彻底解决代码分散、高耦合、难维护的问题
- 逻辑清晰、复用性极强、项目体量越大优势越明显
三、什么是 Vue3 自定义 Hooks?(官方无定义、但业界通用标准)
Vue3 官方没有给出自定义 Hooks 的明确定义,但拥有统一的编码规范与设计思想。
1. 自定义 Hooks 标准定义
自定义 Hooks 是抽离复用逻辑的独立函数模块,将组件内重复、通用的业务逻辑、响应式数据、监听方法,抽离到独立的 JS/TS 文件中,实现逻辑复用与解耦。
2. 自定义 Hooks 编码规范(必遵守)
- 文件名、函数名统一以 use 开头(如 useAdd、useScroll、useSearch)
- 以独立函数形式封装,内部可使用 Vue3 所有响应式API(ref、reactive、watch 等)
- 通过 return 显式暴露变量与方法
- 组件中通过 ES6 解构按需引入,清晰可控
- 支持传参定制逻辑,复用灵活性极高
四、Vue3 自定义 Hooks 实战案例(TS 完整版)
通过加法、减法通用逻辑封装,手把手演示自定义 Hooks 标准写法与使用方式。
案例1:封装加法逻辑 Hook(useAdd.ts)
typescript
// hooks/useAdd.ts
import { ref, watch } from 'vue'
// 接收外部传入的两个数字参数
export default function useAdd({ num1, num2 }: { num1: number; num2: number }) {
// 响应式结果
const addNum = ref(0)
// 监听参数变化,自动计算
watch([num1, num2], () => {
addFn(num1, num2)
})
// 核心计算方法
const addFn = (n1: number, n2: number) => {
addNum.value = n1 + n2
}
// 显式暴露数据与方法
return {
addNum,
addFn
}
}
案例2:封装减法逻辑 Hook(useSub.ts)
typescript
// hooks/useSub.ts
import { ref, watch } from 'vue'
export default function useSub({ num1, num2 }: { num1: number; num2: number }) {
const subNum = ref(0)
watch([num1, num2], () => {
subFn(num1, num2)
})
const subFn = (n1: number, n2: number) => {
subNum.value = n1 - n2
}
return {
subNum,
subFn
}
}
组件中使用自定义 Hooks
xml
<script setup lang="ts">
import { ref } from 'vue'
import useAdd from './hooks/useAdd'
import useSub from './hooks/useSub'
// 定义源数据
const num1 = ref(100)
const num2 = ref(200)
// 使用加法Hook
const { addNum, addFn } = useAdd({ num1: num1.value, num2: num2.value })
addFn(num1.value, num2.value)
console.log('加法结果:', addNum.value) // 300
// 使用减法Hook
const { subNum, subFn } = useSub({ num1: num2.value, num2: num1.value })
subFn(num2.value, num1.value)
console.log('减法结果:', subNum.value) // 100
</script>
五、Vue3 Hooks VS Vue2 Mixin(彻底秒杀)
在 Vue2 中,Mixin 是唯一的逻辑复用方案,但存在无法根治的硬伤;而 Vue3 自定义 Hooks 完美规避所有问题,是完全的降维替代。
1. Mixin 三大致命缺点
- 命名冲突严重:多个 Mixin 混入同一组件,变量、方法名极易冲突,且难以排查
- 传参能力缺失:无法向 Mixin 传递参数定制逻辑,复用灵活性极低
- 溯源困难、可读性差:组件中使用的 this 属性,无法判断来自哪一个 Mixin,维护成本极高
2. Vue3 自定义 Hooks 优势碾压
- 作用域隔离:Hook 拥有独立函数作用域,不会全局污染
- 溯源清晰:通过解构引入,变量方法来源一目了然
- 支持传参定制:可根据业务动态传参,复用性极强
- 完美解决命名冲突:同名变量可通过 ES6 解构重命名区分
冲突解决示例(Hook 独有能力)
若多个 Hook 返回同名变量,Mixin 直接冲突报错,Hook 可轻松解构重命名:
scss
// 两个Hook都返回同名 result,完全不冲突
const { result: addResult } = useAdd()
const { result: subResult } = useSub()
六、核心思想总结(高内聚低耦合)
1. Vue2 Options API 本质
碎片化、分散式代码结构,按代码类型分类(data、methods、watch),功能越复杂、代码越乱,高耦合难维护。
2. Vue3 Composition API + Hooks 本质
按业务功能分类,同一个功能的所有数据、逻辑、监听统一收拢,抽离为独立 Hook。
Mixin 是组件全局作用域(不可控、易污染)
自定义 Hooks 是组件函数作用域(隔离、安全、可控)
七、终极总结
Vue3 自定义 Hooks 不是新语法,而是优秀的代码架构思想:
- 彻底抛弃 Vue2 this 强耦合、碎片化烂代码写法
- 替代落后的 Mixin,解决命名冲突、溯源困难、复用死板问题
- 实现真正的高内聚、低耦合
- 让复杂项目代码整洁、优雅、可复用、易维护,真正实现「像写诗一样写代码」
八、企业级高频实战 Vue3 Hooks 合集(可直接落地)
前面通过加减运算案例讲解了 Hooks 的核心封装思想 ,本节补充实际开发中 90% 项目都会用到的通用 Hooks,全部为 TS 完整版、开箱即用,适配 Vue3 正式项目,无需二次改造。
1、通用防抖 Hook(useDebounce)
封装 Lodash 防抖逻辑,全局复用,适配搜索输入、窗口缩放、表单校验等高频场景,自带默认延迟、支持自定义配置。
javascript
// hooks/useDebounce.ts
import { debounce } from 'lodash'
import { onUnmounted } from 'vue'
/**
* 通用防抖Hook
* @param fn 防抖执行函数
* @param delay 延迟时间,默认300ms
* @returns 防抖处理后函数
*/
export default function useDebounce(fn: (...args: any[]) => void, delay = 300) {
// 返回防抖函数
const debounceFn = debounce(fn, delay)
// 手动取消防抖
const cancel = () => {
debounceFn.cancel()
}
// 组件自动销毁取消防抖,杜绝内存泄漏
onUnmounted(() => {
cancel()
})
return {
debounceFn,
cancel
}
}
组件使用示例
xml
<script setup lang="ts">
import { ref } from 'vue'
import useDebounce from '@/hooks/useDebounce'
const keyword = ref('')
// 搜索请求逻辑
const search = (val: string) => {
console.log('搜索关键词:', val)
}
// 包裹防抖
const { debounceFn } = useDebounce(search, 400)
</script>
<template>
<input v-model="keyword" @input="debounceFn(keyword)" placeholder="防抖搜索" />
</template>
2、通用节流 Hook(useThrottle)
封装 Lodash 节流逻辑,适配页面滚动、按钮防重、拖拽、窗口缩放等高频持续触发事件,有效提升页面性能。
javascript
// hooks/useThrottle.ts
import { throttle } from 'lodash'
import { onUnmounted } from 'vue'
/**
* 通用节流Hook
* @param fn 节流执行函数
* @param delay 间隔时间,默认300ms
* @returns 节流函数 + 取消方法
*/
export default function useThrottle(fn: (...args: any[]) => void, delay = 300) {
const throttleFn = throttle(fn, delay)
// 手动取消节流
const cancel = () => {
throttleFn.cancel()
}
// 组件销毁自动取消,防止内存泄漏
onUnmounted(() => {
cancel()
})
return {
throttleFn,
cancel
}
}
3、弹窗通用 Hook(useModal)
项目最高频复用逻辑,统一管理弹窗显示/隐藏、重置状态,告别每个组件重复写 visible 变量,极简代码。
javascript
// hooks/useModal.ts
import { ref } from 'vue'
/**
* 弹窗通用状态管理
*/
export default function useModal() {
// 弹窗显示状态
const visible = ref(false)
// 打开弹窗
const openModal = () => {
visible.value = true
}
// 关闭弹窗
const closeModal = () => {
visible.value = false
}
return {
visible,
openModal,
closeModal
}
}
组件极简使用
xml
<script setup lang="ts">
import useModal from '@/hooks/useModal'
// 直接解构即用,无需重复定义变量
const { visible, openModal, closeModal } = useModal()
</script>
<template>
<button @click="openModal">打开弹窗</button>
<div v-if="visible">
弹窗内容区域
<button @click="closeModal">关闭</button>
</div>
</template>
4、通用分页 Hook(usePagination)
封装后台系统通用分页逻辑,包含页码、页长、总数、重置分页、页码切换,后台管理系统必备,全项目通用。
javascript
// hooks/usePagination.ts
import { reactive } from 'vue'
/**
* 通用分页参数管理
* @param pageSize 默认页长
*/
export default function usePagination(pageSize = 10) {
// 分页响应式参数
const pagination = reactive({
page: 1,
pageSize,
total: 0
})
// 重置分页为第一页
const resetPage = () => {
pagination.page = 1
}
// 修改总数
const setTotal = (val: number) => {
pagination.total = val
}
return {
pagination,
resetPage,
setTotal
}
}
5、异步请求通用 Hook(useRequest)
封装接口请求通用逻辑,统一管理 loading、数据、错误、请求刷新,解决每个组件重复写 loading 状态的问题,企业级标准封装。
javascript
// hooks/useRequest.ts
import { ref, onUnmounted } from 'vue'
/**
* 通用异步请求封装
* @param api 请求Promise函数
*/
export default function useRequest<T>(api: () => Promise<T>) {
const data = ref<T | null>(null)
const loading = ref(false)
const error = ref<Error | null>(null)
// 标记是否取消请求
let isCancel = false
// 发起请求
const run = async () => {
// 重置取消状态
isCancel = false
loading.value = true
error.value = null
try {
const res = await api()
// 页面销毁则不赋值,防止异步回调报错
if (!isCancel) {
data.value = res
}
} catch (err) {
if (!isCancel) {
error.value = err as Error
}
} finally {
if (!isCancel) {
loading.value = false
}
}
}
// 手动取消请求
const cancel = () => {
isCancel = true
loading.value = false
}
// 组件销毁自动取消请求,防止页面卸载后依然执行回调
onUnmounted(() => {
cancel()
})
return {
data,
loading,
error,
run,
cancel
}
}
业务组件使用示例
scss
// 模拟接口
const getList = () => Promise.resolve([1,2,3,4])
// 使用请求Hook
const { data, loading, run } = useRequest(getList)
// 页面加载自动请求
run()
九、Vue3 Hooks 开发黄金规范(必看避坑)
- 纯逻辑抽离:Hook 只处理业务逻辑、数据、状态,不操作 DOM、不写模板相关代码
- 单一职责原则:一个 Hook 只做一件事,例如 useModal 只管理弹窗,不掺杂其他逻辑
- 统一命名规范:所有自定义 Hook 必须 use 开头,语义化命名,见名知意
- 必须主动取消监听:包含定时器、节流、防抖、全局事件的 Hook,组件销毁必须 cancel / 移除监听,杜绝内存泄漏
- 支持传参、可配置:通用 Hook 尽量支持参数自定义,提升复用场景,不写死逻辑
- TS 强类型约束:正式项目全部采用 TS 写法,规范入参、出参类型,减少线上 bug
十、最终总结
Vue3 自定义 Hooks 是Vue3 项目架构升级的核心,也是区分初级前端与中高级前端的重要标准:
- 告别 Vue2 碎片化、高耦合、难维护的 Options API 烂代码
- 彻底替代 Mixin,解决命名冲突、溯源困难、复用僵硬等痛点
- 通过高内聚低耦合的设计,让逻辑复用更优雅、项目架构更清晰
- 以上通用 Hooks 覆盖 90% 日常开发场景,可直接搭建项目通用 Hooks 库