Hooks 使用与管理全攻略
概述:
本文系统整理了 Vue 项目中使用组合式函数(hooks/composables)的最佳实践,涵盖结构、收集、复用、依赖管理等方面。
🎯 一句话总结
将逻辑从组件中解耦出来,变得更清晰、更可复用、更易测试和维护。
✅ 1. 为什么使用自定义 hooks?
❌ 问题背景(传统写法带来的痛点)
如果你在组件中直接写:
<script setup>
import { ref } from 'vue';
const username = ref('');
function resetUsername() {
username.value = '';
}
</script>
问题在于:
| 问题 | 自定义 hooks 的解决方式 |
|---|---|
| 表单字段逻辑重复 | useFormInput()、useFormCheckbox() 提取复用逻辑 |
| 组件代码臃肿不清晰 | 把状态逻辑抽出去,组件更专注展示 |
| 多页面字段逻辑一致 | hooks 复用一致逻辑,维护一处生效 |
| 不易测试和扩展 | hooks 可独立单测、添加校验、监听等能力 |
✅ Hooks 的作用和优势
-
✅ 逻辑复用
你只要写一次 useFormInput,可以在多个组件中复用这个“输入+重置”的逻辑。
const username = useFormInput("张三"); const phone = useFormInput("123456");而不用每次都手动写:
const xxx = ref(""); function resetXxx() { xxx.value = ""; } -
✅ 关注点分离
把「表单状态 + 初始化 + 重置」的逻辑独立出去,组件里就只关心业务,不用管这些底层细节。组件变得更专注、更干净。
-
✅ 更易扩展和维护
比如我们要添加“输入值监听”或“校验”:
export function useFormInput(initialValue = "") { const value = ref(initialValue); // 新增监听逻辑 watch(value, (val) => { console.log("输入值变化为", val); }); function reset() { value.value = initialValue; } return { value, reset }; }你只改了
useFormInput这一处,所有使用它的组件都立刻受益。 -
✅ 可单独测试(更容易写单元测试)
import { useFormInput } from "./useFormInput"; test("should reset to initial value", () => { const { value, reset } = useFormInput("abc"); value.value = "xyz"; reset(); expect(value.value).toBe("abc"); });组件逻辑越“干净”,越能通过组合函数单独测试。
✅ 2. 推荐目录结构
✅ 命名 composables 更通用,适合团队协作,或者也可以叫 hooks/
src/
├── composables/ 👈 推荐命名:组合式函数(推荐)
│ ├── form/
│ │ ├── useFormInput.js
│ │ ├── useFormCheckbox.js
│ │ ├── useFormDate.js
│ │ ├── useFormGroup.js
│ │ └── index.js 👈 统一导出
使用示例:
// composables/form/index.js
export * from "./useFormInput";
export * from "./useFormCheckbox";
export * from "./useFormRadio";
import { useFormInput, useFormCheckbox } from "@/composables/form";
✅ 3. 如何统一收集多个 hooks 的 value?
使用 useFormGroup 工具封装:
当字段太多时,推荐写一个 useFormGroup 来自动统一管理字段。
📦 useFormGroup 封装示例:
// useFormGroup.js
export function useFormGroup(fields) {
function getValues() {
const result = {};
for (const key in fields) {
result[key] = fields[key].value;
}
return result;
}
function resetAll() {
Object.values(fields).forEach((field) => field.reset?.());
}
function validateAll() {
let valid = true;
Object.values(fields).forEach((field) => {
if (typeof field.validate === "function") {
valid = field.validate() && valid;
}
});
return valid;
}
return {
getValues,
resetAll,
validateAll,
fields,
};
}
使用方式(配合多个 useFormXXX):
import { useFormInput } from "@/composables/useFormInput";
import { useFormCheckbox } from "@/composables/useFormCheckbox";
import { useFormGroup } from "@/composables/useFormGroup";
const username = useFormInput("", { required: true });
const password = useFormInput("", { required: true });
const hobbies = useFormCheckbox([]);
const { getValues, validateAll, resetAll } = useFormGroup({
username,
password,
hobbies,
});
async function onSubmit() {
if (!validateAll()) {
console.warn("校验不通过");
return;
}
const formData = getValues(); // 🚀 自动收集所有 value
await api.submitForm(formData);
}
✅ 4. 如何组合多个 hooks?
随着 hooks 越来越多、功能越来越复杂,如何管理它们之间的依赖关系?
问题本质
- 数据依赖:A hook 的行为依赖于 B 的结果或状态
- 调用顺序依赖:必须先初始化某些 hooks,再用其他 hooks
- 功能组合依赖:多个 hooks 一起组成一个“功能模块”(如一个表单字段)
管理多个 hooks 依赖关系的 5 个常见方式:
1️⃣ 创建组合器函数:
写一个“组合器”,用来统一初始化并组合多个 hooks —— 类似 useFormGroup
export function useUserFormGroup() {
const username = useFormInput("", { required: true });
const gender = useFormSelect("", ["男", "女"]);
const hobbies = useFormCheckbox([]);
return {
fields: { username, gender, hobbies },
getValues() {
return {
username: username.value,
gender: gender.value,
hobbies: hobbies.value,
};
},
};
}
✅ 所有依赖集中管理 ✅ 表单相关逻辑聚合在一个地方 ✅ 非侵入,保持 hooks 独立
2️⃣ 创建组合器函数:
如果一个 hook 依赖另一个 hook,可以通过参数传入,避免隐式耦合。
export function usePasswordConfirm(passwordField) {
const confirmPassword = useFormInput("");
const error = ref("");
watch(confirmPassword.value, (val) => {
if (val !== passwordField.value) {
error.value = "两次输入不一致";
} else {
error.value = "";
}
});
return { confirmPassword, error };
}
✅ 明确声明依赖 ✅ 更易读,更易测试
3️⃣ 组合基础能力层(useFormField)继承封装
所有字段 hook 继承同一个低层基础结构,内部可以有一致的 validate/reset 接口。
function useFormField(initialValue, config = {}) {
const value = ref(initialValue);
const error = ref("");
function reset() {
value.value = initialValue;
error.value = "";
}
function validate() {
/_..._/;
}
return { value, error, reset, validate };
}
然后其他 hooks 复用它:
function useFormInput(...args) {
return useFormField(...args);
}
✅ 保持一致的结构 ✅ 可以在更高层组合 validateAll/resetAll
4️⃣ 用工厂函数统一注册 hooks(适用于 schema 场景)
function createField(type, config) {
switch (type) {
case "input":
return useFormInput(config.default || "", config);
case "select":
return useFormSelect(config.default || "", config.options || []);
}
}
const schema = [
{ type: "input", name: "username" },
{ type: "select", name: "gender", options: ["男", "女"] },
];
const formFields = {};
schema.forEach((item) => {
formFields[item.name] = createField(item.type, item);
});
5️⃣ 使用 hooks 容器或注册器(进阶)
如果项目很复杂,你甚至可以实现一个“hook 容器”:
class FormHookRegistry {
constructor() {
this.registry = {};
}
register(name, hook) {
this.registry[name] = hook;
}
get(name) {
return this.registry[name];
}
getAllValues() {
return Object.fromEntries(Object.entries(this.registry).map(([key, hook]) => [key, hook.value]));
}
}
✅ 5. hooks 之间有依赖,如何解耦管理
💡 场景 1:字段依赖字段(如密码确认)
推荐:用参数注入依赖(不要在 hook 内部 import 另一个 hook)
export function usePasswordConfirm(passwordField) {
const confirmPassword = useFormInput("");
watch(confirmPassword.value, (val) => {
if (val !== passwordField.value) {
error.value = "不一致";
}
});
}
💡 场景 2:动态字段(如 schema)
推荐:字段注册 + 工厂方法
function createField(type, config) {
switch (type) {
case "input":
return useFormInput(config.default || "");
case "select":
return useFormSelect(config.default || "", config.options);
}
}
然后:
const formFields = {};
schema.forEach((field) => {
formFields[field.name] = createField(field.type, field);
});
💡 场景 3:字段共享上下文或 validate/reset
推荐封装 useFormField 基础字段结构,所有字段继承它:
function useFormField(value, config) {
return {
value: ref(value),
error: ref(''),
validate() { ... },
reset() { ... }
};
}
✅ 推荐组合与结构总结
| 功能 | Hook 用法 |
|---|---|
| 输入框字段 | useFormInput('', { required: true }) |
| 多选字段 | useFormCheckbox([], { required: true }) |
| 字段组合 | useFormGroup({ username, password }) |
| 字段依赖传参 | usePasswordConfirm(username) |
| 字段类型工厂 | createField('input', config) |
| schema 驱动 | schema.forEach(field => createField(...)) |
✅ 可扩展方向建议
- ✅ 异步校验支持(如手机号唯一性)
- ✅ schema 配置驱动渲染表单
- ✅ 字段状态管理(disabled / readonly)
- ✅ 和 pinia 状态缓存联动
- ✅ 自动生成 validateAll / resetAll / getValues 工具函数