no message

This commit is contained in:
WRSNDM\Administrator 2026-01-04 21:19:13 +08:00
parent 724a5ec624
commit a22eeeb940
3 changed files with 2578 additions and 0 deletions

236
react/README.md Normal file
View File

@ -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 = <div className="foo">{isShow && <Span />}</div>;
// 编译为: 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 15Stack 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 使用双端 DiffVue 3 使用**最长递增子序列 (LIS)** 算法处理乱序移动,效率极高。
**React 原理:**
React Fiber 的 Diff 算法Reconciliation相对简单采用**单向遍历**。
1. **仅右移**React 在对比数组列表时,采用 `lastPlacedIndex` 指针。如果新节点在旧集合中存在且位置靠后,则不动;如果位置靠前,则向后移动。
2. **为什么不用双端?**Fiber 结构是单向链表Sibling 指针),很难像数组那样方便地从尾部开始对比(没有反向指针)。
3. **Key 的重要性**:和 Vue 一样Key 是识别节点的唯一标识。没有 KeyReact 只能按索引对比,导致状态错乱或性能低下。
**总结对比:**
- **Vue**:双端比较 / 最长递增子序列 -> 移动次数最少,算法复杂度稍高但 DOM 操作最少。
- **React**:单向遍历 / 右移策略 -> 算法简单,但在特定逆序场景下 DOM 移动次数可能多于 Vue。
---
## 7. TypeScript 实战差异
### Q9: React.FC 还需要用吗Hooks 怎么定义泛型?
**Vue 3 视角解析:**
Vue `defineComponent``<script setup lang="ts">` 自动推导类型Props 定义非常清晰。
**React 最佳实践:**
1. **React.FC (FunctionComponent)**
- **不推荐**React 18 以前包含隐式 `children`容易导致类型不严谨React 18 后移除了,但手动写 Props 类型更纯粹)。
- **推荐写法**:直接定义 Props 接口。
```typescript
interface Props {
title: string;
children?: React.ReactNode; // 显式声明 children
}
const MyComp = ({ title, children }: Props) => { ... }
```
2. **Hooks 泛型**
```typescript
// useState
const [user, setUser] = useState<User | null>(null);
// useRef (DOM)
const inputRef = useRef<HTMLInputElement>(null);
// forwardRef (难点)
const MyInput = forwardRef<HTMLInputElement, Props>((props, ref) => { ... });
```
---
## 总结:给 Vue 开发者的 React 学习建议
1. **忘掉"自动"**习惯手动管理依赖useEffect deps、手动优化性能memo/useCallback
2. **拥抱"函数式"**:理解闭包、纯函数、不可变数据。
3. **深入 Fiber**:这是 React 区别于其他框架最核心的底层技术,也是高级面试必问。

240
ts/README.md Normal file
View File

@ -0,0 +1,240 @@
# TypeScript 深度面试题及解析
> 本文档汇集了高频且具有深度的 TypeScript 面试题,旨在帮助开发者掌握 TS 类型系统的核心原理及高级应用。
## 目录
1. [基础概念与类型系统](#1-基础概念与类型系统)
2. [高级类型与类型体操](#2-高级类型与类型体操)
3. [TS 模块与工程化](#3-ts-模块与工程化)
4. [React/Vue 实战场景](#4-reactvue-实战场景)
---
## 1. 基础概念与类型系统
### Q1: `interface``type` (Type Alias) 的核心区别是什么?在什么场景下应该优先使用哪一个?
**解析:**
虽然两者在很多场景下可以互换,但设计初衷不同。
* **Interface (接口)**:主要用于定义**对象**的形状Shape和类的契约。它支持**声明合并 (Declaration Merging)**,这对于扩展第三方库的类型非常重要。
* **Type (类型别名)**:不仅仅是对象,它可以定义基本类型别名、联合类型 (Union)、元组 (Tuple) 等。它**不支持**声明合并。
**深度对比:**
1. **扩展方式**`interface` 使用 `extends``type` 使用交叉类型 `&`
2. **声明合并**:同名的 `interface` 会自动合并,同名的 `type` 会报错。
3. **索引签名**`type` 在某些复杂的映射类型中表现更好,而 `interface` 在索引签名上有时会有限制(例如不能直接映射非对象类型)。
**最佳实践:**
* **编写库或公共 API 时**,优先使用 `interface`,以便使用者可以通过声明合并扩展类型。
* **定义联合类型、复杂的工具类型、提取类型时**,必须使用 `type`
* **一般应用开发中**,保持一致即可,`type` 因其灵活性目前更受欢迎。
### Q2: 解释 TypeScript 中的 `any``unknown``never` 的区别
**解析:**
这是考察对类型安全理解深度的经典问题。
* **`any` (任意类型)**
* **含义**:关闭类型检查。任何类型都能赋值给 `any``any` 也能赋值给任何类型。
* **风险**:使用 `any` 会失去 TS 的保护,像是在写普通的 JS。应尽量避免。
* **`unknown` (未知类型)**
* **含义**:类型安全的 `any`。任何类型都能赋值给 `unknown`,但 `unknown` **不能**直接赋值给其他类型(除了 `any``unknown`),也不能直接调用其方法。
* **使用**:必须先进行**类型收窄 (Type Narrowing)**(如 `typeof`, `instanceof`, 类型断言)才能使用。
* **`never` (永不存在的类型)**
* **含义**:表示那些永远不存在的值的类型。例如:抛出异常的函数返回值、无限循环的函数返回值、联合类型中被排除后的剩余类型。
* **特性**`never` 是所有类型的子类型Bottom Type可以赋值给任何类型但没有类型可以赋值给 `never`
* **应用**:常用于**全面性检查 (Exhaustiveness Checking)**。
**代码示例:全面性检查**
```typescript
type Shape = 'circle' | 'square';
function getArea(shape: Shape) {
switch (shape) {
case 'circle': return Math.PI;
case 'square': return 1;
default:
// 如果 Shape 扩展了 'triangle',这里会报错,因为 'triangle' 不能赋值给 never
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
```
### Q3: 什么是协变 (Covariance) 和逆变 (Contravariance)?在 TS 中如何体现?
**解析:**
这是类型系统中非常底层的概念,主要体现在**函数类型**的兼容性上。
* **协变 (Covariance)**:允许子类型赋值给父类型。
* **体现****对象属性**、**函数返回值**。
* 如果 `Dog extends Animal`,那么 `() => Dog` 可以赋值给 `() => Animal`
* **逆变 (Contravariance)**:允许父类型赋值给子类型(看起来是反直觉的)。
* **体现****函数参数**(在开启 `strictFunctionTypes` 时)。
* 如果 `Dog extends Animal`,那么 `(animal: Animal) => void` 可以赋值给 `(dog: Dog) => void`
* **理解**:如果一个函数能处理所有的 `Animal`,那它一定能处理 `Dog`。反之,如果一个函数只能处理 `Dog`,你传给它一个 `Cat` (也是 Animal) 它就崩了。
**口诀:**
> **参数逆变,返回值协变。**
---
## 2. 高级类型与类型体操
### Q4: 如何实现一个 `DeepReadonly<T>` 工具类型?
**解析:**
考察递归类型和映射类型 (Mapped Types)。
```typescript
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? (T[P] extends Function ? T[P] : DeepReadonly<T[P]>) // 处理函数和对象的递归
: T[P];
};
```
*注意:实际面试中,简单的递归对象即可,处理 Function 的情况属于加分项。*
### Q5: 解释 `infer` 关键字的作用,并写一个提取 Promise 返回值的类型 `UnwrapPromise<T>`
**解析:**
`infer` 用于在条件类型 (Conditional Types) 中声明一个类型变量,用于**推断**类型。
```typescript
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
// 进阶:处理嵌套 Promise (递归)
type DeepUnwrapPromise<T> = T extends Promise<infer U>
? DeepUnwrapPromise<U>
: T;
```
### Q6: 什么是分布式条件类型 (Distributive Conditional Types)
**解析:**
当条件类型作用于**泛型**,并且传入的是**联合类型**时,条件类型会**分发**Distribute执行。
**公式:**
`(A | B) extends T ? X : Y`
=> `(A extends T ? X : Y) | (B extends T ? X : Y)`
**应用Exclude 的实现**
```typescript
type MyExclude<T, U> = T extends U ? never : T;
type Result = MyExclude<'a' | 'b' | 'c', 'a'>;
// 过程:
// ('a' extends 'a' ? never : 'a') |
// ('b' extends 'a' ? never : 'b') |
// ('c' extends 'a' ? never : 'c')
// => never | 'b' | 'c'
// => 'b' | 'c'
```
**如何阻止分发?**
用元组包裹:`[T] extends [U] ? X : Y`
---
## 3. TS 模块与工程化
### Q7: `const enum` 和普通 `enum` 有什么区别?
**解析:**
* **普通 `enum`**
* 运行时**存在**。会编译成一个双向映射的 JavaScript 对象(如果是数字枚举)。
* 优点:可以反向查找(值 -> 键)。
* 缺点:增加 bundle 体积。
* **`const enum` (常量枚举)**
* 运行时**不存在**。在编译阶段会被**内联**Inlined替换为具体的值。
* 优点:零运行时开销,体积小。
* 缺点:不能反向查找;在某些构建工具(如 Babel 早期版本)或 `isolatedModules` 模式下可能存在兼容性问题(因为没有运行时对象)。
### Q8: 什么是类型守卫 (Type Guard)?有哪些方式可以实现?
**解析:**
类型守卫用于在运行时检查类型,从而在特定作用域内**收窄**类型。
1. **`typeof`**: 识别基础类型 (`string`, `number` 等)。
2. **`instanceof`**: 识别类实例。
3. **`in` 关键字**: 检查对象是否包含某属性。
4. **自定义类型谓词 (Type Predicate)**: `parameter is Type`
**示例:自定义类型谓词**
```typescript
interface Fish { swim: () => void; }
interface Bird { fly: () => void; }
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
// 使用
if (isFish(pet)) {
pet.swim(); // TS 知道这里 pet 是 Fish
}
```
---
## 4. React/Vue 实战场景
### Q9: 在 React 中,`React.FC` 还需要用吗?为什么现在不推荐了?
**解析:**
在 React 18 之前,`React.FC` (FunctionComponent) 曾是定义组件的标准方式,但现在社区(包括 Create React App 模板)倾向于直接声明函数参数类型。
**`React.FC` 的问题(旧版):**
1. **隐式 `children`**:旧版 `React.FC` 默认包含了 `children` 属性,即使你的组件不需要 children这破坏了类型严格性。
2. **泛型支持差**:给 `React.FC` 组件添加泛型比较麻烦。
3. **组件静态属性**`defaultProps``React.FC` 下处理较为复杂。
**推荐写法:**
```typescript
interface Props {
title: string;
children?: React.ReactNode; // 显式声明 children
}
const MyComponent = ({ title, children }: Props) => {
return <div>{title}{children}</div>;
};
```
### Q10: Vue 3 中 `defineProps` 如何结合 TypeScript 使用?运行时声明 vs 类型声明?
**解析:**
Vue 3 提供了两种方式定义 props
1. **运行时声明 (Runtime Declaration)**:
```typescript
const props = defineProps({
foo: { type: String, required: true }
})
```
* 优点:运行时有校验。
* 缺点TS 类型推导不如纯类型声明直接。
2. **基于类型的声明 (Type-based Declaration) - 推荐**:
```typescript
const props = defineProps<{
foo: string
bar?: number
}>()
```
* 优点:纯 TS 语法DX开发体验更好支持复杂类型。
* 注意:`withDefaults` 宏用于设置默认值。
---
> **总结**TypeScript 面试的核心在于理解**类型系统是图灵完备的**(可以通过类型编程解决复杂问题),以及理解**编译时类型**与**运行时代码**的边界Erasure

2102
vue3/README.md Normal file

File diff suppressed because it is too large Load Diff