一、完整代码示例。
1. 自定义 Hook:hooks/useMousePosition.js
javascript
import { reactive, onMounted, onUnmounted } from 'vue'
/*
hook本质:
一个普通函数,内部利用Vue3的reactive、onMounted等API封装"响应式数据+逻辑+生命周期"
最后返回需要暴露的数据或方法。
hook好处:
逻辑复用:如果多个组件都需要 "监听鼠标位置",只需调用 useMousePosition() 即可,无需重复写监听代码。
逻辑集中:比Vue 的mixin更清晰,避免命名冲突。
响应式联动:hook可以返回响应式对象供其他组件使用
*/
// 自定义 hook:命名通常以 use 开头
export default function() {
// 1. 响应式数据:存储鼠标坐标
const mouse = reactive({
x: 0,
y: 0
})
// 2. 方法:更新鼠标坐标
function updatePosition(e) {
mouse.x = e.pageX
mouse.y = e.pageY
}
// 3. 生命周期钩子:组件挂载后开始监听
onMounted(() => {
window.addEventListener('mousemove', updatePosition)
})
// 4. 生命周期钩子:组件卸载前停止监听(避免内存泄漏)。原生事件自行移除
onUnmounted(() => {
window.removeEventListener('mousemove', updatePosition)
})
// 5. 暴露响应式数据(供组件使用)
return mouse
}
2. 组件使用:App.vue
在组件中引入并调用自定义 Hook,无需关心内部实现,直接使用响应式的鼠标坐标。
html
<template>
<div>
鼠标位置:x={{ mouse.x }}, y={{ mouse.y }}
</div>
</template>
<script>
// 引入自定义 hook
import useMousePosition from './hooks/useMousePosition'
export default {
name:'App',
setup(){
//useMousePosition()用于获取响应式对象,同时其内部的生命周期函数会被绑定当前vc实例上
const mouse = useMousePosition()
return {
mouse
}
}
}
</script>
二、代码解析
1. 自定义 Hook 核心逻辑(useMousePosition.js
)
(1)响应式数据定义
javascript
const mouse = reactive({ x: 0, y: 0 })
- 使用
reactive
创建响应式对象:Vue3 会通过 Proxy 代理该对象,当mouse.x
/mouse.y
变化时,自动触发依赖更新(如模板重新渲染)。 - 数据作用域:
mouse
是 Hook 内部变量,仅通过返回值暴露给组件,避免全局污染。
(2)事件处理函数
javascript
function updatePosition(e) {
mouse.x = e.pageX
mouse.y = e.pageY
}
- 接收浏览器
mousemove
事件对象e
,获取pageX
/pageY
(鼠标相对于文档左上角的坐标)并更新响应式数据。 - 该函数是 Hook 内部逻辑,不暴露给组件,实现「封装细节、暴露结果」的设计。
(3)生命周期钩子:绑定与清理事件
javascript
// 组件挂载后绑定事件
onMounted(() => {
window.addEventListener('mousemove', updatePosition)
})
// 组件卸载前清理事件
onUnmounted(() => {
window.removeEventListener('mousemove', updatePosition)
})
- 为什么在
onMounted
绑定?
组件挂载后(onMounted
),DOM 已就绪,此时绑定全局事件(window
上的mousemove
)不会丢失,且能确保事件监听与组件生命周期同步。 - 为什么必须在
onUnmounted
清理?
addEventListener
是浏览器原生事件,Vue 不会自动解绑。若不清理,组件卸载后事件仍会触发,导致回调函数引用的组件实例无法被垃圾回收,造成内存泄漏。
(4)返回响应式数据
javascript
return mouse
- 仅将需要的响应式数据暴露给组件,隐藏
updatePosition
等内部函数,符合「最小暴露原则」,降低组件与 Hook 的耦合度。
2. 组件使用逻辑(App.vue
)
(1)引入与调用 Hook
javascript
import useMousePosition from './hooks/useMousePosition'
setup() {
const mouse = useMousePosition()
return { mouse }
}
- 在
setup
中调用 Hook:setup
是 Vue3 组件的「逻辑入口」,此时调用 Hook,Hook 内部的生命周期钩子会自动绑定到当前组件实例(即App
组件),确保钩子能按组件生命周期触发。 - 返回数据给模板:
mouse
是响应式对象,模板中引用mouse.x
/mouse.y
时,会自动建立依赖,鼠标移动时模板实时更新。
(2)模板渲染
html
<p class="coordinate">x: {{ mouse.x }}px</p>
<p class="coordinate">y: {{ mouse.y }}px</p>
- 无需编写任何监听逻辑,直接使用 Hook 返回的响应式数据,实现「数据驱动视图」的 Vue 核心思想。
三、自定义 Hook 的优势(对比传统写法)
假设不用 Hook,直接在组件中写监听逻辑,代码会是这样:
javascript
// 无 Hook 的组件写法(冗余、难复用)
setup() {
const mouse = reactive({ x: 0, y: 0 })
function updatePosition(e) {
mouse.x = e.pageX
mouse.y = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', updatePosition)
})
onUnmounted(() => {
window.removeEventListener('mousemove', updatePosition)
})
return { mouse }
}
若有 3 个组件需要监听鼠标位置,上述代码要复制 3 次,而用 Hook 只需调用 useMousePosition()
即可。对比之下,Hook 的优势显而易见:
- 逻辑复用:一份代码,多组件共享,减少重复开发。
- 代码解耦:组件只关心「使用数据」,Hook 关心「如何获取数据」,职责分明。
- 避免冲突 :相比 Vue2 的
mixin
,Hook 不会出现命名冲突(数据和方法作用域独立)。
四、扩展:增强 Hook 功能
我们可以轻松扩展这个 Hook 的功能,比如添加「重置鼠标坐标」的方法,让组件能主动控制 Hook 内部逻辑:
javascript
// 增强版 useMousePosition.js
export default function useMousePosition() {
const mouse = reactive({ x: 0, y: 0 })
function updatePosition(e) {
mouse.x = e.pageX
mouse.y = e.pageY
}
// 新增:重置鼠标坐标的方法(暴露给组件)
function resetPosition() {
mouse.x = 0
mouse.y = 0
}
onMounted(() => {
window.addEventListener('mousemove', updatePosition)
})
onUnmounted(() => {
window.removeEventListener('mousemove', updatePosition)
})
// 返回数据 + 方法
return { mouse, resetPosition }
}
组件中使用新增方法:
javascript
<template>
<!-- 新增重置按钮 -->
<button @click="resetPosition">重置坐标</button>
</template>
<script>
setup() {
// 获取方法并返回给模板
const { mouse, resetPosition } = useMousePosition()
return { mouse, resetPosition }
}
</script>
五、总结
- Vue3 自定义 Hook 本质:普通函数 + Composition API(响应式 API + 生命周期钩子),封装可复用逻辑。
- 核心规范 :以
use
开头命名,仅暴露必要的接口,隐藏内部实现。 - 适用场景 :任何需要复用的逻辑(如请求数据
useFetch
、定时器useTimer
、本地存储useLocalStorage
等)。