👉 👉 Vue3 自定义 Hook:从入门到进阶(~~安静的阅读2分钟,相信我,这篇文章一定能给你启发)

在 Vue3 中,组合式 API(Composition API)让逻辑复用变得更加自然。而 自定义 Hook(组合式函数) 正是其中的核心:它们像 React Hook 一样,把逻辑抽离成独立函数,在多个组件中复用。

Hook 离我们并不远,而且一点也不复杂。本文将从浅到深理解自定义 Hook,并通过四个层次的案例,以及通用 Hook 模板,让你快速掌握它的用法。

👉 花 2 分钟安静阅读,相信我,这篇文章一定能带来启发。~~

为什么需要自定义 Hook?

在 Vue 2.x 的 Options API 中,逻辑往往分散在 datamethodswatch 等配置项里。想要复用时只能依赖 Mixin,但 Mixin 存在不少问题:

  • 来源不清晰,变量和方法从哪来?需要翻文件。
  • 多个 Mixin 混用时容易冲突,逻辑也难追踪。
  • 对 TypeScript 不友好,类型推导混乱。

来看一个例子:

Vue2.x Mixin 写法

jsx 复制代码
// counterMixin.js
export const counterMixin = {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
html 复制代码
<template>
  <div>
    <p>count: {{ count }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

<script>
import { counterMixin } from './counterMixin'

export default {
  mixins: [counterMixin]
}
</script>

这里你可能会疑惑:countincrement 从哪里来的?要去看 mixin 文件才能搞明白。

Vue3 Hook 写法

jsx 复制代码
// useCounter.ts
import { ref } from 'vue'

export function useCounter(initial = 0) {
  const count = ref(initial)
  const increment = () => count.value++

  return { count, increment }
}
jsx 复制代码
<script setup lang="ts">
import { useCounter } from './useCounter'

const { count, increment } = useCounter()
</script>

<template>
  <div>
    <p>count: {{ count }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

相比之下:

  • useCounter 一看就知道逻辑来源。
  • 不会命名冲突。
  • 对 TypeScript 友好,类型推导清晰。

下面,我们就通过 四个层次的案例,逐步感受 Hook 的魅力。

四个层次的案例

一、最简单的 Hook:鼠标跟随

比如我们想要在多个组件中实现 鼠标跟随效果

jsx 复制代码
// useMouse.ts
import { ref, onMounted, onUnmounted } from 'vue'

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

  function update(e: MouseEvent) {
    x.value = e.pageX
    y.value = e.pageY
  }

  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  return { x, y }
}

在组件中使用:

jsx 复制代码
<script setup lang="ts">
import { useMouse } from '@/hooks/useMouse'

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

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

这样,一个简单的逻辑就能在多个地方复用。

二、带参数的 Hook:倒计时

有时候 Hook 需要支持配置,比如 倒计时

jsx 复制代码
// useCountdown.ts
import { ref, onUnmounted } from 'vue'

export function useCountdown(initial: number = 60, interval = 1000) {
  const time = ref(initial)
  let timer: number | null = null

  function start() {
    if (timer) return
    timer = setInterval(() => {
      if (time.value > 0) {
        time.value--
      } else {
        stop()
      }
    }, interval) as unknown as number
  }

  function stop() {
    if (timer) {
      clearInterval(timer)
      timer = null
    }
  }

  onUnmounted(stop)

  return { time, start, stop }
}

使用:

jsx 复制代码
<script setup lang="ts">
import { useCountdown } from '@/hooks/useCountdown'

const { time, start, stop } = useCountdown(10)
</script>

<template>
  <div>
    倒计时:{{ time }}
    <button @click="start">开始</button>
    <button @click="stop">停止</button>
  </div>
</template>

👉 这就是 参数化能力:更灵活、更通用。

三、结合外部依赖:监听窗口大小

Hook 也可以对接外部依赖库。

比如监听 窗口大小,在图表自适应、响应式布局中很常用:

jsx 复制代码
// useWindowSize.ts
import { ref, onMounted, onUnmounted } from 'vue'

export function useWindowSize() {
  const width = ref(window.innerWidth)
  const height = ref(window.innerHeight)

  function update() {
    width.value = window.innerWidth
    height.value = window.innerHeight
  }

  onMounted(() => window.addEventListener('resize', update))
  onUnmounted(() => window.removeEventListener('resize', update))

  return { width, height }
}

这类 Hook 常见于 UI 框架,我们也能轻松实现。

四、进阶:组合 Hook

Hook 本身也能 组合 ,像乐高一样拼接逻辑。

比如结合 useMouseuseWindowSize,计算 鼠标相对窗口的百分比位置

jsx 复制代码
// useMousePercent.ts
import { computed } from 'vue'
import { useMouse } from './useMouse'
import { useWindowSize } from './useWindowSize'

export function useMousePercent() {
  const { x, y } = useMouse()
  const { width, height } = useWindowSize()

  const percentX = computed(() => (x.value / width.value) * 100)
  const percentY = computed(() => (y.value / height.value) * 100)

  return { percentX, percentY }
}

👉 这就是组合的威力:小 Hook 拼成大逻辑。

通用 Hook 模板(开箱即用)

讲到这里,相信你已经大概掌握了写 Hook 的套路。🙌

那问题来了:真正写的时候从哪下手?

别担心,我准备了一个 万能 Hook 模板,直接拷贝就能跑,改一改就是你的专属逻辑~

jsx 复制代码
// useTemplate.ts
import { ref, onMounted, onUnmounted } from 'vue'

/**
 * 通用 Hook 模板
 * @param options 可选参数配置
 */
export function useTemplate(options?: { initial?: number }) {
  // 1. 定义状态
  const state = ref(options?.initial ?? 0)

  // 2. 定义逻辑方法
  function start() {
    console.log('start...')
  }

  function stop() {
    console.log('stop...')
  }

  // 3. 生命周期绑定
  onMounted(() => {
    console.log('Hook 已挂载')
  })

  onUnmounted(() => {
    console.log('Hook 已卸载')
  })

  // 4. 返回值(状态 + 方法)
  return {
    state,
    start,
    stop,
  }
}

使用方式:

html 复制代码
<script setup lang="ts">
import { useTemplate } from '@/hooks/useTemplate'

const { state, start, stop } = useTemplate({ initial: 10 })
</script>

<template>
  <div>
    <p>当前状态:{{ state }}</p>
    <button @click="start">开始</button>
    <button @click="stop">停止</button>
  </div>
</template>

"你会怎么改造这个模板?欢迎在评论区分享你写过的 Hook!"

最佳实践与注意事项

  1. 命名规范 :一般以 useXxx 命名,直观易懂。保持函数职责单一,避免过度臃肿。
  2. 与组件生命周期解耦 :Hook 内部可使用 onMounted / onUnmounted,确保资源正确释放。
  3. 可组合性优先:小而精的 Hook 更容易被组合和复用。
  4. 工具库推荐 :社区已有大量高质量 Hook,如 VueUse,覆盖了些常见场景(网络、DOM、状态管理等),能复用就别重复造轮子。

总结

自定义 Hook 是 Vue3 中逻辑复用的核心方式。

从最简单的逻辑抽离,到参数化,再到组合与工具化,都体现了它的灵活性和可维护性。实际项目里,Hook 不仅能让代码更清晰,还能逐渐沉淀出 逻辑库

希望对你有所帮助、有所借鉴。你有什么想法或者问题,欢迎在评论区一起讨论。

相关推荐
南雨北斗4 小时前
Vue3 v-html的用法
前端
爱学习的茄子4 小时前
Function Call:让AI从文本生成走向智能交互的技术革命
前端·深度学习·openai
aol1214 小时前
X6官方示例「数据加工DAG图」转为Vue版
前端·vue.js
南雨北斗4 小时前
vue3 attribute绑定
前端
一枚前端小能手4 小时前
🚀 主线程卡死用户要骂娘?Web Worker让你的应用丝滑如德芙
前端·javascript
小桥风满袖4 小时前
极简三分钟ES6 - Promise
前端·javascript
breeze_whisper4 小时前
当前端收到一个比梦想还大的数字:BigInt处理指南
前端·面试
小喷友4 小时前
阶段四:实战(项目开发能力)
前端·rust
小高0074 小时前
性能优化零成本:只加3行代码,FCP从1.8s砍到1.2s
前端·javascript·面试