Vue3的Hook指南

一、什么是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)的新纪元,其核心价值在于:

  1. 原子化:每个Hook都是独立的功能单元
  2. 可组合:通过函数组合实现复杂功能
  3. 可测试:脱离 UI 实现纯逻辑验证
  4. 类型化:完整的 TypeScript 支持链路

掌握 Hook 开发能力,意味着能够以工业级标准构建可维护、可扩展的前端架构,这是现代 Vue 开发者必须掌握的核心竞争力。