Skip to content

自定义 Hooks

在 Vue3 的组合式 API 中,自定义 Hooks 是一种非常强大的特性,它允许我们封装和复用逻辑代码,提高代码的可维护性和可复用性。

什么是自定义 Hooks?

自定义 Hooks 是使用组合式 API 创建的可复用函数,它可以封装任何逻辑,包括响应式数据、生命周期钩子、副作用等。

自定义 Hooks 的命名规范

  • 通常以 use 开头,如 useCounteruseApi
  • 函数名使用驼峰命名法
  • 文件名通常与函数名相同

基本用法

创建自定义 Hook

javascript
// hooks/useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const doubleCount = computed(() => count.value * 2)

  function increment() {
    count.value++
  }

  function decrement() {
    count.value--
  }

  function reset() {
    count.value = initialValue
  }

  return {
    count,
    doubleCount,
    increment,
    decrement,
    reset
  }
}

使用自定义 Hook

vue
<script setup>
import { useCounter } from './hooks/useCounter'

// 使用自定义 Hook
const { count, doubleCount, increment, decrement, reset } = useCounter(10)
</script>

<template>
  <div>
    <p>当前计数: {{ count }}</p>
    <p>双倍计数: {{ doubleCount }}</p>
    <button @click="increment">增加</button>
    <button @click="decrement">减少</button>
    <button @click="reset">重置</button>
  </div>
</template>

实战示例

1. 封装 API 请求

javascript
// hooks/useApi.js
import { ref, onUnmounted } from 'vue'

export function useApi(url) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  let controller = null

  async function fetchData() {
    loading.value = true
    error.value = null

    try {
      controller = new AbortController()
      const response = await fetch(url, {
        signal: controller.signal
      })
      
      if (!response.ok) {
        throw new Error('请求失败')
      }

      data.value = await response.json()
    } catch (err) {
      if (err.name !== 'AbortError') {
        error.value = err.message
      }
    } finally {
      loading.value = false
    }
  }

  function cancel() {
    if (controller) {
      controller.abort()
    }
  }

  // 组件卸载时取消请求
  onUnmounted(() => {
    cancel()
  })

  return {
    data,
    loading,
    error,
    fetchData,
    cancel
  }
}

使用示例:

vue
<script setup>
import { useApi } from './hooks/useApi'

const { data, loading, error, fetchData } = useApi('https://api.example.com/data')

// 组件挂载时获取数据
fetchData()
</script>

<template>
  <div>
    <div v-if="loading">加载中...</div>
    <div v-else-if="error">{{ error }}</div>
    <div v-else-if="data">
      <pre>{{ JSON.stringify(data, null, 2) }}</pre>
    </div>
  </div>
</template>

2. 封装本地存储

javascript
// hooks/useLocalStorage.js
import { ref, watch } from 'vue'

export function useLocalStorage(key, initialValue) {
  // 从本地存储中获取初始值
  const storedValue = localStorage.getItem(key)
  const value = ref(storedValue ? JSON.parse(storedValue) : initialValue)

  // 监听值的变化,同步到本地存储
  watch(value, (newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue))
  }, { deep: true })

  return value
}

使用示例:

vue
<script setup>
import { useLocalStorage } from './hooks/useLocalStorage'

// 使用本地存储
const userPreferences = useLocalStorage('userPreferences', {
  theme: 'light',
  fontSize: 16
})

function toggleTheme() {
  userPreferences.value.theme = userPreferences.value.theme === 'light' ? 'dark' : 'light'
}
</script>

<template>
  <div :class="userPreferences.theme">
    <p>当前主题: {{ userPreferences.theme }}</p>
    <p>字体大小: {{ userPreferences.fontSize }}px</p>
    <button @click="toggleTheme">切换主题</button>
  </div>
</template>

<style>
.light {
  background-color: #fff;
  color: #333;
}

.dark {
  background-color: #333;
  color: #fff;
}
</style>

3. 封装鼠标位置

javascript
// hooks/useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  function updateMousePosition(event) {
    x.value = event.clientX
    y.value = event.clientY
  }

  onMounted(() => {
    window.addEventListener('mousemove', updateMousePosition)
  })

  onUnmounted(() => {
    window.removeEventListener('mousemove', updateMousePosition)
  })

  return { x, y }
}

使用示例:

vue
<script setup>
import { useMouse } from './hooks/useMouse'

const { x, y } = useMouse()
</script>

<template>
  <div>
    <p>鼠标位置: ({{ x }}, {{ y }})</p>
  </div>
</template>

自定义 Hooks 的优势

  1. 代码复用:将重复的逻辑封装到一个函数中,提高代码的可复用性
  2. 逻辑清晰:将相关的逻辑组织在一起,提高代码的可读性和可维护性
  3. 关注点分离:将业务逻辑与 UI 逻辑分离,使组件更加简洁
  4. 类型安全:在 TypeScript 中可以为自定义 Hooks 添加类型定义,提高代码的类型安全性

最佳实践

  • 单一职责:每个自定义 Hook 应该只负责一个功能
  • 命名规范:使用 use 前缀,清晰表达 Hook 的用途
  • 返回值:返回与 Hook 功能相关的响应式数据和方法
  • 清理副作用:在 Hook 中使用 onUnmounted 等生命周期钩子清理副作用
  • 文档:为复杂的自定义 Hook 添加适当的文档说明

通过自定义 Hooks,我们可以更好地组织和复用代码,提高开发效率和代码质量。

© 2026 编程马·菜鸟教程 版权所有