React 原理
React Fiber
Section titled “React Fiber”在React 15及以前,React 使用的是 “Stack Reconciler”(栈调和器)。当组件状态更新时,React 会立即、同步地从组件树的根部开始,递归地调用每个组件的渲染方法,对比新旧虚拟 DOM(这个过程称为 Diffing),然后一次性将变更应用到真实 DOM。这个过程就像在一个很深的函数调用栈里执行,必须一口气完成。
栈调和器存在如下的几个痛点:
-
不可中断:一旦开始渲染,就必须执行到底。
-
阻塞主线程:如果组件树很大,这个计算过程会长时间占用
JavaScript主线程。而浏览器的主线程还负责布局、绘制、处理用户输入(点击、滚动等)。结果就是,页面在渲染期间会卡顿、无响应,用户体验非常差。
React Fiber解决了上述痛点,是React v16版本的重大更新,核心思想是将复杂的渲染更新任务拆分成多个小工作单元,并赋予其可中断、可恢复和优先级调度的能力,从而极大地提升用户体验和应用的响应速度。
React Fiber 的优点
Section titled “React Fiber 的优点”-
中断与恢复:Fiber 支持任务的暂停和恢复,避免长时间占用主线程,提升页面的响应速度。
-
优先级管理:不同任务可以分配不同的优先级,高优先级任务(如用户输入)可以优先执行。
-
性能优化:通过链表结构和副作用标记,减少不必要的节点遍历,提高渲染效率
React Fiber 工作原理
Section titled “React Fiber 工作原理”Fiber 节点结构
Section titled “Fiber 节点结构”Fiber节点的结构大致如下:
// Fiber节点对象伪代码{ // >>> 1. 实例标识信息 (Instance Identification) type: Function | String, // 组件类型(函数、类)或HTML标签名('div') key: null | String, // 同级节点中的唯一标识,用于Diff算法 stateNode: Component | DOMNode, // 对应的类组件实例或DOM节点
// >>> 2. 构成链表树的结构信息 (LinkedList Structure) - 这是实现可中断遍历的关键! return: Fiber | null, // 指向父Fiber节点(父节点) child: Fiber | null, // 指向第一个子Fiber节点(第一个子节点) sibling: Fiber | null, // 指向下一个兄弟Fiber节点(兄弟节点) // (通过child->sibling->sibling...和return,构成了深度优先遍历的链表结构)
// >>> 3. 副作用相关信息 (Side Effects) - 标记需要做什么工作 flags: Number, // (旧版本为effectTag) 一个二进制数字,标记这个Fiber需要执行的副作用类型(如:Placement-插入,Update-更新,Deletion-删除) subtreeFlags: Number, // 标记子树中是否存在副作用,避免递归遍历整个子树 deletions: Array<Fiber> | null, // 记录需要被删除的子Fiber hooks: null | HookLinkedList, // 对于函数组件,存储其hooks链表
// >>> 4. 状态和Props (State and Props) memoizedProps: any, // 上次渲染时传入的props pendingProps: any, // 新的、待处理的props memoizedState: any, // 上次渲染后的state(对于函数组件,是hook链表的状态) updateQueue: UpdateQueue<any> | null, // 存储来自setState的更新队列
// >>> 5. 用于调度和交替的工作进度信息 (Work-in-Progress) alternate: Fiber | null, // 指向current树或workInProgress树上对应的节点,用于比较和复用 // current树:当前屏幕上显示内容对应的Fiber树 // workInProgress树:正在内存中构建的、下一次要渲染的Fiber树}可以看到,Fiber节点采用链表结构来描述虚拟DOM树。使用链表结构有如下好处:
-
实现可中断与恢复:链表结构将树的父子兄弟关系显式地存储为指针。这意味着
React可以使用一个简单的while循环来手动模拟深度优先遍历。循环是完全可以控制的-
中断:
React处理完一个Fiber节点(一个工作单元)后,可以立即检查当前帧是否还有剩余时间。如果没有,直接 break 跳出循环即可中断工作。此时唯一需要保存的“进度”就是一个指向当前节点的指针。 -
恢复:当浏览器空闲时,
React重新进入工作循环。它只需要从上次保存的当前节点指针开始,根据链表指针(child->sibling->return)找到下一个要处理的节点,然后继续循环即可。
-
-
支持优先级调度和任务插队: 链表结构使得“任务插队”变得可行。当一个高优先级的更新(如用户输入)到来时,
React可以:-
中断当前正在进行的低优先级渲染工作(可能正处在链表中间的某个节点)。
-
直接开始处理高优先级的更新,构建一棵新的、更高优先级的
Fiber链表(称为workInProgress tree)。
等高优先级的更新被提交到
DOM后,React可以完全丢弃之前被中断的低优先级的work-in-progress链表,或者重新开始低优先级的渲染,而不会造成任何状态不一致。 -
Fiber架构下的更新流程
Section titled “Fiber架构下的更新流程”在协调/渲染阶段,React做了下面的事情:
-
构建
WorkInProgress Tree:React会尝试复用现有的Fiber节点,创建一棵新的、代表“工作进度”的Fiber树(workInProgress tree)。这使用了双缓存技术,在内存中完成所有计算,不直接影响屏幕。 -
遍历并处理每个
Fiber(beginWork):从根节点开始,React会深度优先遍历每个Fiber节点,对每个节点执行的主要工作包括:-
更新状态:处理组件状态和属性的更新。
-
调用渲染方法:对于类组件,调用
render方法;对于函数组件,调用函数本身(这就是为什么函数组件每次更新都会执行整个函数体)。 -
Diffing算法:将渲染返回的新子元素(children)与旧的子Fiber进行对比。 -
标记副作用:根据对比结果,为
Fiber节点打上“副作用”(Effect)的标签(使用二进制位flags表示,如: Placement-插入、Update-更新、Deletion-删除),但此时并不执行任何DOM操作。 -
决定子节点:通过
Diffing的结果,决定是复用旧的Fiber、创建新的Fiber还是标记删除。 -
完成处理 (
completeWork):当一个节点的所有子节点都处理完毕后,会回到该节点完成“收尾”工作。例如,为宿主组件(DOM节点)准备props的更新。
-
当整个workInProgress tree的所有节点都遍历完成后,React得到了一棵完整的、标记了所有变更的新树。React会将这些所有标记了副作用的Fiber节点连接成一个线性链表,称为Effect List(副作用列表)。
在提交更新阶段,React会遍历Effect List,执行DOM增删改、调用生命周期/Effect钩子。(提交更新阶段不可中断)
Scheduler:时间切片
Section titled “Scheduler:时间切片”React通过时间切片的方式去拆分任务,具体工作流程如下:
-
任务开始:
setState触发一个新的渲染任务。 -
申请时间片:
React调度器开始工作,根据任务优先级,向浏览器申请一个时间片(例如5ms)。 -
循环工作与检查:
-
React 开始循环处理
Fiber节点(执行beginWork和completeWork)。 -
每处理完一个
Fiber节点,它都会检查当前时间片是否已经耗尽。
-
-
决策点:
-
时间充足:继续处理下一个节点。
-
时间耗尽:立即中断工作,保存当前进度,并归还主线程。
-
-
浏览器接管:浏览器利用这拿回的主线程进行布局、绘制,并响应用户的点击、输入等操作。用户不会感到卡顿。
-
恢复工作:通过
requestIdleCallback(或其polyfill),浏览器在空闲时通知React。React再次获取主控制权,从上一次保存的Fiber节点继续处理,申请下一个时间片。 -
完成:重复步骤 2-6,直到所有 Fiber 节点处理完毕。然后一次性同步地执行提交阶段(
commitWork),将最终结果更新到DOM上。
由于requestIdleCallback的兼容性和触发频率问题,React团队自己实现了这部分能力,源码见:https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js。
Scheduler:优先级调度机制
Section titled “Scheduler:优先级调度机制”ExpirationTime
Section titled “ExpirationTime”在React 16.x(即React 17之前)的版本中,Fiber架构使用的优先级调度机制是基于ExpirationTime(过期时间) 的模型。ExpirationTime模型的核心理念是:为每个更新任务计算一个唯一的“过期时间戳”,优先级越高的任务,其过期时间戳越小(意味着越紧迫,需要越快执行)。
React 内部定义了不同的优先级等级,例如:
-
ImmediatePriority: 用于需要同步执行的任务。
-
UserBlockingPriority: 用于用户交互(如点击、输入)。
-
NormalPriority: 用于普通的数据更新、网络请求结果。
-
LowPriority: 用于可延迟的任务(如 Offscreen 内容)。
-
IdlePriority: 用于非必要的后台任务(最低优先级)。
当一个更新(如 setState)被触发时,React 会根据其优先级,计算出一个过期时间。计算公式简化后类似于:expirationTime = currentTime + timeout。其中:
-
currentTime: 当前时间(一个从应用加载开始持续递增的毫秒数)。
-
timeout: 一个常量,优先级越高,timeout 值越小。
例如,高优先级任务的timeout可能是100ms,而低优先级任务的timeout可能是5s。这意味着,高优先级任务在currentTime + 100ms后就“过期”了,而低优先级任务则有5s的“缓冲期”。
React调度器始终优先执行队列中expirationTime最小的任务(即最紧迫、最快过期的任务)。在执行一个任务(渲染一棵 Fiber 树)的过程中,React 会不断地检查当前时间:
-
如果当前时间小于下一个最高优先级任务的
expirationTime,说明还有时间,可以继续当前工作。 -
如果有一个更高优先级(更小
expirationTime)的任务被加入队列,或者当前时间已经超过了当前任务的expirationTime(任务已过期),React就会中断当前工作,转而去执行那个更高优先级的或已过期的任务。
使用ExpirationTime模型来调度更新存在如下问题:
-
饥饿问题(
Starvation): 如果一直有高优先级的更新(如用户输入),那么低优先级的更新(如渲染一个大型列表)可能会因为其过期时间戳不断被推后而永远无法被处理。 -
无法批量处理多个更新:
ExpirationTime模型更倾向于处理单个最高优先级的更新,难以将多个同一优先级的更新进行高效的批量处理。 -
表达能力有限: 它无法同时表示和处理多个不同优先级的更新。调度器一次只能盯着“一个”最快过期的任务。
React 17之后引入了Lanes模型(车道模型)来管理优先级,解决了上述问题,为并发模式的实现奠定了坚实的基础。
想象一条有着多条车道的公路。
-
每条车道代表一个优先级(如:用户输入、过渡更新、普通更新等)。
-
一辆车代表一个更新(Update)。
-
一次渲染任务可以占用一条或多条车道(即处理多个优先级的更新)。
车道有宽度(优先级),也有编号。React 使用一个31位的二进制数来表示这些车道(Lane和Lanes类型)。因为优先级是二进制位,所以可以非常高效地使用位操作(|, &, ~)来组合、分离和判断优先级。
源码见:https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberLane.js 。
基于Lanes的优先级机制工作流程如下:
-
Schedule Update: 当发生一个更新时(例如setState、startTransition),React会根据更新的来源和上下文,为其分配一个或多个对应的车道。 -
Select Lane: 在每次准备构建新的渲染树(workLoopSync或workLoopConcurrent)之前,调度器需要决定本次渲染要处理哪些优先级的更新(getNextLanes)。-
检查是否有挂起的车道: 查看
pendingLanes集合。 -
选择最高优先级车道: 从
pendingLanes中找出优先级最高的车道(数值最小的非零车道)。 -
进行调度: 这是并发模式的核心。调度器会询问浏览器是否有空余时间。
-
如果当前是同步任务(如
SyncLane) 或浏览器空闲: 则直接处理选中的车道。 -
如果浏览器繁忙: 即使有高优先级更新,
React也可能中断当前渲染,将主线程交还给浏览器去绘制UI或处理用户输入,以避免页面卡顿。这就是时间切片(Time Slicing)。 -
对于低优先级更新(如
TransitionLane),React会主动yield(让步) 给浏览器,检查是否有更高优先级的更新进来。
-
-
-
Render and Interrupt:-
渲染过程: React 开始渲染组件树。本次渲染只会处理那些优先级与选中车道相匹配的更新。
-
中断机制:
- 在并发渲染过程中,React 会定期中断渲染工作,去检查是否有新的、更高优先级的更新被加入。例如,正在渲染一个低优先级的
Transition更新时,用户突然点击了按钮(产生SyncLane更新)。React会立即中断当前的渲染工作,丢弃已经进行的Fiber树构建(因为可能已经过时),然后立即开始一次新的、更高优先级的渲染来处理用户的点击。之前被中断的低优先级渲染会完全作废,然后以最新的状态重新开始。
- 在并发渲染过程中,React 会定期中断渲染工作,去检查是否有新的、更高优先级的更新被加入。例如,正在渲染一个低优先级的
-
-
Commit and Cleanup: 当一棵Fiber树的渲染工作(Render Phase)完成,React会将其提交(Commit Phase)到 DOM。提交阶段是同步的、不可中断的,以保证UI的一致性。提交完成后,React会清理已处理的车道:pendingLanes = pendingLanes & ~renderedLanes,将刚刚渲染过的车道从待处理集合中移除。
React Diff算法
Section titled “React Diff算法”React Diff算法是React中用于比较虚拟DOM树并计算出最小更新操作的一种算法。它的工作流程可以概括为以下几个步骤:
-
树的递归比较:
React会对新旧两棵虚拟DOM树进行递归比较,从根节点开始,逐层比较节点。 -
比较规则:
-
节点类型不同:如果节点类型(标签名或组件名)不同,
React会直接销毁旧节点及其子节点,创建新节点并替换。 -
节点类型相同:如果是相同类型的节点,
React会更新该节点的属性,然后递归比较其子节点。
-
-
列表节点的比较:当比较一组子节点(如列表)时,
React使用Key属性来优化比较过程:会尽量复用具有相同key的节点,而不是直接销毁和创建。如果没有key,React可能会采用更暴力的方式更新,导致性能下降和不必要的渲染。 -
Diffing策略:
-
分层比较:
React只会对同一层次的节点进行比较,不会跨层次比较。如果节点跨层次移动,React会销毁并重新创建。 -
列表比较(Reconciliation):
-
定义两个指针(
oldIndex和newIndex)分别指向旧列表和新列表的头部。 -
开始遍历,如果新旧列表指针指向的
Fiber节点的key相同且类型相同,则视为可复用,指针后移。 -
如果遇到不可复用的节点,则停止遍历。
-
然后,根据停止后的情况:
-
如果旧列表指针指向为空,那么新列表指针指向的
Fiber节点以及往后的Fiber节点都是待新增的。 -
如果旧列表指针指向不为空,则:
a. 将剩余的旧
Fiber节点(从oldIndex开始)放入一个以key为键的map中。b. 然后从新列表的当前指针(
newIndex)开始遍历,对于每个新Fiber节点,从map中查找是否有相同key的旧节点。-
如果找到,且类型相同,则复用,并从
map中删除。 -
如果找不到,则说明是新增的。
c. 最后,
map中剩余的旧节点都是待删除的。 -
-
-
-
React Hooks 链表
Section titled “React Hooks 链表”在函数组件中,每次调用一个 Hook(比如 useState、useEffect 等),React 都会将这些 Hook 按照调用的顺序添加到链表中。这个链表存储在组件对应的 Fiber 节点上,每个 Hook 节点都会存储相关的状态和更新队列。
Hook 链表的存储结构
Section titled “Hook 链表的存储结构”在 Fiber 节点的 memoizedState 属性中,函数组件的 Hooks 以链表形式存储:
Fiber.memoizedState (Hook链表头节点)
Hook (useState #1) Hook (useEffect #2) Hook (useState #3)┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐│ memoizedState: 0 │ │ memoizedState: ...│ │ memoizedState: '' ││ queue: UpdateQueue│ │ queue: null │ │ queue: UpdateQueue││ next: ———————————————> │ next: ———————————————> │ next: null │└───────────────────┘ └───────────────────┘ └───────────────────┘ ↑ ↑ ↑ | | |const [count, setCount] useEffect(...) const [text, setText]= useState(0) = useState('')Hook 节点的内部结构
Section titled “Hook 节点的内部结构”每个 Hook 节点包含以下关键属性:
- memoizedState: 存储 Hook 的当前状态值
- queue: 更新队列,用于批量处理状态更新(useState、useReducer 等)
- next: 指向下一个 Hook 节点的指针
React 内部实现原理
Section titled “React 内部实现原理”基于 React 源码,Hook 的调用机制如下:
// React 内部简化实现let componentHooks = [];let currentHookIndex = 0;
function useState(initialState) { let pair = componentHooks[currentHookIndex];
if (pair) { // 非首次渲染,返回已存在的状态 currentHookIndex++; return pair; }
// 首次渲染,创建新的状态对 pair = [initialState, setState];
function setState(nextState) { pair[0] = nextState; // 触发重新渲染 updateDOM(); }
componentHooks[currentHookIndex] = pair; currentHookIndex++; return pair;}Hook 调用规则的原理
Section titled “Hook 调用规则的原理”基于链表的设计,React 强制执行以下规则:
1. 顶层调用原则
- Hook 必须在函数组件的顶层调用,不能在条件、循环或嵌套函数中调用
- 原因:React 依赖 Hook 的调用顺序来匹配链表中的节点
2. 调用顺序一致性
- 每次渲染时,Hook 的调用顺序必须保持一致
- React 通过
currentHookIndex来追踪当前处理的 Hook 节点
3. 多个 Hook 互不干扰
- 同一组件中多次调用
useState会创建不同的链表节点 - 每个节点维护独立的状态和更新队列
这种设计确保了 Hook 状态的正确管理和组件的可预测行为。
实际应用示例
Section titled “实际应用示例”以下是一个展示 Hook 链表工作原理的实际例子:
function Gallery() { // Hook #1: useState const [index, setIndex] = useState(0);
// Hook #2: useState const [showMore, setShowMore] = useState(false);
// Hook #3: useEffect useEffect(() => { document.title = `Gallery - Image ${index + 1}`; }, [index]);
function handleNextClick() { setIndex(index + 1); }
function handleMoreClick() { setShowMore(!showMore); }
return ( <div> <button onClick={handleNextClick}>Next</button> <button onClick={handleMoreClick}> {showMore ? 'Hide' : 'Show'} details </button> {showMore && <p>Image details...</p>} </div> );}在这个组件中,React 会创建如下的 Hook 链表:
Fiber.memoizedState ↓Hook #1 (useState - index)├─ memoizedState: 0├─ queue: UpdateQueue└─ next ──→ Hook #2 (useState - showMore) ├─ memoizedState: false ├─ queue: UpdateQueue └─ next ──→ Hook #3 (useEffect) ├─ memoizedState: Effect对象 ├─ queue: null └─ next: null错误示例与原因
Section titled “错误示例与原因”❌ 错误的条件调用:
function BadComponent({ condition }) { const [count, setCount] = useState(0);
if (condition) { // 错误:条件调用会破坏链表顺序 const [name, setName] = useState(''); }
const [visible, setVisible] = useState(true);}问题分析:
- 首次渲染时如果
condition为true,会创建 3 个 Hook 节点 - 再次渲染时如果
condition为false,只会创建 2 个 Hook 节点 - React 无法正确匹配链表中的节点,导致状态混乱
✅ 正确的实现:
function GoodComponent({ condition }) { const [count, setCount] = useState(0); const [name, setName] = useState(''); const [visible, setVisible] = useState(true);
// 在渲染逻辑中处理条件 return ( <div> <p>Count: {count}</p> {condition && <p>Name: {name}</p>} {visible && <p>Visible content</p>} </div> );}这样确保了每次渲染时 Hook 的调用顺序都保持一致,维护了链表结构的稳定性。
React.memo 原理
Section titled “React.memo 原理”React.memo 是一个高阶组件(HOC),用于对函数组件进行浅比较优化,防止不必要的重新渲染。它通过缓存组件的渲染结果,只有当 props 发生变化时才重新渲染组件。
React.memo 的核心原理是浅比较(Shallow Comparison):
- 首次渲染:组件正常渲染并缓存结果
- 后续渲染:比较新旧 props 是否相同
- 相同 props:直接返回缓存的渲染结果,跳过重新渲染
- 不同 props:重新渲染组件并更新缓存
内部实现原理
Section titled “内部实现原理”// React.memo 的简化实现原理function memo(Component, areEqual) { return function MemoizedComponent(props) { const ref = useRef();
// 首次渲染或 props 发生变化时重新渲染 if (!ref.current || !areEqual(ref.current.props, props)) { ref.current = { props, result: Component(props) }; }
return ref.current.result; };}
// 默认的浅比较函数function shallowEqual(prevProps, nextProps) { const keys1 = Object.keys(prevProps); const keys2 = Object.keys(nextProps);
if (keys1.length !== keys2.length) { return false; }
for (let key of keys1) { if (prevProps[key] !== nextProps[key]) { return false; } }
return true;}| 比较类型 | 说明 | 示例 |
|---|---|---|
| 基本类型 | 直接值比较 | string、number、boolean |
| 引用类型 | 引用地址比较 | object、array、function |
| 自定义比较 | 使用第二个参数函数 | memo(Component, customCompare) |
性能优化场景
Section titled “性能优化场景”React.memo 适用于以下场景:
- 纯展示组件:只依赖 props 渲染的组件
- 昂贵计算组件:渲染过程包含复杂计算的组件
- 频繁更新的父组件:父组件频繁更新但子组件 props 稳定
import { memo } from 'react';
// 普通组件function Profile({ person }) { return ( <div> <h2>{person.name}</h2> <p>Age: {person.age}</p> </div> );}
// 使用 memo 优化const MemoizedProfile = memo(Profile);
// 父组件function App() { const [count, setCount] = useState(0); const person = { name: 'John', age: 30 }; // 引用类型 props
return ( <div> <button onClick={() => setCount(count + 1)}>Count: {count}</button> {/* 每次 count 更新时,Profile 都会重新渲染 */} <Profile person={person} /> {/* MemoizedProfile 不会重新渲染,因为 person 引用相同 */} <MemoizedProfile person={person} /> </div> );}解决引用类型 props 问题
Section titled “解决引用类型 props 问题”import { memo, useMemo } from 'react';
function App() { const [count, setCount] = useState(0); const [name, setName] = useState('John'); const [age, setAge] = useState(30);
// 使用 useMemo 确保 person 对象的引用稳定性 const person = useMemo(() => ({ name, age }), [name, age]);
return ( <div> <button onClick={() => setCount(count + 1)}>Count: {count}</button> {/* 现在 MemoizedProfile 只在 name 或 age 变化时重新渲染 */} <MemoizedProfile person={person} /> </div> );}自定义比较函数
Section titled “自定义比较函数”import { memo } from 'react';
function ExpensiveComponent({ items, threshold }) { // 昂贵的计算过程 const processedItems = items.filter(item => item.value > threshold);
return ( <ul> {processedItems.map(item => ( <li key={item.id}>{item.name}: {item.value}</li> ))} </ul> );}
// 自定义比较函数:只关心 items 数组长度和 thresholdconst MemoizedExpensiveComponent = memo(ExpensiveComponent, (prevProps, nextProps) => { return ( prevProps.items.length === nextProps.items.length && prevProps.threshold === nextProps.threshold );});实际应用场景
Section titled “实际应用场景”场景一:列表项组件优化
Section titled “场景一:列表项组件优化”// 列表项组件const ListItem = memo(function ListItem({ item, onDelete }) { return ( <div> <span>{item.name}</span> <button onClick={() => onDelete(item.id)}>删除</button> </div> );});
// 列表组件function TodoList({ items }) { const [filter, setFilter] = useState('');
// 使用 useCallback 确保 onDelete 函数引用稳定 const handleDelete = useCallback((id) => { // 删除逻辑 }, []);
return ( <div> <input value={filter} onChange={(e) => setFilter(e.target.value)} placeholder="筛选..." /> {items.map(item => ( <ListItem key={item.id} item={item} onDelete={handleDelete} /> ))} </div> );}优化方案对比
Section titled “优化方案对比”| 优化方案 | 适用场景 | 优点 | 缺点 | 使用建议 |
|---|---|---|---|---|
| React.memo | 函数组件 props 比较 | 简单易用,自动浅比较 | 只能比较 props,无法优化内部状态 | 适用于纯展示组件 |
| useMemo | 昂贵计算结果缓存 | 精确控制缓存逻辑 | 需要手动管理依赖数组 | 适用于复杂计算场景 |
| useCallback | 函数引用稳定性 | 防止子组件不必要渲染 | 过度使用可能适得其反 | 配合 memo 使用效果更佳 |
| PureComponent | 类组件优化 | 自动 props 和 state 比较 | 只适用于类组件 | 类组件的首选优化方案 |
注意事项与最佳实践
Section titled “注意事项与最佳实践”1. 避免过度优化
Section titled “1. 避免过度优化”// ❌ 不必要的 memo 使用const SimpleText = memo(({ text }) => <span>{text}</span>);
// ✅ 适合使用 memo 的场景const ExpensiveChart = memo(({ data }) => { // 复杂的图表渲染逻辑 return <ComplexChart data={data} />;});2. 引用类型 props 处理
Section titled “2. 引用类型 props 处理”// ❌ 每次渲染都创建新对象function Parent() { return <Child config={{ theme: 'dark', size: 'large' }} />;}
// ✅ 使用 useMemo 或提取到组件外部const defaultConfig = { theme: 'dark', size: 'large' };
function Parent() { return <Child config={defaultConfig} />;}3. 与 React Compiler 的关系
Section titled “3. 与 React Compiler 的关系”随着 React Compiler 的发展,手动使用 memo、useMemo、useCallback 的需求将逐渐减少:
// 传统手动优化方式const OptimizedComponent = memo(function Component({ data, onClick }) { const processedData = useMemo(() => expensiveProcessing(data), [data]); const handleClick = useCallback((item) => onClick(item.id), [onClick]);
return <div>{/* 渲染逻辑 */}</div>;});
// React Compiler 自动优化后function Component({ data, onClick }) { const processedData = expensiveProcessing(data); const handleClick = (item) => onClick(item.id);
return <div>{/* 渲染逻辑 */}</div>;}React.memo 作为 React 性能优化的重要工具,通过浅比较机制有效减少不必要的组件重新渲染,但需要合理使用以避免过度优化带来的负面影响。