Files
Situation-Awareness-Platfor…/docs/Composition API Hooks逻辑复用实现详解.md
cfy666 c1e9a4be83 chore: sync local changes and add documentation
- Update yarn.lock
- Add project implementation docs in docs/
- Add personal internship experience notes in 实习讲解/
2026-06-29 19:47:30 +08:00

769 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Composition API Hooks 逻辑复用 — 面试版
> 简历原话:**"封装 Composition API Hooks 实现逻辑复用"**
>
> 这篇文档帮你理解项目里到底封装了哪些 Hooks、怎么复用的以及面试时怎么讲。
---
## 一、先搞清楚:什么是 Hooks为什么要封装
### 问题场景
```
没有 Hooks 的时候:
页面A需要响应式布局 → 写一堆 width 判断逻辑
页面B也需要响应式布局 → 再写一遍 width 判断逻辑
页面C也需要响应式布局 → 又写一遍...
页面D需要获取仓库权限 → 调 API、存 ref、写 computed
页面E也需要获取仓库权限 → 同样的代码再写一遍...
```
**痛点**:相同的逻辑散落在多个组件里,改了 A 忘改 B代码臃肿、维护困难。
### 有了 Hooks 之后
```
封装一个 usePageResize() Hook
页面A → const { widthType } = usePageResize()
页面B → const { widthType } = usePageResize()
页面C → const { widthType } = usePageResize()
26 个文件复用同一套逻辑,改 Hook 一处,全局生效
```
**一句话概括**:把可复用的响应式逻辑封装成独立的 `useXxx()` 函数,哪个组件需要就直接调用,避免重复代码。这是 Vue3 Composition API 最核心的设计思想。
### 项目中 Hooks 的定位
```
┌────────────────────────────────────────────────────────┐
│ Vue 组件层 │
│ Security/index.vue Ecosystem/index.vue AI/index.vue │
└────────┬───────────┬───────────┬──────────────────────┘
│ │ │ 调用 useXxx()
▼ ▼ ▼
┌────────────────────────────────────────────────────────┐
│ Hooks 逻辑层30+ 个) │
│ usePageResize useRepoId usePagination useLogin ... │
└────────┬───────────┬───────────┬──────────────────────┘
│ │ │
▼ ▼ ▼
┌────────────────────────────────────────────────────────┐
│ Pinia Store / API / Router │
│ (底层数据源和基础能力) │
└────────────────────────────────────────────────────────┘
```
---
## 二、项目里有 30+ 个 Hooks分六大类
| 分类 | Hooks | 数量 | 核心思想 |
|------|-------|------|----------|
| **通用交互** | usePageResize、useShowMore、useModel、usePagination、useLazyImport | 5+ | 跨组件复用的 UI 交互逻辑 |
| **表单与认证** | useFormInteraction、useLogin、useAccount、useAccModal、useNotice、useLoginCheck | 6+ | 登录/注册/表单校验的完整流程 |
| **数据获取** | useReq/useAsync、useRepoInit、useWikiHome/create/detail/history、useUserDashboard、useRepoList | 9+ | 异步请求 + 状态管理的一站式封装 |
| **路由/参数** | useRepoId、useOrgId、useBranchName、useTitle、useNav、useRepoPathValidate | 6+ | URL 参数解析、导航控制 |
| **权限控制** | useUserAccessLevel、usePagePermission、useIsPrivate | 3 | 权限判断 + computed 封装 |
| **其他辅助** | useFile、usePopup、useReport、useTimeFormat、useStarFollow、useUserInfo、useDiscussGetUserInfo 等 | 7+ | 文件图标、弹窗管理、埋点上报等 |
---
## 三、每个分类怎么实现的?(面试核心)
### 3.1 通用交互 Hooks最常用
#### usePageResize — 响应式断点判断26 个文件复用)
```typescript
import { computed } from 'vue';
import { useWindowSize } from '@vueuse/core';
const { width } = useWindowSize();
export const usePageResize = () => {
const widthConfig = { xxl: 1536, xl: 1280, md: 1024, lg: 768 };
const widthType = computed(() => {
if (width.value > 1536) return 'xxl';
if (width.value > 1280) return 'xl';
if (width.value > 1024) return 'md';
if (width.value > 768) return 'lg';
return 'sm';
});
const isMobile = computed(() => width.value <= 1024);
return { widthType, width, isMobile };
};
```
**怎么用**
```vue
<script setup>
const { widthType, isMobile } = usePageResize();
// 根据不同断点渲染不同布局
</script>
```
**复用在**15 个布局文件 + 4 个态势感知图表组件 + 导航栏、底部栏等,共 26 个文件。
**面试话术**
> "封装了 usePageResize基于 vueuse 的 useWindowSize 响应式监听窗口宽度,定义了 5 个断点xxl/xl/md/lg/sm返回 computed 类型的 widthType 和 isMobile。26 个布局和组件文件都在用,页面布局根据断点自动切换,不需要每个文件自己写一遍判断。"
---
#### useModel — v-model 双向绑定封装18 个文件复用)
```typescript
// 把 v-model 的 prop + emit 封装成统一的写法
export function useModel(props, emits) {
const vModels = computed({
get() { return props.modelValue; },
set(val) { emits('update:modelValue', val); }
});
return { vModels };
}
```
**解决什么**Vue3 自定义组件里使用 v-model 需要写 `props.modelValue` + `emits('update:modelValue')`,每个弹窗/输入组件都写一遍很繁琐。这个 Hook 一行代码搞定。
**怎么用**
```vue
<script setup>
const props = defineProps({ modelValue: Boolean });
const emits = defineEmits(['update:modelValue']);
const { vModels } = useModel(props, emits);
// vModels.value 就是双向绑定的值
</script>
```
**复用在**18 个弹窗/表单组件(登录弹窗、标签选择、分支选择等)。
---
#### usePagination — 分页器封装
```typescript
export const usePagination = ({ storageKey, clientType }) => {
const pager = reactive({
page: 1, pageSize: 10, total: 0,
loading: false, isFirstLoad: true,
showLoading: false, showEmpty: false,
});
// 自动判断显示骨架屏还是 loading
watch(() => pager.loading, (value) => {
if (value) {
if (pager.isFirstLoad || pager.total === 0) {
pager.showLoading = false; // 首次加载用骨架屏
} else {
pager.showLoading = true; // 切换页用 loading
}
} else {
pager.showEmpty = pager.total === 0;
}
});
return { pager, pageOptions };
};
```
**亮点**:自动区分"首次加载"和"翻页加载",首次加载显示骨架屏(结构预览),翻页显示 loading 动画,体验更好。
---
#### useShowMore — 内容溢出检测
```typescript
export const useShowMore = (eleRef) => {
const isOver = ref(false); // 内容是否超出容器
const checkRange = () => {
isOver.value = eleRef.value.scrollHeight > eleRef.value.clientHeight;
};
useResizeObserver(eleRef, checkRange); // 利用 ResizeObserver 自动检测
return { isOver, showMore, onShowMore };
};
```
**解决什么**:很多卡片展示简介时只能显示 3 行,超出部分要显示"查看更多"。这个 Hook 自动检测内容是否溢出。
---
#### useLazyImport — 异步组件懒加载封装
```typescript
export function useLazyImport(loader, options = {}) {
return defineAsyncComponent({
loader,
loadingComponent: showLoading ? LoadingComponents : '',
timeout: 2000, // 2s 超时
delay: 100, // 100ms 后才显示 loading避免闪烁
onError: (_, retry, fail) => {
Message.error('加载失败');
fail();
}
});
}
```
**解决什么**:大组件异步加载时,统一处理 loading 状态、超时、错误重试。项目中有至少 6 个组件通过它注册为异步组件。
---
### 3.2 表单与认证 Hooks
#### useFormInteraction — 表单校验完整流程最复杂332 行)
这是项目中最大的 Hook封装了整个表单交互逻辑
```typescript
export function useFormInteraction(currentForm, flag, extraStatus) {
// 状态管理
const formErrors = reactive({}); // 表单校验错误
const disabled = ref(true); // 提交按钮禁用
const loading = ref(false); // 提交 loading
const FormRef = shallowRef(null); // 表单组件引用
const status = ref(flag); // 协议勾选状态
// 表单字段 change实时校验单个字段
const handleFormChange = ({ key, errors }) => { ... };
// 表单 input判断是否禁用提交
const handleFormInput = (val) => { disabled.value = val; };
// 表单 submit校验全部 → 通过则回调
const handleSubmit = async (callback) => {
const formData = await FormRef.value.ValidateForm();
if (formData.type === 'success') {
await callback(formData.forms); // 执行提交逻辑
} else {
catchFormErrors(formData); // 展示错误
}
};
// 倒计时:发送验证码后 59 秒倒计时
const handleCountDown = async (conf, callback) => { ... };
// 第三方登录OAuth 跳转
const handleAuthLogin = (type) => { ... };
return {
FormRef, formErrors, disabled, loading,
handleFormChange, handleFormInput, handleSubmit,
handleCountDown, handleAuthLogin, ...
};
}
```
**封装了什么**:表单校验、清错、提交、协议勾选、倒计时、第三方 OAuth 登录——所有登录/注册页的通用逻辑,全部封装在一个 Hook 里。
**怎么用**
```vue
<script setup>
const { FormRef, disabled, loading, handleSubmit, handleFormChange } =
useFormInteraction(formList);
</script>
<template>
<Form ref="FormRef" @change="handleFormChange">
<!-- 表单内容 -->
</Form>
<Button :disabled="disabled" :loading="loading" @click="handleSubmit(onLogin)">
登录
</Button>
</template>
```
---
#### useLogin / useAccModal / useNotice — 弹窗类 Hooks
这三个 Hook 都基于 `usePopup` 封装,负责不同类型的弹窗:
```typescript
// useLogin — 登录弹窗
export function useLogin() {
const { mount, unMount, isMounted } = usePopup();
const login = (options) => {
if (isMounted()) return; // 防止重复弹出
mount(LoginModal, {
onLogin: (userInfo) => { unMount(); RecordInfo(userInfo); },
onClose: () => { unMount(); }
});
};
return { login };
}
// useAccModal — 快捷登录弹窗(支持 CSDN/Gitee/GitHub
export function useAccModal() {
const { mount, unMount } = usePopup('g-acc-modal');
const openModal = () => {
if (isMounted()) return;
mount(AccModal, { onConfirm: (type) => { /* OAuth 跳转 */ } });
};
return { openModal };
}
// useNotice — 邮箱修改提醒弹窗
export function useNotification() {
const { mount, unMount } = usePopup('global-notification', document.body);
const notice = () => {
mount(NoticeModal, { onConfirm: () => { router.push('/setting/email'); } });
};
return { notice };
}
```
**设计模式**:三个弹窗 Hook 基于同一个 `usePopup` 底层能力,各自封装不同的弹窗组件和业务逻辑。调用方只需要一行:
```typescript
const { login } = useLogin();
login({ type: 'login' }); // 弹出登录窗口
```
---
#### useAccount — 用户信息增删改查
```typescript
export function useAccount() {
const userInfo = useAccountStore();
const { accountInfo } = storeToRefs(userInfo);
// 记录用户信息(登录时)
const RecordInfo = (source) => {
localStorage.setItem('op_access_token', source.op_access_token);
localStorage.setItem('opUserInfo', JSON.stringify(userInfo));
saveStatus(true);
saveAccountInfo(userInfo);
};
// 清除用户信息(退出时)
const RemoveInfo = () => {
localStorage.removeItem('op_access_token');
// ... 清除 10+ 个 localStorage key
saveStatus(false);
saveAccountInfo();
};
return { RecordInfo, RemoveInfo, accountInfo };
}
```
**封装的逻辑**Token 存 localStorage + Store 同步 + 登录状态更新。一次调用 RecordInfo 完成三件事13 个文件复用。
---
### 3.3 数据获取 Hooks
#### useReq/useAsync — 异步请求封装(通用能力)
```typescript
export const useAsync = (fn, params, precondition, map) => {
const data = ref(null);
const error = ref(null);
const loading = ref(false);
watchEffect(() => {
if (!precondition()) return; // 前提条件不满足,不发请求
loading.value = true;
fn(params).then(res => data.value = map(res))
.catch(e => error.value = e)
.finally(() => loading.value = false);
});
return { data, error, loading, mutate }; // mutate 手动触发重新请求
};
```
**解决什么**:每个页面都要写 `data/loading/error` 三件套 + try/catch。这个 Hook 一行调用搞定,自动处理 loading 状态和错误捕获。
**面试话术**
> "封装了 useAsync 和 useReq 两个通用请求 Hook。useAsync 接收请求函数、参数、前置条件和结果映射函数,自动管理 data/loading/error 三种状态。前置条件不满足时不会发请求(比如用户未登录时不发),通过 watchEffect 自动追踪依赖变化。还提供了 mutate 方法手动触发重新请求。"
---
#### useRepoInit — 仓库首页全部数据一站式初始化243 行)
```typescript
export const useRepoInit = () => {
const { repoId } = useRepoId();
const repoInfo = reactive({...}); // 仓库信息
const loadingStatus = reactive({ // 5 个模块的加载状态
profileLoading: true,
readmeLoading: true,
eventsLoading: true,
contributorLoading: true,
releasesLoading: true,
});
const initRepoHeader = async () => { // 顶部初始化(快)
initRepoData(); await initNotice();
};
const initRepoDashboard = () => { // 首页初始化(完整)
initRepoData(); initEvents(); initReadme();
initContributors(); initRelease();
};
return {
repoInfo, loadingStatus, readmeText, timeData,
contributorList, releasesList,
initRepoHeader, initRepoDashboard, ...
};
};
```
**为什么这样设计**仓库首页需要加载仓库信息、README、动态、贡献者、Release 等 5+ 个模块的数据。每个模块都有独立的 loading 状态。Hook 封装了它们的初始化时机header 先加载dashboard 完整加载),页面只管调用 `initRepoHeader()``initRepoDashboard()`
---
#### useWikiHome / useWikiCreate / useWikiDetail / useWikiHistory — Wiki 全流程 Hooks
四个 Hook 覆盖 Wiki 的完整生命周期(首页 → 创建 → 详情 → 历史版本),每个 Hook 封装对应的数据获取 + 状态管理:
```
useWikiHome() → 首页:获取 Wiki 列表 + Home.md 内容
useWikiCreate() → 创建/编辑页:表单数据 + 提交 + 删除
useWikiDetail() → 详情页:内容 + 侧边栏 + 页脚
useWikiHistory() → 历史版本:版本列表 + 分页
```
**设计思想**:每个页面一个 Hook页面组件只负责渲染数据逻辑全部在 Hook 里。
---
#### useUserDashboard — 用户首页一站式数据初始化272 行)
```typescript
export const useUserDashboard = () => {
// 6 个模块的数据
const userRepoList = ref([]); // 我的仓库
const userEventsList = ref({}); // 最近动态
const recommandRepoList = ref([]); // 推荐仓库
const recommandOrgList = ref([]); // 推荐组织
const userActivityList = ref([]); // 关注动态
const operationData = ref({}); // 运营信息
// 6 个模块的加载状态
const loadingStatus = reactive({
boardLoading: true, myRepoLoading: true,
eventLoading: true, repoRecmLoading: true,
orgRecmLoading: true, activityLoading: true,
});
const pageInit = async () => { // 一键初始化
loadingStatus.boardLoading = true;
await getUserInfo();
await getUserRepos();
loadingStatus.boardLoading = false;
getRecentEvents(); // 不阻塞首屏
getOperationData(); // 不阻塞首屏
recommandRepos();
recommandOrgs();
getUserActivity();
};
return { userInfo, userRepoList, loadingStatus, pageInit, ... };
};
```
**面试话术**
> "比如 useUserDashboard 这个 Hook封装了用户首页 6 个模块的数据获取——仓库列表、最近动态、推荐仓库、推荐组织、关注动态、运营信息,每个模块有独立的 loading 状态。pageInit 方法分层加载:先加载用户信息和仓库列表(阻塞首屏),然后并行加载其他 5 个模块(不阻塞首屏)。页面上只需要 `const { pageInit } = useUserDashboard(); onMounted(pageInit);` 两行代码。"
---
#### useRepoList — 用户仓库列表(含 Star 操作)
```typescript
export const useRepoList = (params) => {
const repoList = ref([]);
const allRepoList = ref([]);
const createdRepoList = ref([]);
const starredRepoList = ref([]);
const loading = ref(false);
// 四类仓库列表 + Star/Unstar 操作
const toggleRepoStar = ({ id, isStar }) => {
if (!userInfo.username) {
emitEvent('login', { triggerType: 'Star' }); // 未登录 → 弹出登录框
return;
}
isStar ? unstarRepo({ repoId: id }) : starRepo({ repoId: id });
init(); // 操作后重新拉取列表
};
return { allRepoList, createdRepoList, starredRepoList, toggleRepoStar, init };
};
```
**封装的逻辑**:四种仓库列表 + Star/Unstar 操作 + 未登录时自动弹出登录框。8 个文件复用。
---
### 3.4 路由/参数 Hooks
#### useRepoId — 从 URL 提取仓库 ID30 个文件复用,使用最多)
```typescript
export const useRepoId = (connector = '%2F', namespace = '') => {
const route = useRoute();
const repoId = computed(() => {
const [, ns, repo, ...rest] = route.path.split('/');
if (namespace) {
return [ns, repo, ...rest].join(connector); // 拼接完整路径
}
return [ns, repo].join(connector); // namespace/repo
});
return { repoId };
};
```
**解决什么**:项目中大量页面需要从 URL 中提取仓库的 namespace/repoName`gitcode/MyProject`。30 个文件都在用这个 Hook不需要每个文件自己解析 URL。
**类似的还有**`useOrgId`(提取组织 ID`useBranchName`(提取分支名+编码)。
---
#### useTitle — 页面标题设置
```typescript
export const usePageTitle = (title, name) => {
if (title && name) {
pageTitle.value = `${title}`;
} else {
pageTitle.value = title || (name ? `${name} - GitCode` : 'Tcode');
}
};
```
路由守卫中每个页面切换时调用,自动更新浏览器标签页标题。
---
### 3.5 权限控制 Hooks
#### useUserAccessLevel — 权限查询 + 角色判断
```typescript
export function useUserAccessLevel({ type = 'org', targetId = '' } = {}) {
const access_level = ref(0);
async function getPermission(id) {
if (type === 'org') {
const res = await getOrgPermission({ group_id: id });
access_level.value = res.data.data.my_role?.access_level || 0;
} else {
const res = await getRepoPermission({ repo_id: id });
access_level.value = res.data.data.access_level || 0;
}
}
// 三种角色自动 computed
const isAdmin = computed(() => access_level.value >= admin); // 50
const isDeveloper = computed(() => access_level.value >= developer); // 30
const isVisitor = computed(() => access_level.value >= visitor); // 10
return { access_level, getPermission, isAdmin, isDeveloper, isVisitor };
}
```
**解决什么**:查询用户在当前组织/仓库的权限后,自动算出三个布尔角色值。配合 Store 里的权限 computed页面只需 `if (isAdmin)` 判断是否显示管理按钮。
#### usePagePermission — 页面级权限控制
```typescript
export function usePagePermission(access_level, isPrivate) {
// 根据权限等级和是否私有,返回哪些菜单/操作可用
}
```
---
### 3.6 其他辅助 Hooks
#### useFile — 文件类型识别(图标 + 语言高亮)
```typescript
export function useFile() {
// 根据文件名后缀返回对应图标名
const getFileIcon = (filename) => {
const format = filename?.split('.').pop();
if (/^(zip|rar|7z)$/i.test(format)) return 'gt-file-zip-c';
if (/^(png|jpg|jpeg|gif)$/i.test(format)) return 'gt-picture-c';
if (/^(js|ts|vue|py)$/i.test(format)) return 'gt-file-code-c';
return 'gt-file-c';
};
// 根据文件名返回代码高亮语言
const getFileLanguage = (filename) => {
const map = { js: 'javascript', ts: 'typescript', vue: 'html', py: 'python' };
return map[format] || format;
};
return { getIcon, getFileLanguage, getFileFormat };
}
```
#### useStarFollow — 关注/粉丝操作
```typescript
export const useStarFollow = (params, auto) => {
const toggleStar = (username, followedUsername, follow) => {
if (!username) {
emitEvent('login', { triggerType: '关注用户' }); // 未登录→弹出登录
return;
}
follow ? followUser(...) : unfollowUser(...);
microApp.setData('user-center', { type: 'user_hasFollowed_update', ... });
};
return { fanCount, followCount, hasFollowed, toggleStar };
};
```
**封装了什么**:关注/取消关注的 API 调用 + 状态更新 + 微前端数据同步 + 未登录自动弹登录框。
---
#### useReport — 埋点上报
```typescript
export const useReport = (eventID, eventParams, headers) => {
// 统一埋点上报逻辑
};
```
#### usePopup — 弹窗管理器(底层能力)
被 useLogin、useAccModal、useNotice 三个 Hook 依赖的底层弹窗管理:
```typescript
export function usePopup(className?, rootElement?) {
const mount = (component, props) => { /* 挂载弹窗到 DOM */ };
const unMount = () => { /* 卸载弹窗 */ };
const isMounted = () => { /* 是否已挂载 */ };
return { mount, unMount, isMounted };
}
```
---
## 四、Hooks 之间的层次关系
```
底层能力(被其他 Hook 依赖)
├── usePopup → 弹窗挂载/卸载
├── useReq/useAsync → 通用异步请求封装
├── useRepoId → URL 参数解析
└── useModel → v-model 封装
中间能力(组合底层能力)
├── useLogin → 基于 usePopup
├── useAccModal → 基于 usePopup
├── useNotice → 基于 usePopup
├── useFormInteraction → 基于 useAccount + useRouter
├── useUserAccessLevel → 基于 useUserInfo
业务能力(组合中间能力)
├── useRepoInit → 基于 useRepoId
├── useWikiHome → 基于 useRepoId + useRouter
├── useRepoList → 基于 API 调用
├── useUserDashboard → 基于 6 个 API 调用
├── useStarFollow → 基于 API + eventBus + microApp
└── usePageResize → 基于 useWindowSizevueuse
```
**面试话术**
> "我设计的 Hooks 是有层次的。底层 Hooks 提供通用能力(如 usePopup 管理弹窗生命周期、useAsync 封装异步请求);中间层 Hooks 基于底层组合出业务场景(如 useLogin 基于 usePopup 封装登录弹窗流程);最上层是业务 Hooks组件里一行代码调用就能获得完整的数据和操作方法。"
---
## 五、整体架构总结
```
30+ 个 Hooks 在项目中的定位:
可复用逻辑提取为 Hooks
┌───────────────┐
│ 通用交互 (5+) │ usePageResize(26文件) useModel(18文件) usePagination useShowMore useLazyImport
├───────────────┤
│ 表单认证 (6+) │ useFormInteraction(8文件) useLogin useAccount(13文件) useAccModal useNotice
├───────────────┤
│ 数据获取 (9+) │ useAsync/useReq useRepoInit(7文件) useWikiHome/create/detail/history
│ │ useUserDashboard useRepoList(8文件) useDiscussGetUserInfo
├───────────────┤
│ 路由参数 (6+) │ useRepoId(30文件) useOrgId useBranchName useTitle useNav useRepoPathValidate
├───────────────┤
│ 权限控制 (3) │ useUserAccessLevel usePagePermission useIsPrivate
├───────────────┤
│ 其他辅助 (7+) │ useFile usePopup useReport useTimeFormat useStarFollow(4文件) useUserInfo
└───────────────┘
Vue 组件层40+ 组件调用)
```
---
## 六、面试问答准备
### Q1"封装 Composition API Hooks 实现逻辑复用"具体做了什么?
> 项目里封装了 30 多个 Hooks分为六大类。最核心的几个usePageResize 把响应式断点判断封装成 Hook26 个布局文件复用useRepoId 从 URL 提取仓库 ID30 个文件复用useFormInteraction 封装了完整的表单校验流程332 行8 个登录/注册页共享同一套校验逻辑useUserDashboard 封装了用户首页 6 个模块的数据初始化页面只需两行代码完成全部数据加载。Hooks 之间还有层次关系——底层 HooksusePopup、useAsync提供通用能力中间层组合底层能力业务层直接给组件用。
### Q2Hooks 和之前写的工具函数有什么区别?
> 工具函数是无状态的纯函数输入→输出Hooks 是有状态的(包含 ref、computed、watch。比如 useFormInteraction 内部管理了 formErrors、disabled、loading 等 6 个状态,还监听了表单 change 事件自动校验。如果写成工具函数这些状态管理就要在每个页面里重复写。Hooks 把"响应式状态 + 操作逻辑"封装在一起,是 Vue3 Composition API 的核心优势。
### Q3Hooks 怎么保证类型安全?
> 所有 Hooks 都用 TypeScript 写,返回值和参数都有类型定义。比如 usePagination 接收 `{storageKey, clientType}` 接口类型useRepoList 接收包含 profile/all/created/starred 四种子参数的类型。组件调用时 VSCode 有完整的智能提示。
### Q4有没有用过 VueUse 的 Hooks
> 用过useWindowSize、useResizeObserver、useTitle 等都是从 @vueuse/core 引入的。但项目不只是用第三方 Hooks更关键的是基于 VueUse 做二次封装——比如 usePageResize 基于 useWindowSize 封装了断点判断逻辑useShowMore 基于 useResizeObserver 封装了内容溢出检测。既用了社区的最佳实践,又做了业务定制。
### Q530 多个 Hooks 怎么组织的?会不会管理混乱?
> 按职责分目录:通用的放在 `src/utils/hooks/`30 个),特定业务域放在对应的 views 目录下(如 `src/views/User/hooks/` 放用户相关的 7 个 Hook布局相关的放在 `src/layouts/hooks/`。调用时能直观看出 Hook 的作用域。
### Q6Hooks 和 Pinia Store 怎么分工?
> Hooks 和 Store 的分工很明确。Store 存跨组件共享的持久状态用户信息、仓库信息Hooks 封装的是"行为逻辑"——怎么获取数据、怎么校验表单、怎么处理交互。Hooks 经常调用 Store 来读写状态,但它们不替代 Store。比如 useAccount Hook 既调用 Store 的 saveAccountInfo又操作 localStorage把"用户信息存储到 Store + 持久化到 localStorage + 更新登录状态"三步封装成一步 RecordInfo。
---
## 七、关键数字(面试时用)
| 数据 | 数字 |
|------|------|
| Hooks 总数 | 30+ 个 |
| 使用最多的 Hook | useRepoId30 个文件、61 处引用) |
| 第二常用 | usePageResize26 个文件、51 处引用) |
| 第三常用 | useModel18 个文件、35 处引用) |
| 最大的 Hook | useFormInteraction332 行) |
| 业务最全的 Hook | useUserDashboard6 个模块数据初始化) |
| 弹窗类 Hooks | 3 个useLogin/useAccModal/useNotice共用 usePopup |
| Wiki 全家桶 | 4 个Home/Create/Detail/History |
| 权限类 Hooks | 2 个useUserAccessLevel/usePagePermission |
| 基于 @vueuse/core 封装的 | 3 个useWindowSize/useResizeObserver/useTitle |
---
## 八、涉及的源码文件(需要看的时候查)
| 做什么 | 文件在哪 |
|--------|----------|
| 通用交互 Hooks5 个) | `src/utils/hooks/usePageResize.ts``useShowMore.ts``useModel.ts``usePagination.ts``useLazy.ts` |
| 表单与认证 Hooks6 个) | `src/utils/hooks/useForm.ts``useLogin.ts``useAccount.ts``useAccModal.ts``useNotice.ts``useLoginCheck.ts` |
| 数据获取 Hooks9 个) | `src/utils/hooks/useReq.ts``useRepoInit.ts``useWikiInit.ts``useIssueTemplate.ts``src/api/discussion/hook.ts` |
| 用户域 Hooks7 个) | `src/views/User/hooks/useUserDashboard.ts``useRepoList.ts``useStarFollow.ts``useContributes.ts``useTimelineActivities.ts``useIsPrivate.ts``useUserLang.ts` |
| 路由/参数 Hooks6 个) | `src/utils/hooks/useRepoId.ts``useOrgId.ts``usebranchName.ts``useTitle.ts``useNav.ts``useRepoPathValidate.ts` |
| 权限控制 Hooks3 个) | `src/utils/hooks/useUserAccessLevel.ts``src/layouts/hooks/usePagePermission.ts``src/views/User/hooks/useIsPrivate.ts` |
| 其他辅助 Hooks7 个) | `src/utils/hooks/useFile.ts``usePopup.ts``useReport.ts``useTimeFormat.ts``useUserInfo.ts``useRepoHeaderInit.ts``useBranchOptions.ts` |
| 组织域 Hooks | `src/views/Org/hooks/orgInfo.ts` |
| 弹窗底层能力 | `src/utils/hooks/usePopup.ts`(被 useLogin/useAccModal/useNotice 依赖) |