1. 场景背景
面试官提问: “在大型项目中,我们经常遇到多个页面都有相似的功能,比如:‘表格的搜索/分页/加载状态’、‘倒计时功能’或者是‘埋点上报逻辑’。如果每个页面都写一遍,代码会非常臃肿。在 Vue 3 中,你会如何设计一套方案来复用这些逻辑?为什么要这么设计?它比 Vue 2 的 Mixins 好在哪里?”
2. 核心考点
- Hooks(Composition API) 的设计思想。
- 逻辑抽象能力(把业务代码抽离为无 UI 的逻辑函数)。
- 对比 Mixins 的缺陷(命名冲突、来源不明、隐式依赖)。
- 响应式丢失问题(如何正确解构 Hooks 的返回值)。
3. 综合解决方案
方案:封装自定义 Hook (useTable / useList)
思路: 将“状态”和“改变状态的方法”封装在一个函数中,并返回响应式数据。
实现示例(以通用的表格加载逻辑为例):
TypeScript
// useTable.ts
import { ref, onMounted } from 'vue';
export function useTable(apiFn: Function, params = {}) {
const data = ref([]);
const loading = ref(false);
const total = ref(0);
const fetchList = async () => {
loading.value = true;
try {
const res = await apiFn(params);
data.value = res.list;
total.value = res.total;
} finally {
loading.value = false;
}
};
onMounted(fetchList);
return { data, loading, total, fetchList };
}
业务组件使用:
代码段
<script setup>
import { useTable } from '@/hooks/useTable';
import { getOrderList } from '@/api/order';
// 逻辑一键引入,保持 setup 简洁
const { data: orders, loading } = useTable(getOrderList, { status: 1 });
</script>
4. 满分回答示例
回答要点: “我会优先使用 Vue 3 的 Composition API 封装自定义 Hooks 来解决逻辑复用问题。
-
实现方式: 我会将具有通用性的业务逻辑抽离到
src/hooks文件夹下。每个 Hook 实际上是一个以use开头的函数,它内部可以维护自己的ref或reactive状态,并利用onMounted等生命周期钩子。最后将组件需要用到的状态和方法return出来。 -
对比 Mixins 的优势:
- 来源清晰: 在组件中,我们可以清楚地看到某个变量是从哪个 Hook 中解构出来的,而 Mixins 是‘隐式注入’,很难追溯。
- 解决命名冲突: Hooks 返回的是普通变量,我们可以在解构时重命名(如
const { data: userList } = useList()),完全避开了 Mixins 变量名覆盖的问题。 - 灵活性: Hooks 可以接收参数(如 API 函数或配置项),从而根据不同场景动态调整逻辑。
-
注意事项: 在封装时,我会注意为了保持响应式,返回的对象通常使用
ref或toRefs处理。此外,我会遵循‘单一职责原则’,一个 Hook 只处理一个独立的业务维度(比如useAuth处理权限,usePagination处理分页)。”
5. 查漏补缺(进阶加分项)
- 与 Teleport 结合: 如果逻辑涉及弹窗(如全局 Loading),Hook 可以配合
Teleport实现逻辑与 DOM 的双重解耦。 - 单例模式: 有些 Hook(如
useUser)需要全局共享状态,我会配合provide/inject或者Pinia来确保数据在整个应用中是同一份。 - 组合性: 强调 Hooks 之间是可以互相嵌套使用的,这种“乐高式”的开发体验是 Vue 3 最大的架构进步。