一、React Hooks 相关(1-14 题)
1. 项目中使用过哪些常用的 hooks?
答:常用的包括:
- 基础:
useState、useEffect、useRef、useContext - 性能优化:
useMemo、useCallback - 状态管理:
useReducer - React 18 新增:
useId、useTransition、useDeferredValue、useSyncExternalStore - 其他:
useImperativeHandle、useLayoutEffect
2. useState 为什么返回一个数组的形式?
答:
- 解构赋值的灵活性:返回数组可以让用户自由命名状态变量和更新函数,比如
const [count, setCount],如果返回对象,就只能用固定的 key(如state、setState),无法自定义变量名。 - 支持多个状态:同一组件中可以多次调用
useState,分别声明不同状态,互不干扰。 - API 设计简洁:相比返回对象,数组解构更直观,代码更简洁。
3. hooks 函数为什么要在顶层使用?
答:React 依赖 hooks 调用顺序 来关联组件的状态。
- 若在条件、循环、嵌套函数中调用 hooks,会导致调用顺序不固定,React 无法正确匹配状态,出现状态错乱的问题。
- 规则:不能在
if/for/while等条件语句、嵌套函数中调用 hooks,必须在组件的顶层调用。
4. setState 做了哪些事情?
这里默认指 React 类组件的 setState,函数组件的 setXxx 底层逻辑类似:
- 合并更新(批量更新) :多次
setState会被合并成一次更新,减少渲染次数。 - 触发协调(Diff 算法) :生成新的虚拟 DOM 树,与旧树对比,找出差异。
- 更新真实 DOM:将差异应用到真实 DOM,完成页面更新。
- 触发生命周期 / Effect:类组件触发
shouldComponentUpdate、componentDidUpdate,函数组件触发useEffect。
5. React 的 Diff 算法跟 Vue 的 Diff 算法有什么区别?
表格
| 维度 | React Diff | Vue Diff |
|---|---|---|
| 对比方式 | 同层节点对比,采用双端遍历(旧头、旧尾、新头、新尾),找不到再按 key 找 | Vue 2 同层双端遍历;Vue 3 优化为最长递增子序列,减少移动操作 |
| key 的作用 | 用于识别节点是否可复用,无 key 会强制删除重建 | 同 React,Vue 3 中 key 对优化移动操作更关键 |
| 复杂度优化 | 时间复杂度优化到 O (n) | Vue 2 同 React;Vue 3 最长递增子序列算法进一步减少移动次数 |
| 场景侧重 | 列表结构简单、移动少的场景 | 列表频繁移动、增删的场景,Vue 3 性能更优 |
6. 它的 Diff 算法的时间复杂度是什么?
答:React 的 Diff 算法将传统树对比的 O (n³) 复杂度,通过同层对比、key 复用、单向遍历优化到了 O(n) ,其中 n 是节点总数。
7. Vue 3 的 Diff 算法中,最长递增子序列的算法内部实现了解吗?
答:Vue 3 中,当新旧列表节点无法通过双端遍历复用节点时,会用最长递增子序列(LIS)算法找出不需要移动的节点,只移动非递增子序列中的节点,从而减少 DOM 操作。实现思路:
- 先建立新节点索引与旧节点索引的映射数组。
- 对映射数组求 LIS,得到最长的、顺序不变的子序列。
- 遍历新列表,LIS 中的节点保持原位,其他节点移动到正确位置。算法采用贪心 + 二分实现,时间复杂度为 O (n log n)。
8. React 中 useReducer 使用过没?
答:用过。useReducer 是 useState 的替代方案,适合处理复杂状态逻辑。
- 语法:
const [state, dispatch] = useReducer(reducer, initialState) - 适用场景:状态更新逻辑复杂、多个子值相互依赖、需要集中管理状态变更逻辑。
- 示例:
js
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
default: return state;
}
}
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'increment' });
9. Zustand 为什么只适用于轻量的项目?
答:
- 缺乏中间件生态:相比 Redux,Zustand 中间件较少,复杂场景(如日志、持久化、异步流)支持弱。
- 状态管理分散:Store 可独立创建,大型项目中容易导致状态碎片化,难以统一管理和调试。
- DevTools 支持弱:调试体验不如 Redux DevTools 完善。
- 不依赖 Provider 导致的问题:大型项目中难以做统一的状态注入和权限控制。
10. Zustand 为什么不需要依赖 Provider?它的底层实现是怎么样的?
答:
-
为什么不需要 Provider:Zustand 的 Store 是单例模式,直接创建的 store 实例是全局的,组件直接引用即可获取状态,不需要通过 Context 传递。
-
底层实现:
- 核心是
create函数,创建一个包含状态、更新函数的 store 对象。 - store 内部维护一个状态对象和订阅者列表。
useStorehook 内部使用useSyncExternalStore,订阅 store 的状态变化,状态更新时触发组件重渲染。
- 核心是
11. Zustand 掉了 set 方法,为什么能更新对应的组件,重新 render?
答:Zustand 的 set 方法会触发 store 的状态更新,并通知所有订阅该状态的组件。
- 组件通过
useSyncExternalStore订阅 store,状态变化时,useSyncExternalStore会触发组件重渲染。 - 本质上,组件订阅的是 store 的状态变化事件,
set方法会发布事件,通知订阅者更新。
12. useSyncExternalStore 底层怎么实现的?为什么能触发 setState 一样的更新机制?
答:
-
底层实现:
- 接收三个参数:
subscribe(订阅函数)、getSnapshot(获取状态快照)、getServerSnapshot(可选,服务端渲染用)。 - 组件挂载时,调用
subscribe注册回调,当外部 store 状态变化时,回调会触发。 - 回调内部调用
getSnapshot获取最新状态,对比新旧状态,若不同则触发组件重渲染。
- 接收三个参数:
-
为什么能触发更新:
useSyncExternalStore是 React 官方提供的用于订阅外部 store 的 hook,它会在状态变化时,通过 React 的内部调度机制触发组件更新,和setState触发更新的底层调度是一致的。
13. React 18 新增了哪些 Hooks?
答:
useId:生成唯一 ID,解决 SSR 中 ID 不匹配的问题。useTransition:将状态更新标记为过渡,实现非阻塞更新,优化用户体验。useDeferredValue:延迟更新非紧急的状态,优先处理高优先级更新。useSyncExternalStore:订阅外部 store(如 Zustand、Redux)的状态变化。useInsertionEffect:用于 CSS-in-JS 库,在布局副作用前注入样式。
14. Redux 使用过吗?
答:用过。Redux 是一个集中式状态管理库,核心概念包括:
store:唯一的状态容器。action:描述状态变化的对象。reducer:纯函数,接收旧状态和 action,返回新状态。dispatch:发送 action,触发状态更新。现代 Redux 一般用 Redux Toolkit,简化了配置,内置了 immer、异步处理等功能。
15. 对纯函数的理解?
答:纯函数是指满足以下两个条件的函数:
- 相同输入,得到相同输出:输出只由输入决定,不受外部状态影响。
- 无副作用:不修改外部变量、不进行 I/O 操作、不调用非纯函数等。
- 示例:
const add = (a, b) => a + b; - 意义:可预测、可测试、可缓存,是 React 状态管理(如 Redux reducer)的核心要求。
二、微前端相关(16-19 题)
16. 项目中微前端使用了什么架构?
答:我们项目使用的是 qiankun 微前端框架,主应用 + 多个子应用的架构,主应用负责路由分发、公共依赖加载,子应用独立开发、独立部署。
17. 为什么选择 qiankun,而不是 ice(飞冰)的微前端架构?
答:
- 生态成熟度:qiankun 基于 single-spa,社区更活跃,文档和问题解决方案更多。
- 技术栈无关:qiankun 支持 React、Vue、Angular 等任意技术栈的子应用,而 ice 微前端更偏向阿里体系的技术栈。
- 沙箱隔离完善:qiankun 提供了 JS 沙箱和样式沙箱,解决了全局变量污染和样式冲突问题。
- 配置简单:qiankun 接入成本低,子应用改造少,适合快速落地。
18. qiankun 和 ice 微前端在性能 / 其他方面有什么不一样?
答:
- 性能:qiankun 沙箱隔离(尤其是 JS 沙箱)会有一定性能开销,ice 微前端的沙箱优化更好,性能略优。
- 生态:ice 微前端和 ice 工程体系绑定较深,qiankun 更通用,适配性更强。
- 场景侧重:ice 微前端更适合阿里内部业务,qiankun 更适合跨技术栈、跨团队的复杂微前端场景。
19. qiankun 里面的沙箱隔离是怎么做的?
答:qiankun 提供了两种沙箱:
-
JS 沙箱:
- 旧版:基于
with+Proxy,隔离子应用的全局变量,防止污染主应用。 - 新版(快照沙箱):支持不支持 Proxy 的浏览器,通过记录 / 恢复全局变量快照实现隔离。
- 旧版:基于
-
样式沙箱:
- 通过给子应用的样式加前缀(如
data-qiankun属性),或者使用shadow DOM,实现样式隔离,防止子应用样式污染主应用。
- 通过给子应用的样式加前缀(如
三、知识库 / AI 检索相关(20-26 题)
20. 知识库的切片方式有哪几种?
答:常见的切片方式:
- 固定长度切片:按固定字符数(如 512 字符)切割,实现简单,但可能破坏语义。
- 语义切片:按句子、段落、章节等语义单元切割,保留语义完整性,效果更好。
- 递归字符切片:按不同层级的分隔符(如换行、句号、空格)递归切割,平衡语义完整性和长度限制。
- 基于 LLM 的智能切片:利用大模型识别文本语义边界,智能切割,效果最好但成本高。
21. 切片之后,下面的流程有哪些?
答:
- 向量化:将切片后的文本通过 Embedding 模型转换为向量。
- 向量存储:将向量和元数据存入向量数据库(如 Pinecone、Chroma)。
- 检索阶段:用户查询文本,同样转换为向量,进行相似度匹配。
- 结果召回:返回相似度最高的切片文本。
- 增强生成:将召回的文本和用户问题一起传入 LLM,生成回答。
22. 检索过程有哪些优化可以做?
答:
- 向量数据库优化:使用 HNSW、IVF 等索引算法,加快向量检索速度。
- 混合检索:结合向量检索和关键词检索(如 BM25),提高召回率。
- 重排序:使用 CrossEncoder 等模型对召回结果进行二次排序,提升相关性。
- 查询改写:对用户查询进行扩展、纠错、意图识别,提高匹配准确率。
- 缓存优化:对高频查询结果进行缓存,减少重复检索。
23. 项目中的向量维度是多少?
答:(根据实际情况回答,比如)我们项目使用的是 OpenAI 的 text-embedding-ada-002 模型,向量维度是 1536;或者使用国内模型,如智谱的 Embedding 模型,维度是 1024。
24. 相似度匹配的原理是什么?
答:文本被转换为向量后,通过计算向量之间的相似度来衡量文本的相关性,常见的计算方式:
- 余弦相似度:计算两个向量的夹角余弦值,值越接近 1,相似度越高,是向量检索中最常用的方式。
- 欧氏距离:计算两个向量的直线距离,距离越小,相似度越高。
- 点积:计算向量的点积,向量长度归一化后等价于余弦相似度。
25. 如何发起一个 SSE 请求?
答:使用 EventSource API 发起 SSE 请求:
js
const eventSource = new EventSource('/stream');
eventSource.onmessage = (e) => {
console.log('收到消息:', e.data);
};
eventSource.onerror = (err) => {
console.error('SSE 错误:', err);
eventSource.close();
};
// 关闭连接
// eventSource.close();
26. fetch 和 EventSource 实现流式输出的区别是什么?
答:
表格
| 维度 | EventSource | fetch |
|---|---|---|
| 协议 | 基于 HTTP/1.1,只支持 GET 请求 | 支持 HTTP/1.1/2/3,支持 GET/POST 等方法 |
| 连接方式 | 自动重连,服务端可发送事件类型 | 需手动处理重连,无内置事件类型 |
| 数据格式 | 固定的 text/event-stream 格式 | 自定义格式(如 JSON、纯文本) |
| 浏览器支持 | 所有现代浏览器支持 | 支持度高,可配合 ReadableStream 使用 |
| 适用场景 | 服务端主动推送、消息通知 | 大文件下载、LLM 流式输出等复杂场景 |
四、Next.js/ TS 相关(27-37 题)
27. 项目是小程序还是 H5?
答:(根据实际情况回答,比如)我们项目是 H5 应用,使用 Next.js 开发,支持 SSR/SSG。
28. 为什么选择 Next 15?
答:
- App Router 稳定:Next 15 中 App Router 成为默认,支持 React Server Components,减少客户端 JS 体积。
- Server Actions:内置服务端交互方案,简化表单提交、数据请求,避免手写 API。
- 增量静态再生成(ISR) :支持静态页面的增量更新,兼顾性能和实时性。
- 性能优化:内置图片优化、字体优化、路由预加载,提升用户体验。
- TypeScript 友好:内置 TS 支持,类型推断完善,开发体验好。
29. 使用 Next 会不会给服务器增加压力?
答:
-
会有一定压力,但可以通过优化缓解:
- 对于静态页面,使用 SSG/ISR 生成静态文件,服务器只需托管静态资源,压力很小。
- 对于 SSR 页面,会在服务器渲染 HTML,增加 CPU 压力,但可以通过缓存(如 CDN、Redis)缓解。
- Next 支持边缘运行时(Edge Runtime),在 CDN 节点渲染,分散服务器压力。
30. 项目中使用的是 Page Router 还是 App Router?
答:我们项目使用的是 App Router,它是 Next 推荐的路由方案,支持 React Server Components、嵌套布局、加载状态等新特性。
31. Next.js 中文件夹即路由的实现原理?
答:App Router 基于文件系统路由,规则如下:
-
以
app/目录为根,文件夹结构对应 URL 路径。 -
特殊文件:
page.tsx:对应路由的页面组件,决定该路径的内容。layout.tsx:对应路由的布局组件,嵌套路由共享布局。loading.tsx:路由加载时的骨架屏组件。error.tsx:路由错误时的错误边界组件。
-
动态路由:用
[param]命名文件夹,对应 URL 中的动态参数。Next.js 会自动扫描app/目录下的文件,生成路由表,映射 URL 到对应的组件。
32. TS 使用过吗?
答:用过,项目中全程使用 TypeScript 开发,用于类型检查、提升代码可维护性和开发体验。
33. interface 和 type 的区别是什么?
答:
表格
| 维度 | interface | type | |
|---|---|---|---|
| 扩展方式 | 用 extends 继承,支持声明合并 | 用 & 交叉类型扩展,不支持声明合并 | |
| 适用场景 | 主要用于对象 / 类的类型定义 | 可定义对象、联合类型、元组、基本类型等 | |
| 声明合并 | 支持,多个同名 interface 会合并 | 不支持,同名 type 会报错 | |
| 计算属性 | 不支持 | 支持,如 `type Keys = 'a' | 'b'; type Obj = { [key in Keys]: string }` |
34. 如何提取一个函数的参数类型?
答:使用 TypeScript 内置工具类型 Parameters<T>:
ts
function add(a: number, b: number): number {
return a + b;
}
type AddParams = Parameters<typeof add>; // [number, number]
35. 如果要封装一个工具类型,会怎么做?
答:根据需求,使用 TypeScript 的泛型、条件类型、映射类型等特性封装。示例:封装一个提取函数返回类型的工具类型(类似 ReturnType):
ts
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function add(a: number, b: number): number {
return a + b;
}
type AddReturn = MyReturnType<typeof add>; // number
36. Next.js 中的水合和同构你了解吗?
答:
-
同构(Isomorphic) :代码可以同时在服务端和客户端运行,Next.js 中服务端渲染 HTML,客户端复用服务端渲染的代码进行交互绑定。
-
水合(Hydration) :客户端加载 JS 后,将服务端渲染的静态 HTML 转换为可交互的 React 组件的过程。
- 服务端渲染生成 HTML 字符串,返回给客户端。
- 客户端加载 JS,解析组件,将事件处理函数绑定到 DOM 上,使页面可交互。
- 水合过程中,React 会对比服务端渲染的 HTML 和客户端渲染的虚拟 DOM,确保一致,否则会报错。
37. 水合过程中,服务端的数据如何给到客户端?
答:
- SSR 场景:服务端渲染时,通过
getServerSideProps(Page Router)或 Server Components 获取数据,渲染到 HTML 中,客户端水合时,Next.js 会将数据序列化后注入到 HTML 的__NEXT_DATA__中,客户端 JS 读取该数据,用于水合。 - SSG/ISR 场景:构建时获取数据,生成静态 HTML,数据直接嵌入 HTML 中,客户端直接使用。
- App Router:Server Components 直接在服务端获取数据,渲染 HTML,客户端 Client Components 可以通过 props 接收服务端传递的数据,无需额外请求。