react-inner
React 原理
国内面试,大厂必考原理�?
::: tip
- 目标不在中大厂的同学,可以略过这一节�?2. �?React 使用尚不熟练的同学,不要在此花费太多精力,先熟悉使用再说�?
:::
::: tip
如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务�?专业、系统、高效、全流程 准备前端面试
:::
JSX 的本质是什么?
参考答�?
::: details
*JSX(JavaScript XML�? 是一�?JavaScript 的语法扩展,允许�?JavaScript 代码中通过�?HTML 语法创建 React 元素。它需要通过 Babel 等工具编译为标准�?JavaScript 代码,最终生�?React 元素对象(React Element),这些元素共同构成虚拟 DOM(Virtual DOM)树�?
核心原理
JSX 编译�?React 元素
JSX 会被转换�?React.createElement()调用(或 React 17+ �?_jsx函数),生成描述 UI 结构的对象(React 元素),而非直接操作真实 DOM�?1
2
3
4
5
6
7// JSX
const element = <h1 className="title">Hello, world!</h1>
// 编译后(React 17 之前�? const element = React.createElement('h1', { className: 'title' }, 'Hello, world!')
// 编译后(React 17+,自动引�?_jsx�? import { jsx as _jsx } from 'react/jsx-runtime'
const element = _jsx('h1', { className: 'title', children: 'Hello, world!' })*虚拟 DOM 的运�?
- React 元素组成虚拟 DOM 树,通过 Diff 算法对比新旧树差异,最终高效更新真�?DOM�? - 虚拟 DOM 是内存中的轻量对象,避免频繁操作真实 DOM 的性能损耗�?
*JSX 的核心特�?
- React 元素组成虚拟 DOM 树,通过 Diff 算法对比新旧树差异,最终高效更新真�?DOM�? - 虚拟 DOM 是内存中的轻量对象,避免频繁操作真实 DOM 的性能损耗�?
*�?HTML 语法�?JavaScript 的融�?
- **表达式嵌�?*:通过
{}嵌入 JavaScript 表达式(如变量、函数调用、三元运算符):1
2const userName = 'Alice'
const element = <p>Hello, {userName.toUpperCase()}</p> - 禁止语句:
{}内不支持if/for等语句,需改用表达式(如三元运算符或逻辑与)�? ```jsx{isLoggedIn ? 'Welcome' : 'Please Login'}1
2
3
4
5
6
7
8
2. **语法规则**
- **属性命�?*:使用驼峰命名(�?`className` 代替 `class`,`htmlFor` 代替 `for`)�? - **闭合标签**:所有标签必须显式闭合(�?`<img />`)�? - **单一根元�?*:JSX 必须有唯一根元素(或用 `<></>` 空标签包裹)�?
3. **安全�?*
- **默认 XSS 防护**:JSX 自动转义嵌入内容中的特殊字符(如 `<` 转为 `<`)�? - **例外场景**:如需渲染原始 HTML,需显式使用 `dangerouslySetInnerHTML`(需谨慎):
```jsx
<div dangerouslySetInnerHTML={{ __html: userContent }} />
- **表达式嵌�?*:通过
编译与工具链
编译流程
JSX 需通过 Babel 编译为浏览器可执行的 JavaScript。典型配置如下:1
2
3
4// .babelrc
{
"presets": ["@babel/preset-react"]
}*React 17+ 的优�?
- 无需手动导入 React:编译器自动引入
_jsx函数�? - 更简洁的编译输出:减少代码体积,提升可读性�?
:::
- 无需手动导入 React:编译器自动引入
参考资�?
::: details
:::
如何理解 React Fiber 架构�?
参考答�?
::: details
- Fiber 架构的本质与设计目标
Fiber �?React 16+ �?*核心算法重写,本质是基于链表的增量式协调模型**。其核心目标并非单纯提升性能,而是重构架构以实现:
- 可中断的异步渲染:将同步递归的调和过程拆解为可暂�?恢复的异步任务�?- **优先级调�?*:高优先级任务(如用户输入)可打断低优先级任务(如数据更新)�?- 并发模式基础:为
Suspense、useTransition等特性提供底层支持�?
- *Fiber 节点的核心设�?
每个组件对应一�?Fiber 节点,构�?*双向链表树结�?*,包含以下关键信息:
- 组件类型:函数组件、类组件或原生标签�?- **状态与副作�?*:Hooks 状态(�?
useState)、生命周期标记(�?useEffect)�?- 调度信息:任务优先级(lane模型)、到期时间(expirationTime)�?- 链表指针:child(子节点)、sibling(兄弟节点)、return(父节点)�?1
2
3
4
5
6
7
8
9
10
11// Fiber 节点结构简化示�?const fiberNode = {
tag: FunctionComponent, // 组件类型
stateNode: ComponentFunc, // 组件实例�?DOM 节点
memoizedState: {
/* Hooks 链表 */
},
pendingProps: {
/* 待处�?props */
},
lanes: Lanes.HighPriority, // 任务优先�? child: nextFiber, // 子节�? sibling: null, // 兄弟节点
return: parentFiber, // 父节�?}
- *Fiber 协调流程(两阶段提交�?
*阶段 1:Reconciliation(协�?渲染阶段�?
可中断的增量计算�? React 将组件树遍历拆解为多�?Fiber 工作单元,通过循环(而非递归)逐个处理�? - 每次循环执行一�?Fiber 节点,生成子 Fiber 并连接成树�? - 通过
requestIdleCallback(或 Scheduler 包)在浏览器空闲时段执行,避免阻塞主线程�?- 对比策略�? 根据key�?type复用节点,标�?Placement(新增)、Update(更新)、Deletion(删除)等副作用�?
阶段 2:Commit(提交阶段)不可中断�?DOM 更新�? 同步执行所有标记的副作用(�?DOM 操作、生命周期调用),确�?UI 一致性�?- **副作用分�?*�? - BeforeMutation:
getSnapshotBeforeUpdate�? - Mutation:DOM 插入/更新/删除�? - Layout:useLayoutEffect、componentDidMount/Update�?
- *优先级调度机�?
React 通过 Lane 模型 管理任务优先级(�?31 个优先级车道):
- **事件优先�?*�?
javascript // 优先级从高到�? ImmediatePriority(用户输入) UserBlockingPriority(悬停、点击) NormalPriority(数据请求) LowPriority(分析日志) IdlePriority(非必要任务�? - 调度策略�? - 高优先级任务可抢占低优先级任务的执行权�? - 过期任务(如 Suspense 回退)会被强制同步执行�?
- *Fiber 架构的优势与局限�?
优势
**流畅的用户体�?*:异步渲染避免主线程阻塞,保障高优先级任务即时响应�?- 复杂场景优化:支持大规模组件树的高效更新(如虚拟滚动、动画串联)�?- 未来特性基础:为并发模式(Concurrent Mode)、离线渲染(SSR)提供底层支持�?
*局限�?**学习成本�?*:开发者需理解底层调度逻辑以优化性能�?- 内存开销:Fiber 树的双向链表结构比传统虚�?DOM 占用更多内存�?
- *与旧架构的关键差�?
| 特�? | Stack Reconciler(React 15-�? | Fiber Reconciler(React 16+�? |
|---|---|---|
| 遍历方式 | 递归(不可中断) | 循环(可中断 + 恢复�? |
| 任务调度 | 同步执行,阻塞主线程 | 异步分片,空闲时段执�? |
| *优先级控�? | �? | 基于 Lane 模型的优先级抢占 |
| 数据结构 | 虚拟 DOM �? | Fiber 链表树(含调度信息) |
:::
Fiber 结构和普�?VNode 区别
参考答�?
::: details
- 本质差异
| 维度 | 普�?VNode(虚�?DOM�? | Fiber 结构 |
|---|---|---|
| 设计目标 | 减少真实 DOM 操作,提升渲染性能 | 实现可中断的异步渲染 + 优先级调�? |
| 数据结构 | 树形结构(递归遍历�? | 双向链表树(循环遍历�? |
| 功能范畴 | 仅描�?UI 结构 | 描述 UI 结构 + 调度任务 + 副作用管�? |
- 数据结构对比
普�?VNode(React 15 及之前)
1 | const vNode = { |
- 核心字段:仅包含 UI 描述相关属性(type、props、children)�?
*Fiber 节点(React 16+�?
1 | const fiberNode = { |
- 核心扩展�? - 调度控制:
lanes优先级、任务到期时间�? - **状态管�?*:Hooks 链表(函数组件)、类组件状态队列�? - **副作用追�?*:effectTag标记和副作用链表�? - 遍历结构:child/sibling/return构成双向链表�?
- 协调机制对比
| 流程 | VNode(Stack Reconciler�? | Fiber Reconciler |
|---|---|---|
| 遍历方式 | 递归遍历(不可中断) | 循环遍历链表(可中断 + 恢复�? |
| 任务调度 | 同步执行,阻塞主线程 | 异步分片,空闲时间执�? |
| *优先级控�? | �? | Lane 模型�?1 个优先级车道�? |
| *副作用处�? | 统一提交 DOM 更新 | 构建副作用链表,分阶段提�? |
- **Fiber 两阶段提�?*�? 1. 协调阶段(可中断):
- 增量构建 Fiber 树,标记副作用(
effectTag)�? - 通过requestIdleCallback�?Scheduler 包分片执行�? 2. 提交阶段(同步不可中断)�? - 遍历副作用链表,执行 DOM 操作和生命周期方法�?
- 增量构建 Fiber 树,标记副作用(
能力扩展示例
*a. 支持 Hooks 状态管�?
Fiber 节点通过
memoizedState字段存储 Hooks 链表�?1
2
3
4
5
6
7// 函数组件�?Hooks 链表
fiberNode.memoizedState = {
memoizedState: 'state value', // useState 的状�? next: {
// 下一�?Hook(如 useEffect�? memoizedState: { cleanup: fn },
next: null,
},
}VNode 无状态管理能力,仅描�?UI�?
*b. 优先级调度实�?高优先级任务抢占�? ```javascript
// 用户输入触发高优先级更新
input.addEventListener(‘input’, () => {
React.startTransition(() => {
setInputValue(e.target.value) // 低优先级
})
// 高优先级更新立即执行
})1
2
3
4
5
6
7
8
9
10- VNode 架构无法实现任务中断和优先级插队�?
**c. 副作用批处理**
- Fiber 通过 `effectList` 链表收集所有变更,统一提交�? ```javascript
// 提交阶段遍历 effectList
let nextEffect = fiberRoot.firstEffect
while (nextEffect) {
commitWork(nextEffect)
nextEffect = nextEffect.nextEffect
}VNode 架构�?Diff 后直接操�?DOM,无批处理优化�?
- 性能影响对比
| 场景 | VNode 架构 | Fiber 架构 |
|---|---|---|
| *大型组件树渲�? | 主线程阻塞导致掉�? | 分片渲染,保�?UI 响应 |
| *高频更新(如动画�? | 多次渲染合并困难 | 基于优先级合并或跳过中间状�? |
| *SSR 水合(Hydration�? | 全量同步处理 | 增量水合,优先交互部�? |
:::
简�?React diff 算法过程
参考答�?
::: details
React Diff 算法通过 分层对比策略 �?*启发式规�? 减少树对比的时间复杂度(�?O(n³) 优化�?O(n))。其核心流程如下�?
1. 分层对比策略
React 仅对 *同一层级的兄弟节�? 进行对比,若节点跨层级移动(如从父节�?A 移动到父节点 B),则直�?销毁并重建,而非移动�?原因:跨层操作在真实 DOM 中成本极高(需递归遍历子树),而实际开发中跨层移动场景极少,此策略以概率换性能�?
2. 节点类型比对规则
a. 元素类型不同
若新旧节点类型不同(�?<div> �?<span> �?ComponentA �?ComponentB),则:
- 销毁旧节点及其子树�?2. 创建新节点及子树,并插入 DOM�?
1
2
3
4
5
6
7
8
9// 旧树
<div>
<ComponentA />
</div>
// 新树 �?直接替换
<span>
<ComponentB />
</span>
b. 元素类型相同
若类型相同,则复�?DOM 节点并更新属性:
- 原生标签:更�?
className、style等属性�?- 组件类型�? - 类组件:保留实例,触�?componentWillReceiveProps�?shouldComponentUpdate等生命周期�? - 函数组件:重新执行函数,通过 Hooks 状态判断是否需更新�?1
2
3
4// 旧组件(保留实例并更�?props�?<Button className="old" onClick={handleClick} />
// 新组�?�?复用 DOM,更�?className �?onClick
<Button className="new" onClick={newClick} />
3. 列表节点�?Key 优化
处理子节点列表时,React 依赖 key 进行最小化更新�?
a. �?key 时的默认行为
默认使用 索引匹配(index-based diff),可能导致性能问题�?
1 | // 旧列�?;[<div>A</div>, <div>B</div>][ |
此时 React 会认为索�?0 �?A �?C(更新),索�?1 �?B �?A(更新),并新增索引 2 �?B,实际应仅插�?C�?
*b. 使用 key 的优化匹�?
通过唯一 key 标识节点身份,React 可精准识别移�?新增/删除�?
1 | // 正确使用 key(如数据 ID�?<ul> |
匹配规则�?
遍历新列表,通过 key 查找旧节点:
- 找到且类型相�?�?复用节点�? - 未找�?�?新建节点�?
记录旧节点中未被复用的节�?�?执行删除�?
c. 节点移动优化
若新旧列表节点仅顺序变化,React 通过 key 匹配后,仅执�?DOM 移动操作(非重建),例如�?
1 | // 旧列表:A (key=1), B (key=2) |
**�?key �?*:通过 key 匹配节点,减少移动操�? ```jsx
// key 匹配后,仅插�?D,其他节点不更新1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
**b. Vue 的双端对比策�?*
分四步优化对比效率(Vue2 核心逻辑,Vue3 优化为最长递增子序列)�?
1. **头头对比**:新旧头指针节点相同则复用,指针后移
2. **尾尾对比**:新旧尾指针节点相同则复用,指针前移
3. **头尾交叉对比**:旧�?vs 新尾,旧�?vs 新头
4. **中间乱序对比**:建�?key-index 映射表,复用可匹配节�?
```js
// 旧列表:[A, B, C, D]
// 新列表:[D, A, B, C]
// Vue 通过步骤3头尾对比,仅移动 D 到头�?```
**3. 静态优化机�?*
**a. Vue 的编译时优化**
- **静态节点标�?*�? 模板中的静态节点(无响应式绑定)会被编译为常量,跳�?Diff
```html
<!-- 编译�?-->
<div>Hello Vue</div>
<!-- 编译�?-->
_hoisted_1 = createVNode("div", null, "Hello Vue")**Block Tree(Vue3�?*�? 动态节点按区块(Block)组织,Diff 时仅对比动态部�?
b. React 的运行时优化手动控制更新�? 需通过
React.memo、shouldComponentUpdate�?useMemo避免无效渲染1
const MemoComp = React.memo(() => <div>Static Content</div>)
*4. 响应式更新触�?
| 框架 | 机制 | Diff 触发条件 |
|---|---|---|
| React | 状态变化触发组件重新渲�? | 父组件渲�?�?子组件默认递归 Diff |
| Vue | 响应式数据变更触发组件更�? | 依赖收集 �?仅受影响组件触发 Diff |
1 | // Vue:只�?data.value 变化才会触发更新 |
5. 设计哲学差异
| 维度 | React | Vue |
|---|---|---|
| 控制粒度 | 组件级控制(开发者主导) | 细粒度依赖追踪(框架主导�? |
| 优化方向 | 运行时优化(Fiber 调度�? | 编译时优化(模板静态分析) |
| 适用场景 | 大型动态应用(需精细控制�? | 中小型应用(快速开发) |
:::
React JSX 循环为何使用 key �?
参考答�?
::: details
- 元素的高效识别与复用
React 通过 key 唯一标识列表中的每个元素。当列表发生变化(增删改排序)时,React 会通过 key 快速判断:
- 哪些元素是新增的(需要创建新 DOM 节点�?- 哪些元素是移除的(需要销毁旧 DOM 节点�?- 哪些元素是移动的(直接复用现�?DOM 节点,仅调整顺序�?
如果没有key,React 会默认使用数组索引(index)作为标识,这在动态列表中会导�?性能下降 �?**状态错�?*�?
- *避免状态混�?
如果列表项是 有状态的组件(比如输入框、勾选框等),错误的 key 会导致状态与错误的内容绑定。例如:
1 | // 如果初始列表�?[A, B],用索引 index 作为 key�?<ul> |
- DOM 事件
使用**全小写命�?*(如onclick、onchange),通过字符串或addEventListener绑定�? ```html1
2```javascript
button.addEventListener('click', handleClick)
- *事件对象(Event Object�?
React 事件
使用**合成事件(SyntheticEvent�?*,是原生事件对象的跨浏览器包装�?- 通过
e.nativeEvent访问原生事件�? - 事件对象会被复用(事件池机制),异步访问需调用e.persist()�?1
2
3
4const handleClick = (e) => {
e.persist() // 保持事件对象引用
setTimeout(() => console.log(e.target), 100)
}
- 通过
DOM 事件
直接使用浏览器原生事件对象,无复用机制�? ```javascript
button.addEventListener(‘click’, (e) => {
console.log(e.target) // 直接访问
})1
2
3
4
5
6
7
8
9
10
11
3. **事件传播与默认行�?*
- **React 事件**
- **阻止默认行为**:必须显式调�?`e.preventDefault()`�? - **阻止冒泡**:调�?`e.stopPropagation()`�?
```jsx
const handleSubmit = (e) => {
e.preventDefault() // 阻止表单默认提交
e.stopPropagation() // 阻止事件冒泡
}DOM 事件
- 阻止默认行为:可调用
e.preventDefault()�?return false(在 HTML 属性中)�? - 阻止冒泡:调�?e.stopPropagation()�?return false(仅部分情况)�? ```html1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
4. **性能优化**
- **React 事件**
采用**事件委托**机制�?
- React 17 之前将事件委托到 `document` 层级�? - React 17+ 改为委托到渲染的根容器(�?`ReactDOM.render` 挂载的节点)�? - 减少内存占用,动态添加元素无需重新绑定事件�?
- **DOM 事件**
直接绑定到元素,大量事件监听时可能导致性能问题�?
5. **跨浏览器兼容�?*
- **React 事件**
合成事件抹平了浏览器差异(如 `event.target` 的一致性),无需处理兼容性问题�?
- **DOM 事件**
需手动处理浏览器兼容性(�?IE �?`attachEvent` vs 标准 `addEventListener`)�?
6. **`this` 绑定**
- **React 事件**
类组件中需手动绑定 `this` 或使用箭头函数:
```jsx
class MyComponent extends React.Component {
handleClick() {
console.log(this) // 需绑定,否则为 undefined
}
render() {
return <button onClick={this.handleClick.bind(this)}>点击</button>
}
}
- 阻止默认行为:可调用
DOM 事件
事件处理函数中的this默认指向触发事件的元素:1
2
3button.addEventListener('click', function () {
console.log(this) // 指向 button 元素
})
| 特�? | React 事件 | DOM 事件 |
|---|---|---|
| 命名规则 | 驼峰命名(onClick�? |
全小写(onclick�? |
| 事件对象 | 合成事件(SyntheticEvent�? |
原生事件对象 |
| 默认行为阻止 | e.preventDefault() |
e.preventDefault() �?return false |
| 事件委托 | 自动委托到根容器 | 需手动实现 |
| 跨浏览器兼容 | 内置处理 | 需手动适配 |
this 指向 |
类组件中需手动绑定 | 默认指向触发元素 |
React 事件系统通过抽象和优化,提供了更高效、一致的事件处理方式,避免了直接操作 DOM 的繁琐和兼容性问题�?
:::
简�?React batchUpdate 机制
参考答�?
::: details
React �?*batchUpdate(批处理更新)机�? 是一种优化策略,旨在将多个状态更新合并为一次渲染,减少不必要的组件重新渲染次数,从而提高性能�?
核心机制
异步合并更新
当在 **同一执行上下�?*(如同一个事件处理函数、生命周期方法或 React 合成事件)中多次调用状态更新(�?setState、useState�?setter函数),React 不会立即触发渲染,而是将多个更新收集到一个队列中,最终合并为一次更新,统一计算新状态并渲染�?更新队列
React 内部维护一个更新队列。在触发更新的代码块中,所有状态变更会被暂存到队列,直到代码执行完毕,React 才会一次性处理队列中的所有更新,生成新的虚拟 DOM,并通过 Diff 算法高效更新真实 DOM�?
触发批处理的场景React 合成事件
�?onClick、onChange等事件处理函数中的多次状态更新会自动批处理�?1
2
3
4const handleClick = () => {
setCount(1) // 更新入队
setName('Alice') // 更新入队
// 最终合并为一次渲�? }React 生命周期函数
�?componentDidMount、componentDidUpdate等生命周期方法中的更新会被批处理�?React 18+ 的自动批处理增强
React 18 引入createRoot后,即使在异步操作(�?setTimeout、Promise、原生事件回调)中的更新也会自动批处理:1
2
3setTimeout(() => {
setCount(1) // React 18 中自动批处理
setName('Alice') // 合并为一次渲�? }, 1000)
绕过批处理的场景
React 17 及之前的异步代码
�?setTimeout、Promise或原生事件回调中的更新默�?*不会**批处理,每次setState触发一次渲染:1
2
3// React 17 中会触发两次渲染
setTimeout(() => {
setCount(1) // 渲染一�? setName('Alice') // 渲染第二�? }, 1000)手动强制同步更新
使用flushSync(React 18+)可强制立即更新,绕过批处理�?1
2
3
4
5
6import { flushSync } from 'react-dom'
flushSync(() => {
setCount(1) // 立即渲染
})
setName('Alice') // 再次渲染
设计目的
- 性能优化
避免频繁�?DOM 操作,减少浏览器重绘和回流,提升应用性能�? - *状态一致�?
确保在同一个上下文中多次状态变更后,组件最终基于最新的状态值渲染,避免中间状态导致的 UI 不一致�?
示例对比
*自动批处理(React 18+�?
1
2
3
4const handleClick = () => {
setCount((prev) => prev + 1) // 更新入队
setCount((prev) => prev + 1) // 更新入队
// 最�?count 增加 2,仅一次渲�? }*非批处理(React 17 异步代码�?
1
2
3setTimeout(() => {
setCount((prev) => prev + 1) // 渲染一�? setCount((prev) => prev + 1) // 再渲染一�? // React 17 中触发两次渲染,count 仍为 2
}, 1000)
| 场景 | React 17 及之�? | React 18+(使�?createRoot�? |
|---|---|---|
| 合成事件/生命周期 | 自动批处�? | 自动批处�? |
| 异步操作 | 不批处理 | 自动批处�? |
| 原生事件回调 | 不批处理 | 自动批处�? |
React 的批处理机制通过合并更新减少了渲染次数,但在需要即时反馈的场景(如动画)中,可通过 flushSync 强制同步更新�?
:::
简�?React 事务机制
参考答�?
::: details
React �?*事务机制(Transaction�? 是早期版本(React 16 之前)中用于 批量处理更新 �?*管理副作�? 的核心设计模式,其核心思想是通过“包装”操作流程,确保在更新过程中执行特定的前置和后置逻辑(如生命周期钩子、事件监听等)。随着 React Fiber 架构的引入,事务机制逐渐被更灵活的调度系统取代�?
核心概念
*事务的定�?
事务是一个包�?**初始化阶�?�?执行阶段 �?收尾阶段 的流程控制单元。每个事务通过Transaction类实现,提供initialize�?close方法,用于在操作前后插入逻辑。例如:1
2
3
4
5
6
7
8const MyTransaction = {
initialize() {
/* 前置操作(如记录状态) */
},
close() {
/* 后置操作(如触发更新�?*/
},
}包装函数
事务通过perform方法执行目标函数,将其包裹在事务的生命周期中�? ```javascript
function myAction() {
/* 核心逻辑(如调用 setState�?*/
}
MyTransaction.perform(myAction)1
2
3
4
5
6
7
8
9
10
11
12
13
**�?React 中的应用场景**
1. **批量更新(Batching Updates�?*
在事件处理或生命周期方法中,多次调用 `setState` 会被事务合并为一次更新。例如:
```javascript
class Component {
onClick() {
// 事务包裹下的多次 setState 合并为一次渲�? this.setState({ a: 1 })
this.setState({ b: 2 })
}
}*生命周期钩子的触�?
在组件挂载或更新时,事务确保componentWillMount、componentDidMount等钩子在正确时机执行�?*事件系统的委�?
合成事件(如onClick)的处理逻辑通过事务绑定和解绑,确保事件监听的一致性和性能优化�?
*事务的工作流�?*初始化阶�?
执行所有事务的initialize方法(如记录当前 DOM 状态、锁定事件监听)�?2. 执行目标函数
运行核心逻辑(如用户定义�?setState或事件处理函数)�?3. 收尾阶段
执行所有事务的close方法(如对比 DOM 变化、触发更新、解锁事件)�?
*事务机制的局限�?同步阻塞
事务的执行是同步且不可中断的,无法支持异步优先级调度(如 Concurrent Mode 的时间切片)�?2. 复杂性高
事务的嵌套和组合逻辑复杂,难以维护和扩展�?
*Fiber 架构的演�?
React 16 引入�?Fiber 架构 替代了事务机制,核心改进包括�?*异步可中断更�?
通过 Fiber 节点的链表结构,支持暂停、恢复和优先级调度�?2. *更细粒度的控�?
将渲染拆分为多个阶段(如render�?commit),副作用管理更灵活�?3. 替代批量更新策略
使用调度器(Scheduler)和优先级队列实现更高效的批处理(如 React 18 的自动批处理)�?特�? 事务机制(React <16�? Fiber 架构(React 16+�? 更新方式 同步批量更新 异步可中断、优先级调度 *副作用管�? 通过事务生命周期控制 通过 Effect Hook、提交阶段处�? *复杂�? 高(嵌套事务逻辑复杂�? 高(但更模块化和可扩展) 适用场景 简单同步更�? 复杂异步渲染(如动画、懒加载�?
事务机制�?React 早期实现批量更新的基石,但其同步设计无法满足现代前端应用的复杂需求。Fiber 架构通过解耦渲染过程,�?Concurrent Mode �?Suspense 等特性奠定了基础,成�?React 高效渲染的核心�?:::
理解 React concurrency 并发机制
参考答�?
::: details
React 的并发机制(Concurrency)是 React 18 引入的一项重要特性,旨在提升应用的响应性和性能�?
1. 什么是 React 的并发机制?
React 的并发机制允�?React 在渲染过程中根据任务的优先级进行调度和中断,从而确保高优先级的更新能够及时渲染,而不会被低优先级的任务阻塞�?
2. 并发机制的工作原理:
时间分片(Time Slicing): React 将渲染任务拆分为多个小片段,每个片段在主线程空闲时执行。这使得浏览器可以在渲染过程中处理用户输入和其他高优先级任务,避免长时间的渲染阻塞用户交互�?
优先级调度(Priority Scheduling): React 为不同的更新分配不同的优先级。高优先级的更新(如用户输入)会被优先处理,而低优先级的更新(如数据预加载)可以在空闲时处理�?
可中断渲染(Interruptible Rendering): 在并发模式下,React 可以中断当前的渲染任务,处理更高优先级的任务,然后再恢复之前的渲染。这确保了应用在长时间渲染过程中仍能保持响应性�?
3. 并发机制的优势:提升响应性: 通过优先处理高优先级任务,React 能够更快地响应用户输入,提升用户体验�?
*优化性能�? 将渲染任务拆分为小片段,避免长时间的渲染阻塞,提升应用的整体性能�?
更好的资源利用: 在主线程空闲时处理低优先级任务,充分利用系统资源�?
*4. 如何启用并发模式�?
要在 React 应用中启用并发模式,需要使�?createRoot API�?
1 | import React from 'react' |
在并发模式下,React 会自动根据任务的优先级进行调度和渲染�?
:::
React reconciliation 协调的过�?
参考答�?
::: details
React �?*协调(Reconciliation�? 是用于高效更�?UI 的核心算法。当组件状态或属性变化时,React 会通过对比新旧虚拟 DOM(Virtual DOM)树,找出最小化的差异并应用更新。以下是协调过程的详细步骤:
- *生成虚拟 DOM �?
- 当组件状态或属性变化时,React 会重新调用组件的
render方法,生成新�?*虚拟 DOM �?*(一个轻量级�?JavaScript 对象,描�?UI 结构)�?- 虚拟 DOM 是实�?DOM 的抽象表示,操作成本远低于直接操作真�?DOM�?
- Diffing 算法(差异对比)
React 使用 Diffing 算法 比较新旧两棵虚拟 DOM 树,找出需要更新的部分。对比规则如下:
规则一:不同类型的元素
如果新旧元素�?
type不同(例如从<div>变为<span>),React �?*销毁旧子树**�?*重建新子�?*�? - 旧组件的生命周期方法(如componentWillUnmount)会被触发�? - 新组件的生命周期方法(如constructor、componentDidMount)会被触发�?
*规则二:相同类型的元�?如果元素�?
type相同(例�?<div className="old">�?<div className="new">),React �?*保留 DOM 节点**,仅更新变化的属性�? - 对比新旧属性,仅更新差异部分(例如className)�? - 组件实例保持不变,生命周期方法(�?componentDidUpdate)会被触发�?
*规则三:递归处理子节�?对于子节点的对比,React 默认使用逐层递归的方式�?- 列表对比优化�? - 当子元素是列表(例如通过
map生成的元素)时,React 需要唯一key来标识元素,以高效复�?DOM 节点�? - 若未提供key,React 会按顺序对比子节点,可能导致性能下降或状态错误(例如列表顺序变化时)�?
- 更新真实 DOM
- 通过 Diffing 算法找出差异后,React 将生成一系列最小化�?DOM 操作指令(例�?
updateTextContent、replaceChild)�?- 这些指令会被批量应用到真�?DOM 上,以减少重绘和重排的次数,提高性能�?
- *协调的优化策�?
- **Key 的作�?*:为列表元素提供唯一�?
key,帮�?React 识别元素的移动、添加或删除,避免不必要的重建�?- **批量更新(Batching�?*:React 会将多个状态更新合并为一次渲染,减少重复计算�?- Fiber 架构(React 16+):- 将协调过程拆分为可中断的“工作单元”(Fiber 节点),允许高优先级任务(如动画)优先处理�? - 支持异步渲染(Concurrent Mode),避免长时间阻塞主线程�?
:::
- 将协调过程拆分为可中断的“工作单元”(Fiber 节点),允许高优先级任务(如动画)优先处理�? - 支持异步渲染(Concurrent Mode),避免长时间阻塞主线程�?
React 组件渲染和更新的全过�?
参考答�?
::: details
React 组件的渲染和更新过程涉及多个阶段,包�?*初始化、渲染、协调、提交、清�? 等。以下是 React 组件渲染和更新的全过程,结合源码逻辑和关键步骤进行详细分析�?
1. 整体流程概述
React 的渲染和更新过程可以分为以下几个阶段�?
- **初始化阶�?*:创�?Fiber 树和 Hooks 链表�?2. 渲染阶段:生成新的虚�?DOM(Fiber 树)�?3. 协调阶段:对比新�?Fiber 树,找出需要更新的部分�?4. 提交阶段:将更新应用到真�?DOM�?5. 清理阶段:重置全局变量,准备下一次更新�?
2. 详细流程分析
�?)初始化阶段
触发条件:组件首次渲染或状�?属性更新�?- 关键函数:
render、createRoot、scheduleUpdateOnFiber�?- 逻辑�? 1. 通过ReactDOM.render�?createRoot初始化应用�? 2. 创建�?Fiber 节点(HostRoot)�? 3. 调用scheduleUpdateOnFiber,将更新任务加入调度队列�?
*�?)渲染阶�?触发条件:调度器开始执行任务�?- 关键函数:
performSyncWorkOnRoot、beginWork、renderWithHooks�?- 逻辑�? 1. 调用performSyncWorkOnRoot,开始渲染任务�? 2. 调用beginWork,递归处理 Fiber 节点�? 3. 对于函数组件,调�?renderWithHooks,执行组件函数并生成新的 Hooks 链表�? 4. 对于类组件,调用instance.render,生成新的虚�?DOM�? 5. 对于 Host 组件(如div),生成对应�?DOM 节点�?
*�?)协调阶�?触发条件:新的虚�?DOM 生成后�?- 关键函数:
reconcileChildren、diff�?- 逻辑�? 1. 调用reconcileChildren,对比新�?Fiber 节点�? 2. 根据diff算法,找出需要更新的节点�? 3. 为需要更新的节点打上Placement、Update、Deletion等标记�?
*�?)提交阶�?触发条件:协调阶段完成后�?- 关键函数:
commitRoot、commitWork�?- 逻辑�? 1. 调用commitRoot,开始提交更新�? 2. 调用commitWork,递归处理 Fiber 节点�? 3. 根据节点的标记,执行 DOM 操作(如插入、更新、删除)�? 4. 调用生命周期钩子(如componentDidMount、componentDidUpdate)�?
*�?)清理阶�?触发条件:提交阶段完成后�?- 关键函数:
resetHooks、resetContext�?- 逻辑�? 1. 重置全局变量(如currentlyRenderingFiber、currentHook)�? 2. 清理上下文和副作用�? 3. 准备下一次更新�?
:::
为何 Hooks 不能放在条件或循环之内?
参考答�?
::: details
一个组件中�?hook 会以链表的形式串起来�?FiberNode �?memoizedState 中保存了 Hooks 链表中的第一�?Hook�?
在更新时,会复用之前�?Hook,如果通过了条件或循环语句,增加或者删�?hooks,在复用 hooks 过程中,会产生复�?hooks状态和当前 hooks 不一致的问题�?
:::
useEffect 的底层是如何实现的(美团�?
参考答�?
::: details
useEffect �?React 用于管理副作用的 Hook,它�?commit 阶段 统一执行,确保副作用不会影响渲染�?
�?React 源码中,useEffect 通过 Fiber 机制 �?commit 阶段 进行处理�?
**(1) useEffect 存储�?Fiber 节点�?*
React 组件是通过 Fiber 数据结构 组织的,每个 useEffect 都会存储�?fiber.updateQueue 中�?
(2) useEffect 何时执行
React 组件更新后,React �?commit 阶段 统一遍历 effect 队列,并执行 useEffect 副作用�?
React 使用 useEffectEvent() 注册 effect,在 commitLayoutEffect 之后,异步执�?useEffect,避免阻�?UI 渲染�?
**(3) useEffect 依赖变化的处�?*
依赖数组的比较使�?Object.is(),只有依赖变化时才重新执�?useEffect�?
在更新阶段,React 遍历�?effect,并先执行清理函数,然后再执行新�?effect�?
*简化的 useEffect 实现如下�?
1 | function useEffect(callback, dependencies) { |
相比 useLayoutEffect,useEffect �?异步执行,不会阻�?UI 渲染�?
:::