文章目录
一、什么是Hook?
1. 技术本质
Hook是包含响应式状态和逻辑组合的函数。其核心特征是:
- 使用
ref / reactive
创建响应式状态 - 包含
watch / computed
等响应式操作 - 可以调用生命周期钩子
- 返回可复用的状态和方法集合
注意: Vue官方文档中并没有直接称为“hook”,而是使用“组合式函数”(Composable)这个术语。组合式函数的作用是封装和复用有状态的逻辑,类似于React Hooks,但实现方式不同。
2. 与工具函数的区别
// 普通工具函数(无状态)
function formatDate(date) {
return date.toISOString()
}
// Hook 函数(有状态)
function useCounter(initial = 0) {
const count = ref(initial)
const double = computed(() => count.value * 2)
return {
count, douuble }
}
二、Hook存在的意义
1. 解决传统模式的三大痛点
痛点 | Options API | Hook方案 |
---|---|---|
逻辑碎片化 | 分散在 data / methods | 按功能聚合 |
复用困难 | Mixins存在冲突风险 | 函数组合零冲突 |
类型支持弱 | 类型推断受限 | 完整TS类型推导 |
2. 核心优势矩阵
- 逻辑复用率: 提升300%+(同一Hook多出使用)
- 代码可维护性:功能模块化,降低认知负荷
- 类型系统支持:完整TypeScript 类型推导链
- 测试便利性:独立于组件的单元测试能力
三、开发实践指南
1. 基础创建模式
// src/composables/useMousePosition.js
import {
onMounted, onUnmounted, ref } from 'vue'
export default function useMousePosition() {
const x = ref(0)
const y = ref(0)
const update = (e) => {
x.value = e.clientX
y.value = e.clinentY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return {
x, y } // 返回响应式引用
}
2. 组件内使用
<script setup>
import useMousePosition from '@/composables/useMousePosition'
const {
x, y } = useMousePosition()
</script>
<template>
<div>鼠标坐标:{
{ x }}, {
{ y }}</div>
</template>
四、最佳实践
1. 复杂 Hook 结构
处理分页数据请求
// src/composables/usePaginatedFetch.ts
import {
ref, watchEffect } from 'vue'
// 定义配置类型(泛型T表示数据类型)
type PaginationConfig<T> = {
url: string; // API端点
pageSize: number; // 每页数据量
initialData?: T[]; // 初始数据(可选)
}
export default function usePaginatedFetch<T>(config: PaginationConfig<T>) {
// 响应式状态初始化
const data = ref<T[]>(config.initialData || []); // 数据存储
const currentPage = ref(1); // 当前页码
const isLoading = ref(false); // 加载状态
const error = ref<Error | null>(null); // 错误信息
// 核心数据获取方法
const fetchData = async () => {
try {
isLoading.value = true;
// 动态拼接请求URL
const response = await fetch(
`${
config.url}?page=${
currentPage.value}&size=${
config.pageSize}`
);
data.value = await response.json();
} catch (err) {
error.value = err as Error; // 类型断言
} finally {
isLoading.value = false;
}
};
// 自动追踪依赖变化
watchEffect(() => {
fetchData(); // 当currentPage或pageSize变化时自动执行
});
return {
data, // 当前页数据(Ref<T[]>)
currentPage, // 当前页码(Ref<number>)
isLoading, // 加载状态(Ref<boolean>)
error, // 错误对象(Ref<Error | null>)
nextPage: () => currentPage.value++, // 下一页方法
prevPage: () => currentPage.value--, // 上一页方法
};
}
使用场景示例
<template>
<div v-if="isLoading">加载中...</div>
<div v-else-if="error">错误:{
{ error.message }}</div>
<div v-else>
<ul>
<li v-for="item in data" :key="item.id">{
{ item.name }}</li>
</ul>
<button @click="prevPage" :disabled="currentPage === 1">上一页</button>
<span>第 {
{ currentPage }} 页</span>
<button @click="nextPage">下一页</button>
</div>
</template>
<script setup lang="ts">
import usePaginatedFetch from './usePaginatedFetch';
interface User {
id: number;
name: string;
email: string;
}
// 使用Hook
const {
data, currentPage, isLoading, error, nextPage, prevPage } = usePaginatedFetch<User>({
url: '/api/users',
pageSize: 10,
initialData: [] // 可选初始数据
});
</script>
2. 类型安全增强
// 使用泛型约束数据类型
interface User {
id: number
name: string
}
const {
data: users } = usePaginatedFetch<User>({
url: '/api/users',
pageSize: 10
})
// users 自动推导为 Ref<User[]>
五、应用场景
1. 状态共享方案
// useSharedState.ts
import {
reactive } from 'vue'
const globalState = reactive({
user: null as User | null,
theme: 'light'
})
export default function useSharedState() {
return {
get user() {
return globalState.user
},
set user(value: User | null) {
globalState.user = value
},
toggleTheme() {
globalState.theme = globalState.theme === 'light' ? 'dark' : 'light'
}
}
}
2. 跨组件通信
// useEventBus.ts
import {
getCurrentInstance } from 'vue'
type EventHandler<T = any> = (payload: T) => void
export default function useEventBus() {
const internalInstance = getCurrentInstance()
if (!internalInstance) throw new Error('必须在 setup 中使用')
const emitter = internalInstance.appContext.config.globalProperties.emitter
return {
emit(event: string, payload?: any) {
emitter.emit(event, payload)
},
on(event: string, handler: EventHandler) {
emitter.on(event, handler)
},
off(event: string, handler?: EventHandler) {
emitter.off(event, handler)
}
}
}
六、性能优化策略
1. 副作用管理
import {
effectScope } from 'vue'
export function useComplexHook() {
const scope = effectScope()
scope.run(() => {
// 所有响应式操作在此作用域内
const state = reactive({
/*...*/ })
watch(/*...*/)
})
// 组件卸载时自动清理
onUnmounted(() => scope.stop())
return {
/*...*/ }
}
2. 惰性加载 Hook
// 按需加载示例
const useHeavyHook = () => import('./heavyHook')
async function loadWhenNeeded() {
const {
default: heavyHook } = await useHeavyHook()
const data = heavyHook()
}
七、调试技巧
1. 开发工具追踪
// 添加调试标记
function useDebugHook() {
const state = ref(0)
// 添加自定义属性
state.value.__DEBUG_ID = 'CUSTOM_HOOK'
return {
state }
}
2. 控制台检查
// 在 Chrome DevTools 中检查
const {
x, y } = useMousePosition()
console.log({
x, y }) // 查看 Ref 对象内部值
八、应用案例
1. 数据可视化 Hook
// useChart.ts
export default function useChart(containerRef: Ref<HTMLDivElement | undefined>) {
const chartInstance = ref<echarts.ECharts | null>(null)
onMounted(() => {
if (containerRef.value) {
chartInstance.value = echarts.init(containerRef.value)
}
})
const updateData = (data: ChartData) => {
chartInstance.value?.setOption({
dataset: {
source: data },
// ...其他配置
})
}
return {
updateData }
}
2. 微前端状态同步
// useMicroFrontendState.ts
export function useMicroFrontendState<T>(key: string, initial: T) {
const state = ref<T>(initial)
// 跨应用状态同步
window.addEventListener('micro-frontend-event', (e: CustomEvent) => {
if (e.detail.key === key) {
state.value = e.detail.value
}
})
watch(state, (newVal) => {
window.dispatchEvent(
new CustomEvent('micro-frontend-update', {
detail: {
key, value: newVal }
})
)
})
return {
state }
}
九、总结
Hook模式将前端开发带入逻辑即服务(Logic-as-a-Service)的新纪元,其核心价值在于:
- 原子化:每个Hook都是独立的功能单元
- 可组合:通过函数组合实现复杂功能
- 可测试:脱离 UI 实现纯逻辑验证
- 类型化:完整的 TypeScript 支持链路
掌握 Hook 开发能力,意味着能够以工业级标准构建可维护、可扩展的前端架构,这是现代 Vue 开发者必须掌握的核心竞争力。