这一节了解一下Vue3中的自定义Hook,它也叫组合式函数,本质就是抽离出来、可复用的setup逻辑函数,约定以use开头命名,简单总结如下:
自定义Hook 本身不是 API,但是内部一般会使用下面这些Vue API:
-
ref:定义基础响应式数据
-
reactive:对象类型响应式
-
computed:计算属性
-
watch/watchEffect:监听数据变化、执行副作用
-
onMounted/onUnmounted:生命周期钩子(在Hook内部注册,组件销毁自动清理)
场景
1 通用DOM 交互:useMouse、useClickOutside、useScroll
2 异步请求封装:useRequest
3 定时器、事件监听
栗子:
TypeScript
<template>
<div>
<h3>鼠标坐标Hook</h3>
<p>X:{{ x }}</p>
<p>Y:{{ y }}</p>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
// 自定义Hook:useMouse
function useMouse() {
const x = ref(0)
const y = ref(0)
function handleMousemove(e) {
x.value = e.clientX
y.value = e.clientY
}
onMounted(() => {
window.addEventListener('mousemove', handleMousemove)
})
onUnmounted(() => {
window.removeEventListener('mousemove', handleMousemove)
})
return { x, y }
}
// 在组件中使用Hook
const { x, y } = useMouse()
</script>
Demo02
TypeScript
<template>
<div>
<h3>useRequest 异步请求Hook</h3>
<button @click="run">发起请求</button>
<button @click="retry" :disabled="!error">重试</button>
<p v-if="loading">加载中...</p>
<p v-if="error" style="color:red">错误:{{ error.message }}</p>
<p v-if="data">返回数据:{{ data }}</p>
</div>
</template>
<script setup>
import { ref, onUnmounted } from 'vue'
function useRequest(apiFn) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
let abortController = null
async function run() {
if (abortController) {
abortController.abort()
}
abortController = new AbortController()
loading.value = true
error.value = null
try {
const res = await apiFn(abortController.signal)
data.value = res
} catch (err) {
if (err.name !== 'AbortError') {
error.value = err
}
} finally {
loading.value = false
}
}
function retry() {
run()
}
onUnmounted(() => {
if (abortController) {
abortController.abort()
}
})
return {
data,
loading,
error,
run,
retry
}
}
// 模拟接口函数
function mockApi(signal) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
resolve({ name: "张三", age: 20, id: 1001 })
}, 1500)
signal.addEventListener('abort', () => {
clearTimeout(timer)
})
})
}
// 使用Hook
const { data, loading, error, run, retry } = useRequest(mockApi)
</script>
注意
1 Hook内的生命周期,归属调用它的组件;组件销毁,Hook里面的onUnmounted自动执行
2 每次组件调用useXxx()都会生成独立的状态副本(互不干扰);想要全局共享状态,需要单独抽离一份reactive/ref放到Hook外面
3 副作用(监听、定时器)一定要在onUnmounted清理,防止内存泄漏
4 尽量返回ref/toRefs,保证解构出来的值保持响应式