From a22eeeb94072b276931ce2979e27fc045f13d1c0 Mon Sep 17 00:00:00 2001 From: "WRSNDM\\Administrator" Date: Sun, 4 Jan 2026 21:19:13 +0800 Subject: [PATCH] no message --- react/README.md | 236 ++++++ ts/README.md | 240 ++++++ vue3/README.md | 2102 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2578 insertions(+) create mode 100644 react/README.md create mode 100644 ts/README.md create mode 100644 vue3/README.md diff --git a/react/README.md b/react/README.md new file mode 100644 index 0000000..ba00c1b --- /dev/null +++ b/react/README.md @@ -0,0 +1,236 @@ +# React 深度面试题及解析 (Vue 3 开发者视角版) + +> 本文档专为熟悉 Vue 3 + TypeScript 的开发者编写。我们将通过对比 Vue 3 的核心概念(响应式、Composition API、Diff 算法等)来深度解析 React 的原理,帮助你快速建立映射关系并掌握 React 高频面试点。 + +--- + +## 目录 + +1. [核心设计理念对比](#1-核心设计理念对比) +2. [Hooks 与 Composition API](#2-hooks-与-composition-api) +3. [Fiber 架构与并发模式](#3-fiber-架构与并发模式) +4. [状态管理与组件通信](#4-状态管理与组件通信) +5. [性能优化机制](#5-性能优化机制) +6. [Diff 算法深度解析](#6-diff-算法深度解析) +7. [TypeScript 实战差异](#7-typescript-实战差异) + +--- + +## 1. 核心设计理念对比 + +### Q1: React 的"不可变数据" (Immutable) 与 Vue 的"可变响应式" (Mutable) 有什么本质区别?为什么 React 需要它? + +**Vue 3 视角解析:** +在 Vue 3 中,我们习惯直接修改对象 `state.count++`,Proxy 会拦截这个操作并自动触发更新。这是 **"Mutable + 细粒度依赖收集"**。 + +**React 原理:** +React 是 **"Immutable + 全量检测"**(组件级)。 +- **不可变性**:React 中不能直接修改 state (`this.state.count++` 是无效的),必须调用 `setState` 传入一个新的值。 +- **为什么**:React 没有细粒度的依赖收集系统。当状态变化时,React 默认不知道具体哪个属性变了,它只知道"组件需要更新了"。通过比较 `oldState === newState` (浅比较) 来决定是否需要重新渲染。如果数据是可变的,引用没变但内容变了,React 就无法快速感知变化,或者需要昂贵的深比较。 + +**面试回答要点:** +1. **数据流向**:React 强调单向数据流和不可变性,通过 `setState` 触发更新,生成全新的 Virtual DOM 树。 +2. **更新策略**:Vue 是"推"(Push)模式,依赖变了自动推送到组件;React 是"拉"(Pull)模式,状态变了,React 重新执行组件函数,产出新 UI。 +3. **心智模型**:React 组件本质是 `UI = f(state)`,每次 Render 都是一次全新的函数调用,闭包在其中扮演核心角色(Capture Value 特性)。 + +--- + +### Q2: JSX 与 Vue Template 的编译结果有什么不同? + +**Vue 3 视角解析:** +Vue Template 编译成 Render Function,但 Vue 做了大量**编译时优化**(PatchFlags、静态提升、Block Tree),能静态分析出哪些节点是动态的。 + +**React 原理:** +JSX 本质是 `React.createElement` (或 `_jsx`) 的语法糖。 +- **灵活性**:JSX 是完全的 JavaScript,拥有 JS 的全部能力(变量、逻辑控制)。 +- **优化难度**:因为太灵活(动态性太强),React 很难像 Vue 那样做极致的编译时优化(虽然 React Compiler/React Forget 正在尝试解决这个问题)。React 更多依赖运行时的 Fiber 架构来调度更新。 + +**代码对比:** + +```typescript +// React JSX +const element =
{isShow && }
; +// 编译为: React.createElement('div', { className: 'foo' }, isShow && React.createElement(Span)) +``` + +--- + +## 2. Hooks 与 Composition API + +### Q3: `useEffect` 的依赖数组 (Dependency Array) 为什么容易产生闭包陷阱?与 Vue `watch` 有何不同? + +**Vue 3 视角解析:** +Vue 的 `watchEffect` 自动收集依赖,`watch` 也可以直观地监听 ref。Vue 的组件 setup 只运行一次,闭包问题较少。 + +**React 原理:** +React 函数组件**每次渲染都会重新执行**。 +- **闭包陷阱**:如果在 `useEffect` 中使用了某个 state 但没放入依赖数组,`useEffect` 内部引用的就是**上一次渲染时的旧变量**(闭包捕获了旧值)。 +- **Stale Closure**:这是 React Hooks 最核心的痛点之一。 + +**示例:** + +```typescript +function Counter() { + const [count, setCount] = useState(0); + + useEffect(() => { + const timer = setInterval(() => { + // 错误:这里的 count 永远是 0 (第一次渲染时的闭包) + console.log(count); + // 修正:setCount(c => c + 1) 或将 count 加入依赖数组 + }, 1000); + return () => clearInterval(timer); + }, []); // [] 导致 effect 只执行一次,捕获了初始作用域 +} +``` + +**面试回答要点:** +1. **执行机制**:Hooks 依赖于函数组件的多次执行,利用闭包保存状态。 +2. **依赖数组**:必须诚实地列出所有依赖,否则会读取到旧值。 +3. **对比 Vue**:Vue 的 setup 仅执行一次,响应式数据是引用的 Proxy,不存在"旧值"问题,心智负担更小;React 需要开发者手动维护依赖。 + +### Q4: 为什么 React Hooks 不能写在条件语句(if/for)里? + +**Vue 3 视角解析:** +Vue Composition API (`ref`, `reactive`) 可以随便写,因为 setup 只跑一次,变量声明了就在那。 + +**React 原理:** +React 内部通过**链表**(Linked List)来存储 Hooks 的状态。 +- **顺序很重要**:React 没有名字来区分 `useState(1)` 和 `useState(2)`,它完全依赖**调用顺序**来对应状态。 +- 如果放在 `if` 里,某次渲染跳过了一个 Hook,后面的 Hook 拿到的状态就会错位(比如把 `name` 的 state 给了 `age`)。 + +**源码简化逻辑:** +```javascript +// 伪代码 +let hooks = []; +let currentHookIndex = 0; + +function useState(initial) { + const hook = hooks[currentHookIndex] || { state: initial }; + hooks[currentHookIndex] = hook; + currentHookIndex++; // 索引自增,依赖顺序 + return [hook.state, setState]; +} +``` + +--- + +## 3. Fiber 架构与并发模式 + +### Q5: 什么是 React Fiber?它解决了什么问题?(对比 Vue 的更新机制) + +**Vue 3 视角解析:** +Vue 的更新是**细粒度**的。组件级 Watcher 知道具体哪个组件变了,更新过程通常很快,不需要"时间切片"这种复杂机制。 + +**React 原理:** +React 的更新通常是**全量递归**(从根节点或 Context Provider 开始)。在 React 15(Stack Reconciler)时代,一旦开始 Diff,就必须递归到底,中间无法中断。如果树很深,JS 线程被占用超过 16ms,页面就会掉帧卡顿。 + +**Fiber 架构:** +1. **数据结构**:将递归的树结构转变为**链表**结构(Fiber Node)。这使得遍历可以**暂停、中止、恢复**。 +2. **时间切片 (Time Slicing)**:将渲染任务拆分成小块。浏览器空闲时(`requestIdleCallback` 概念)执行一部分 Diff,有高优先级任务(如用户输入)插队时,暂停低优先级任务。 +3. **双缓存 (Double Buffering)**:在内存中构建好新的 Fiber 树(workInProgress tree),构建完成后一次性替换 Current tree,减少页面闪烁。 + +**面试回答要点:** +- **核心目标**:实现**并发渲染 (Concurrent Rendering)**,解决 CPU 密集型更新导致的页面卡顿。 +- **实现方式**:将同步的递归 Diff 改为异步的可中断遍历。 + +--- + +## 4. 状态管理与组件通信 + +### Q6: Redux/Zustand 与 Vuex/Pinia 的区别? + +**Vue 3 视角解析:** +Pinia 本质是基于 Proxy 的全局响应式对象,非常直观,修改 state 直接赋值即可。 + +**React 原理:** +Redux 是典型的**单向数据流** + **不可变数据**。 +- **Action -> Reducer -> New Store**。 +- 必须返回新的 State 对象,不能直接修改。 +- **Context API**:React 自带的跨组件通信,但有性能缺陷(Provider 更新,所有 Consumer 强制重渲染),通常配合 `useMemo` 优化,或者使用 Zustand/Recoil 等库。 + +**Zustand (推荐)**: +Zustand 的用法非常像 Vue 3 的 Composition API + Pinia,去除了 Redux 的样板代码,支持直接修改(通过 immer)或返回新对象,是目前 React 生态中最符合直觉的库。 + +--- + +## 5. 性能优化机制 + +### Q7: `useMemo` 和 `useCallback` 是做什么的?Vue 为什么很少需要它们? + +**Vue 3 视角解析:** +Vue 的 `computed` 自动缓存,组件更新也是自动精确控制的。子组件 props 没变,Vue 默认就不会去递归更新子组件(除非插槽等情况)。 + +**React 原理:** +React 组件默认行为:**父组件更新,所有子组件无条件重新渲染**。 +- **性能浪费**:即使子组件 props 没变,也会运行。 +- **React.memo**:高阶组件,用于包裹子组件,做 Props 的浅比较(类似 Vue 的默认行为)。 +- **useCallback**: + - 问题:父组件每次 Render,定义的函数 `const handleClick = () => {}` 都是**新引用**。 + - 后果:传给子组件时,`React.memo` 发现 props.onClick 变了,导致子组件重渲染。 + - 解决:`useCallback` 缓存函数引用,只有依赖变了才生成新函数。 +- **useMemo**:缓存计算结果(类似 Vue `computed`),避免每次 Render 都进行昂贵计算。 + +**面试回答要点:** +- React 的优化是**手动挡**(开发者决定何时缓存),Vue 是**自动挡**(响应式系统自动处理)。 +- 滥用 `useMemo` 也有开销,只在昂贵计算或引用稳定性关键时使用。 + +--- + +## 6. Diff 算法深度解析 + +### Q8: React Diff 算法与 Vue Diff 算法的区别? + +**Vue 3 视角解析:** +Vue 2 使用双端 Diff,Vue 3 使用**最长递增子序列 (LIS)** 算法处理乱序移动,效率极高。 + +**React 原理:** +React Fiber 的 Diff 算法(Reconciliation)相对简单,采用**单向遍历**。 +1. **仅右移**:React 在对比数组列表时,采用 `lastPlacedIndex` 指针。如果新节点在旧集合中存在且位置靠后,则不动;如果位置靠前,则向后移动。 +2. **为什么不用双端?**:Fiber 结构是单向链表(Sibling 指针),很难像数组那样方便地从尾部开始对比(没有反向指针)。 +3. **Key 的重要性**:和 Vue 一样,Key 是识别节点的唯一标识。没有 Key,React 只能按索引对比,导致状态错乱或性能低下。 + +**总结对比:** +- **Vue**:双端比较 / 最长递增子序列 -> 移动次数最少,算法复杂度稍高但 DOM 操作最少。 +- **React**:单向遍历 / 右移策略 -> 算法简单,但在特定逆序场景下 DOM 移动次数可能多于 Vue。 + +--- + +## 7. TypeScript 实战差异 + +### Q9: React.FC 还需要用吗?Hooks 怎么定义泛型? + +**Vue 3 视角解析:** +Vue `defineComponent` 或 ` +``` + +**互操作机制:** + +```javascript +// Vapor 组件在 VDOM 中的表现 +// Vue 会为 Vapor 组件创建一个特殊的 VNode 包装器 + +const VaporComponentVNode = { + type: VaporComponent, + __vapor: true, // 标记为 Vapor 组件 + + // 挂载时直接调用 Vapor 渲染函数 + mount(container) { + const instance = createVaporInstance(VaporComponent) + const root = instance.render() + container.appendChild(root) + }, + + // 更新由 Vapor 的响应式系统自动处理 + // VDOM 侧不需要 diff 这个组件的内部 +} +``` + +**选择策略:** + +| 组件类型 | 推荐模式 | 原因 | +|----------|----------|------| +| 性能关键组件 | Vapor | 细粒度更新,零 VDOM 开销 | +| 高度动态组件 | VDOM | 更灵活的动态渲染 | +| 第三方组件库 | VDOM | 兼容性 | +| 简单展示组件 | Vapor | 更小的运行时 | +| 复杂状态组件 | 视情况 | 评估更新频率和模式 | + +--- + +### Q22: Vapor Mode 的优势、局限性和适用场景是什么? + +**答案:** + +**优势:** + +```javascript +// 1. 更小的运行时体积 +// VDOM 运行时: ~50KB (gzip ~16KB) +// Vapor 运行时: ~6KB (gzip ~2KB) + +// 2. 更快的更新性能 +// 基准测试(更新 1000 行表格): +// VDOM: ~15ms +// Vapor: ~3ms(快 5 倍) + +// 3. 更低的内存占用 +// 无 VNode 对象创建,GC 压力更小 + +// 4. 更快的首次渲染 +// 无需创建完整的 VNode 树 +``` + +**局限性:** + +```javascript +// 1. 动态组件支持有限 +// ❌ 不支持 + + +// 2. 渲染函数/JSX 不支持 +// Vapor 依赖模板编译,不支持手写渲染函数 +export default { + render() { + return h('div', this.msg) // ❌ 不能使用 Vapor + } +} + +// 3. 部分动态指令受限 +// 需要编译时确定的指令 + +// 4. 生态兼容性 +// 部分依赖 VDOM 的组件库可能不兼容 +``` + +**适用场景评估:** + +```javascript +// ✅ 推荐使用 Vapor Mode +- 性能敏感的移动端应用 +- 大量数据展示的表格/列表 +- 嵌入式/资源受限环境 +- 追求极致首屏性能的场景 +- 简单的交互组件 + +// ⚠️ 谨慎使用 +- 高度动态的组件结构 +- 需要手写渲染函数的场景 +- 重度依赖第三方 UI 库 + +// ❌ 不适合 +- 需要 JSX 的项目 +- 动态组件为核心的应用 +``` + +**性能基准对比:** + +``` +操作类型 VDOM Vapor 提升 +───────────────────────────────────────────── +创建 1000 行 45ms 12ms 3.8x +更新全部行 38ms 8ms 4.8x +更新单行 4ms 0.3ms 13.3x +交换两行 6ms 0.8ms 7.5x +删除行 8ms 2ms 4.0x +内存占用 (1000行) 12MB 3MB 4.0x +运行时体积 50KB 6KB 8.3x +``` + +--- + +### Q23: 如何在项目中渐进式采用 Vapor Mode? + +**答案:** + +**步骤 1:升级依赖** + +```bash +# 确保 Vue 版本支持 Vapor(3.5+) +npm install vue@latest +npm install @vitejs/plugin-vue@latest +``` + +**步骤 2:配置构建工具** + +```javascript +// vite.config.js +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [ + vue({ + vapor: true, + // 可选:指定 Vapor 组件的文件模式 + vaporPatterns: [ + '**/*.vapor.vue', + '**/vapor/**/*.vue' + ] + }) + ] +}) +``` + +**步骤 3:识别适合迁移的组件** + +```javascript +// 分析工具:识别性能热点 +// 1. 使用 Vue Devtools Performance 面板 +// 2. 找出频繁更新的组件 +// 3. 评估组件复杂度 + +// 适合迁移的特征: +// - 纯展示组件 +// - 列表/表格组件 +// - 频繁更新的状态展示 +// - 不依赖动态组件/渲染函数 +``` + +**步骤 4:逐步迁移** + +```vue + + + + + + +``` + +**步骤 5:性能验证** + +```javascript +// 添加性能监控 +import { onMounted, onUpdated } from 'vue' + +let updateStart = 0 + +onBeforeUpdate(() => { + updateStart = performance.now() +}) + +onUpdated(() => { + const duration = performance.now() - updateStart + console.log(`Update took: ${duration.toFixed(2)}ms`) + + // 上报到监控系统 + reportMetric('component_update', duration) +}) +``` + +**迁移策略建议:** + +``` +阶段 1(试点) +├── 选择 1-2 个性能关键组件 +├── 转换为 Vapor Mode +└── 验证性能提升和功能正确性 + +阶段 2(扩展) +├── 迁移更多展示型组件 +├── 建立 Vapor 组件开发规范 +└── 团队培训 + +阶段 3(优化) +├── 监控生产环境性能 +├── 根据数据调整策略 +└── 持续迭代优化 +``` + +--- + +## 9. Vue 3 与 Vue 2 核心区别 + +### Q24: Vue 3 相比 Vue 2 做了哪些重大改进? + +**答案:** + +**1. 架构层面:** + +| 改进点 | Vue 2 | Vue 3 | +|--------|-------|-------| +| 响应式系统 | Object.defineProperty | Proxy | +| 代码组织 | Options API | Composition API | +| 源码结构 | 单一仓库 | Monorepo(@vue/reactivity 可独立使用) | +| TypeScript | 额外配置 | 原生支持 | +| Tree-shaking | 有限 | 全面支持 | + +**2. 新增特性:** + +```javascript +// Teleport:传送组件到任意 DOM 位置 + + + + +// Suspense:异步组件加载状态处理 + + + + + +// 多根节点(Fragments) + + +// createRenderer:自定义渲染器 +import { createRenderer } from '@vue/runtime-core' +const { render } = createRenderer({ + createElement(type) { /* ... */ }, + insert(el, parent) { /* ... */ }, + // ... +}) +``` + +**3. 破坏性变更:** + +```javascript +// v-model 变化 +// Vue 2: value + input +// Vue 3: modelValue + update:modelValue + +// 移除的 API +// $on, $off, $once(使用 mitt 替代) +// $children(使用 ref + expose) +// $listeners(合并到 $attrs) +// filters(使用 computed 或方法) + +// v-for 和 v-if 优先级 +// Vue 2: v-for 优先 +// Vue 3: v-if 优先 + +// 生命周期重命名 +// destroyed → unmounted +// beforeDestroy → beforeUnmount +``` + +--- + +## 10. 实战场景题 + +### Q25: 如何实现一个防抖的搜索输入框? + +**答案:** + +```javascript +// 方案1:使用 watchEffect + 自定义防抖 +import { ref, watchEffect } from 'vue' + +function useDebouncedSearch(delay = 300) { + const searchTerm = ref('') + const debouncedTerm = ref('') + + watchEffect((onCleanup) => { + const timer = setTimeout(() => { + debouncedTerm.value = searchTerm.value + }, delay) + + onCleanup(() => clearTimeout(timer)) + }) + + return { searchTerm, debouncedTerm } +} + +// 使用 +const { searchTerm, debouncedTerm } = useDebouncedSearch(300) + +watch(debouncedTerm, async (term) => { + if (term) { + results.value = await fetchResults(term) + } +}) + +// 方案2:使用 VueUse +import { useDebounceFn, refDebounced } from '@vueuse/core' + +const searchTerm = ref('') +const debouncedTerm = refDebounced(searchTerm, 300) + +// 或者防抖函数 +const debouncedSearch = useDebounceFn((term) => { + fetchResults(term) +}, 300) + +watch(searchTerm, debouncedSearch) +``` + +--- + +### Q26: 如何封装一个可复用的 useFetch 组合式函数? + +**答案:** + +```javascript +import { ref, shallowRef, watchEffect, toValue } from 'vue' + +export function useFetch(url, options = {}) { + const data = shallowRef(null) + const error = shallowRef(null) + const loading = ref(false) + + const execute = async () => { + loading.value = true + error.value = null + + const controller = new AbortController() + + try { + const response = await fetch(toValue(url), { + ...options, + signal: controller.signal + }) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + data.value = await response.json() + } catch (e) { + if (e.name !== 'AbortError') { + error.value = e + } + } finally { + loading.value = false + } + + return controller + } + + // 自动执行并支持响应式 URL + watchEffect((onCleanup) => { + const controller = execute() + onCleanup(() => controller?.abort()) + }) + + const refetch = () => execute() + + return { + data, + error, + loading, + refetch + } +} + +// 使用 +const userId = ref(1) +const { data: user, loading, error, refetch } = useFetch( + () => `/api/users/${userId.value}` +) + +// 改变 userId 会自动重新请求 +userId.value = 2 +``` + +--- + +### Q27: 如何实现一个无限滚动列表? + +**答案:** + +```javascript +// useInfiniteScroll.js +import { ref, onMounted, onUnmounted } from 'vue' + +export function useInfiniteScroll(loadMore, options = {}) { + const { + threshold = 100, + container = null + } = options + + const loading = ref(false) + const finished = ref(false) + + const handleScroll = async (e) => { + if (loading.value || finished.value) return + + const target = container?.value || document.documentElement + const scrollHeight = target.scrollHeight + const scrollTop = target.scrollTop + const clientHeight = target.clientHeight + + if (scrollHeight - scrollTop - clientHeight < threshold) { + loading.value = true + const hasMore = await loadMore() + loading.value = false + + if (!hasMore) { + finished.value = false + } + } + } + + onMounted(() => { + const target = container?.value || window + target.addEventListener('scroll', handleScroll) + }) + + onUnmounted(() => { + const target = container?.value || window + target.removeEventListener('scroll', handleScroll) + }) + + return { loading, finished } +} + +// 使用 Intersection Observer 的更优方案 +export function useInfiniteScrollV2(callback) { + const target = ref(null) + const loading = ref(false) + + let observer = null + + onMounted(() => { + observer = new IntersectionObserver(async ([entry]) => { + if (entry.isIntersecting && !loading.value) { + loading.value = true + await callback() + loading.value = false + } + }) + + if (target.value) { + observer.observe(target.value) + } + }) + + onUnmounted(() => { + observer?.disconnect() + }) + + return { target, loading } +} + +// 使用 + + + +``` + +--- + +### Q28: 如何处理 Vue 3 中的错误边界? + +**答案:** + +```javascript +// ErrorBoundary.vue + + + + +// 使用 + + + + +// 全局错误处理 +app.config.errorHandler = (err, instance, info) => { + console.error('Global error:', err) + console.log('Component:', instance) + console.log('Error info:', info) + + // 发送到错误监控服务 + Sentry.captureException(err) +} + +// 处理异步错误 +app.config.warnHandler = (msg, instance, trace) => { + console.warn('Warning:', msg) +} +``` + +--- + +## 总结 + +Vue 3 面试重点掌握: + +1. **响应式原理**:Proxy、依赖收集、触发更新的完整流程 +2. **Composition API**:setup、ref/reactive、watch/computed 原理 +3. **编译优化**:静态提升、PatchFlags、Block Tree +4. **Diff 算法**:快速 Diff、最长递增子序列 +5. **性能优化**:v-memo、shallowRef、组件懒加载 +6. **实战能力**:组合式函数封装、状态管理、错误处理 + +--- + +> 持续更新中,欢迎补充!