Vue3 hooks 的使用
命名规范
- 使用 useXxx 命名风格,表明这是一个组合函数。
单一职责原则
- 每个 Hook 应专注于一个功能或逻辑单元,避免将多个不相关的逻辑混合在一起。
ts
// bad
function useUserAndProduct() { ... }
// good
function useUser() { ... }
function useProduct() { ... }
可复用性与参数化
- 通过传参提升灵活性,使 Hook 能在不同组件中复用
ts
function useFetch<T>(url: string): { data: Ref<T>; loading: Ref<boolean> } {
const data = ref<T>();
const loading = ref(true);
fetch(url)
.then((res) => res.json())
.then((json) => {
data.value = json;
loading.value = false;
});
return { data, loading };
}
返回响应式状态和方法
- 通常返回 ref、reactive 或 computed 状态以及操作它们的方法
ts
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
const increment = () => count.value++;
const decrement = () => count.value--;
return { count, increment, decrement };
}
易于测试
- 将业务逻辑从组件中抽离到 Hook 中,可以更方便地进行单元测试。
ts
// useCounter.test.ts
test('increments counter', () => {
const { count, increment } = useCounter();
increment();
expect(count.value).toBe(1);
});
合理使用生命周期钩子
- 在 Hook 中合理使用 onMounted、onUnmounted 等生命周期钩子,用于管理副作用
ts
function useEventListener(target, event, handler) {
onMounted(() => {
target.addEventListener(event, handler);
});
onUnmounted(() => {
target.removeEventListener(event, handler);
});
}
模块化组织
- 将多个相关 Hook 组织成模块或工具包,便于管理和导入。
bash
/hooks
├── useCounter.ts
├── useFetch.ts
└── useFormValidation.ts
类型安全(TypeScript)
- 如果项目使用 TypeScript,务必为 Hook 添加类型定义,提高可维护性和 IDE 支持
ts
interface UseFetchReturn<T> {
data: Ref<T | null>;
loading: Ref<boolean>;
error: Ref<Error | null>;
}
function useFetch<T>(url: string): UseFetchReturn<T> {
// ...
}
避免副作用污染
- 不要在 Hook 中直接修改全局状态或执行副作用,应通过返回值让组件自行处理。
结合 UI 层使用
Hook 只负责数据逻辑,UI 层使用其返回值驱动视图
ts
<script setup>
import { useCounter } from '@/hooks/useCounter'
const { count, increment } = useCounter()
</script>
<template>
<button @click="increment">Count: {{ count }}</button>
</template>
hooks 中使用 onMounted 等生命周期的优缺点
✅ 正确性没问题:
在组件挂载时自动发起请求,符合业务逻辑。 代码简洁,易于理解。
❌ 存在的问题:
问题 | 说明 |
---|---|
1. 职责不清晰 | Hook 应该只负责封装逻辑,而不应隐式地绑定生命周期行为。这会让调用者不清楚内部副作用是如何触发的。 |
2. 可测试性差 自动执行 | onMounted 会增加单元测试的复杂度,因为你需要模拟 Vue 生命周期才能测试其行为。 |
3. 灵活性受限 | 如果组件希望手动控制请求时机(例如:根据用户输入再加载),自动触发就变得不合适。 |
4. 复用性降低 | 其他组件如果想复用这个 Hook,但不想在 onMounted 时加载数据,就不得不绕过或重写这部分逻辑。 |