文章目录
-
-
- [一、hooks 文件夹适合存放的文件类型](#一、hooks 文件夹适合存放的文件类型)
- [二、hooks 文件的命名规范(行业通用)](#二、hooks 文件的命名规范(行业通用))
- [三、hooks 文件的目录结构示例](#三、hooks 文件的目录结构示例)
- [四、hooks 文件的编写 & 引用示例](#四、hooks 文件的编写 & 引用示例)
-
- [示例1:通用型 hook(本地存储封装 - useStorage.js)](#示例1:通用型 hook(本地存储封装 - useStorage.js))
- [示例2:业务型 hook(用户登录状态 - useUser.js)](#示例2:业务型 hook(用户登录状态 - useUser.js))
- [示例3:通用请求 hook(useRequest.js)](#示例3:通用请求 hook(useRequest.js))
- 五、进阶技巧
- 六、知识总结
-
在 Vue3 中, hooks 文件夹是 组合式函数(Composables) 的核心存放目录,用于抽离复用的业务逻辑/通用功能,遵循「高内聚、低耦合」的设计原则。下面我会从 文件类型、命名规范、引用方式 三个维度详细讲解,并给出可直接复用的示例。

一、hooks 文件夹适合存放的文件类型
hooks 文件夹专门存放以 use 开头的组合式函数,核心是抽离组件中可复用的逻辑,常见类型包括:
- 通用功能型:如防抖、节流、时间格式化、本地存储操作等;
- 业务逻辑型:如用户登录状态、商品列表请求、表单验证等;
- 交互型:如滚动监听、窗口尺寸监听、鼠标事件处理等;
- API 请求型:封装通用的接口请求逻辑(如列表分页请求、详情请求)。
❌ 不适合放的文件:
- 组件(
.vue文件,应放components文件夹); - 工具函数(纯函数如
utils/format.js,应放utils文件夹); - 全局配置/常量(应放
config/constants文件夹)。
二、hooks 文件的命名规范(行业通用)
命名直接决定可读性,遵循以下规则:
| 规则 | 示例 | 说明 |
|---|---|---|
前缀必须是 use |
useUser.js |
符合 Vue 官方命名习惯 |
| 驼峰式命名 | useScrollPosition.js |
多单词用驼峰,清晰易懂 |
| 功能语义化 | useProductList.js |
一眼能看出功能(商品列表) |
| 拆分粒度适中 | 一个文件一个核心功能 | 避免大而全的「万能 hook」 |
补充:
- 通用型 hook 可加分类前缀:
useStorage/local.js(本地存储)、useRequest/list.js(列表请求); - 避免缩写:用
useWindowSize而非useWinSize。
三、hooks 文件的目录结构示例
src/
├── hooks/ # hooks 根目录
│ ├── useUser.js # 用户相关逻辑(登录、信息获取)
│ ├── useStorage.js # 本地存储封装(localStorage/sessionStorage)
│ ├── useRequest.js # 通用请求 hook(基于 axios)
│ ├── useScroll.js # 滚动监听 hook
│ └── useFormValidate.js # 表单验证 hook
├── components/ # 组件目录
├── views/ # 页面目录
└── main.js
四、hooks 文件的编写 & 引用示例
示例1:通用型 hook(本地存储封装 - useStorage.js)
javascript
// src/hooks/useStorage.js
import { ref, watch } from 'vue'
/**
* 本地存储 hook(封装 localStorage 操作)
* @param {string} key 存储的键名
* @param {any} defaultValue 默认值
* @returns {[value, setValue]} 存储值 + 修改方法
*/
export function useLocalStorage(key, defaultValue) {
// 从本地存储读取值,无则用默认值
const value = ref(JSON.parse(localStorage.getItem(key)) || defaultValue)
// 监听值变化,同步到本地存储
watch(
value,
(newVal) => {
localStorage.setItem(key, JSON.stringify(newVal))
},
{ deep: true } // 深度监听对象/数组
)
// 修改值的方法
const setValue = (newVal) => {
value.value = newVal
}
return [value, setValue]
}
// 封装 sessionStorage(同理)
export function useSessionStorage(key, defaultValue) {
const value = ref(JSON.parse(sessionStorage.getItem(key)) || defaultValue)
watch(
value,
(newVal) => {
sessionStorage.setItem(key, JSON.stringify(newVal))
},
{ deep: true }
)
const setValue = (newVal) => {
value.value = newVal
}
return [value, setValue]
}
组件中引用:
vue
<!-- src/views/Profile.vue -->
<template>
<div>
<p>用户名:{{ username }}</p>
<button @click="setUsername('新用户名')">修改用户名</button>
</div>
</template>
<script setup>
// 按需导入 hook
import { useLocalStorage } from '@/hooks/useStorage'
// 使用 hook:key 为 'user_name',默认值为 '游客'
const [username, setUsername] = useLocalStorage('user_name', '游客')
</script>
示例2:业务型 hook(用户登录状态 - useUser.js)
javascript
// src/hooks/useUser.js
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus' // 假设用了 Element Plus
import { getUserInfo, login } from '@/api/user' // 假设封装了用户相关接口
export function useUser() {
// 响应式数据:用户信息、加载状态
const userInfo = ref(null)
const loading = ref(false)
// 登录方法
const loginHandler = async (account, password) => {
try {
loading.value = true
const res = await login({ account, password })
if (res.code === 200) {
ElMessage.success('登录成功')
// 登录成功后获取用户信息
await fetchUserInfo()
return true
} else {
ElMessage.error(res.msg || '登录失败')
return false
}
} catch (err) {
ElMessage.error('网络错误,请重试')
return false
} finally {
loading.value = false
}
}
// 获取用户信息
const fetchUserInfo = async () => {
try {
const res = await getUserInfo()
userInfo.value = res.data
} catch (err) {
ElMessage.error('获取用户信息失败')
}
}
// 退出登录
const logout = () => {
userInfo.value = null
localStorage.removeItem('token')
ElMessage.success('退出登录成功')
}
// 组件挂载时自动获取用户信息(如有 token)
onMounted(() => {
const token = localStorage.getItem('token')
if (token) {
fetchUserInfo()
}
})
// 返回供组件使用的状态和方法
return {
userInfo,
loading,
loginHandler,
logout
}
}
组件中引用:
vue
<!-- src/views/Login.vue -->
<template>
<div class="login">
<el-input v-model="account" placeholder="账号"></el-input>
<el-input v-model="password" type="password" placeholder="密码"></el-input>
<el-button @click="handleLogin" :loading="loading">登录</el-button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useUser } from '@/hooks/useUser'
import { useRouter } from 'vue-router'
const router = useRouter()
const account = ref('')
const password = ref('')
// 解构获取 hook 中的状态和方法
const { loading, loginHandler } = useUser()
// 登录按钮点击事件
const handleLogin = async () => {
const success = await loginHandler(account.value, password.value)
if (success) {
router.push('/home') // 登录成功跳转到首页
}
}
</script>
示例3:通用请求 hook(useRequest.js)
javascript
// src/hooks/useRequest.js
import { ref } from 'vue'
/**
* 通用请求 hook
* @param {Function} apiFunc 接口请求函数(返回 Promise)
* @returns {Object} 请求相关状态和方法
*/
export function useRequest(apiFunc) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
// 执行请求的方法
const fetchData = async (...args) => {
try {
loading.value = true
error.value = null
const res = await apiFunc(...args) // 传递请求参数
data.value = res.data
return res
} catch (err) {
error.value = err
console.error('请求失败:', err)
throw err // 抛出错误,方便组件捕获
} finally {
loading.value = false
}
}
// 重置数据
const reset = () => {
data.value = null
error.value = null
}
return {
data,
loading,
error,
fetchData,
reset
}
}
组件中引用:
vue
<!-- src/views/GoodsList.vue -->
<template>
<div>
<el-table v-loading="loading" :data="data">
<!-- 表格列配置 -->
</el-table>
<el-button @click="fetchGoodsList(1)">刷新列表</el-button>
</div>
</template>
<script setup>
import { useRequest } from '@/hooks/useRequest'
import { getGoodsList } from '@/api/goods' // 商品列表接口
// 初始化请求 hook
const { data, loading, fetchData: fetchGoodsList } = useRequest(getGoodsList)
// 组件挂载时请求第一页数据
fetchGoodsList(1)
</script>
五、进阶技巧
-
hook 组合复用 :一个 hook 可以引用另一个 hook,例如
useUser中引用useStorage存储 token; -
参数校验 :在 hook 中增加参数校验(如
if (!key) throw new Error('key 不能为空')),提升健壮性; -
类型提示 :结合 TypeScript 给 hook 加类型,示例:
typescript// src/hooks/useStorage.ts export function useLocalStorage<T>(key: string, defaultValue: T): [Ref<T>, (val: T) => void] { // 实现逻辑 }
六、知识总结
- 文件类型 :
hooks文件夹只放以use开头的组合式函数,聚焦可复用的逻辑(通用功能/业务逻辑/交互/API 请求); - 命名规范 :必须以
use开头 + 驼峰式 + 语义化,一个文件一个核心功能; - 引用方式 :通过
import { 方法名 } from '@/hooks/文件名'按需导入,在组件的<script setup>中直接使用,返回的响应式数据可直接绑定到模板。
这种方式能大幅提升代码复用率,让组件逻辑更清晰,是 Vue3 组合式 API 最核心的最佳实践之一。