原理✅
react 和 react-dom 是什么关系?
答案
- react 包含了 react 脱离平台的通用能力,例如 hooks, react 核心 API,详见 react reference 一般需要配合具体的运行环境使用比如 DOM、React Native 等。
- react-dom 是 react 的 DOM 版本,用于在浏览器中渲染 React 组件,包含了特定平台下的组件和 api,详见 react-dom reference
本质上 react 是脱离宿主环境的通用能力,react-dom 是 react 在浏览器中的实现和能力扩充
延伸阅读
- Beyond the DOM 理解 react 的设计理念
React 中 mode 是什么?
答案
mode 是 React 框架内部概念,用来决定渲染策略,React 18 后统一采用 Concurrent Mode。具体模式如下
- Legacy Mode: React 17 中使用的当前模式
- 默认禁用 StrictMode
- 默认为同步模式
- 使用传统的 Suspense 语义
- Blocking Mode: Legacy 和 Concurrent 之间的混合模式
- 默认启用 StrictMode
- 默认为同步模式
- 支持一些新特性
- Concurrent Mode: React 18 中使用的新模式
- 默认启用 StrictMode
- 默认为并发模式
- 支持所有新特性
关联题目
- setState 是同步还是异步 setState 特性受 mode 影响,hooks 同理
延伸阅读
- concurrent mode 官方讲解什么是 concurrent mode
- What happened to concurrent "mode" 官方讲解 concurrent mode 的进展
- Migration Step: Blocking Mode 模式切换的说明
Fiber 是什么,有哪些作用?
答案
Fiber 是用来表示渲染节点的数据结构,用来解决 React 历史的 Reconcliation 必须递归完成渲染,导致无法及时响应用户事件造成的卡顿问题。 每个 Fiber 对应这一个渲染节点,这样可以将组件的递归渲染拆分为一系列的工作单元,从而在此基础上实现对渲染的调度,包括延迟执行,优先级控制等。
Fiber 结构如下
对 fiber 结构的属性聚类如下
分类 | 字段名 | 类型 | 功能描述 |
---|---|---|---|
组件身份与引用 | tag | WorkTag | Fiber 类型,如函数组件、DOM 元素等 |
key | string | null | 唯一标识子节点,用于列表 diff | |
elementType | any | JSX 中的 type,如 'div' 、组件名 | |
type | any | 实际的函数或类组件定义 | |
stateNode | any | 对应的 DOM 节点或类实例 | |
ref | RefObject | Function | null | 节点引用 | |
refCleanup | (() => void) | null | 卸载时清理 ref 的函数 | |
树结构关系 | return | Fiber | null | 父节点 |
child | Fiber | null | 第一个子节点 | |
sibling | Fiber | null | 下一个兄弟节点 | |
index | number | 在父节点子数组中的索引 | |
状态快照与更新 | pendingProps | any | 新的 props |
memoizedProps | any | 上一次渲染用的 props | |
memoizedState | any | 上一次渲染后的 state | |
updateQueue | mixed | 状态更新队列 | |
dependencies | Dependencies | null | Context 等依赖信息 | |
调度与副作用 | mode | TypeOfMode | Fiber 渲染模式,如并发 |
flags | Flags | 当前 Fiber 的副作用标记 | |
subtreeFlags | Flags | 子树副作用标记汇总 | |
deletions | Fiber[] | null | 需要删除的子节点列表 | |
lanes | Lanes | 当前 Fiber 的优先级通道 | |
childLanes | Lanes | 子树中最高优先级通道 | |
双缓冲机制 | alternate | Fiber | null | 当前 Fiber 的“工作副本” |
性能分析(Profiler) | actualDuration? | number | 当前更新中此 Fiber 的实际耗时 |
actualStartTime? | number | 当前渲染任务开始时间 | |
selfBaseDuration? | number | 自身渲染耗时(跳过不更新不变) | |
treeBaseDuration? | number | 子树总耗时 | |
调试信息(仅 DEV) | _debugInfo? | ReactDebugInfo | null | Fiber 的调试元信息 |
_debugOwner? | Fiber | null | 拥有当前 Fiber 的组件 | |
_debugStack? | string | Error | null | 调试堆栈 | |
_debugTask? | ConsoleTask | null | 调试任务追踪 | |
_debugNeedsRemount? | boolean | 是否需要重新挂载 | |
_debugHookTypes? | HookType[] | null | 用于验证 Hook 顺序的列表 |
延伸阅读
- Fiber Principles: Contributing To Fiber 说明 Fiber 的设计原则
- react-fiber-architecture
react 是如何进行渲染的?
答案
以该代码为例
- 原始代码
- 编译后代码
import React, { Component } from 'https://esm.sh/react@19'
import ReactDOM from 'https://esm.sh/react-dom@19/client'
function _defineProperty (e, r, t) {
return (r = _toPropertyKey(r)) in e
? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
})
: e[r] = t,
e
}
function _toPropertyKey (t) {
const i = _toPrimitive(t, 'string')
return typeof i === 'symbol' ? i : i + ''
}
function _toPrimitive (t, r) {
if (typeof t !== 'object' || !t) { return t }
const e = t[Symbol.toPrimitive]
if (void 0 !== e) {
const i = e.call(t, r || 'default')
if (typeof i !== 'object') { return i }
throw new TypeError('@@toPrimitive must return a primitive value.')
}
return (r === 'string' ? String : Number)(t)
}
function HelloWorld () {
debugger; return /* #__PURE__ */
React.createElement('h1', null, 'Hello, World!')
}
class App extends Component {
constructor () {
super(...arguments)
_defineProperty(this, 'state', {
time: new Date().toLocaleTimeString()
})
}
render () {
debugger; return /* #__PURE__ */
React.createElement('div', null, /* #__PURE__ */
React.createElement(HelloWorld, null), ' ', this.state.time)
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(/* #__PURE__ */
React.createElement(App, null))
一. 编译阶段
- 编写的函数或类组件中 jsx 语法被替换为 React.createElement 函数调用。例如
<div>hello</div>
转换为React.createElement('div', null, 'hello')
,可以采用 @babel/preset-react 进行转换,对于 tsx, tsc 支持 --jsx 控制输出产物为 js 或者是 jsx ,然后交给 babel 进一步处理。
注意此 demo 是在浏览器中运行,实际上编译阶段是在构建代码的时候发生的,此处只是为了说明,不要在浏览器采用这种方式
二.运行时阶段首次加载
- 调用 ReactDOM.createRoot(container) 返回 root 节点 ,核心逻辑包括
- 事件委托,将事件挂载在 container 节点上 ,详见listenToAllSupportedEvents
- 创建 FiberRoot 元素,详见 ReactFiberRoot
- 返回 ReactDOMRoot 对象包含
- render(reactNode) 挂载 reactNode 到 container
- unmount() 卸载 container
- 对象内部属性
_internalRoot
指向 FiberRoot
- 调用
root.render(<App />
) 渲染组件到 container 中,核心逻辑包括- 触发 updateContainerImpl
- 触发 scheduleImmediateRootScheduleTask 这里会异步调度渲染任务默认优先级是, 只考虑浏览器端
- 优先使用 queueMicrotask
- Promise.resolve
- setTimeout
new MessageChannel()
- 异步触发 processRootScheduleInMicrotask,内部调用 scheduleTaskForRootDuringMicrotask, 注册一个异步回调 performWorkUntilDeadline 通过 postMessage 触发,内部执行 performWorkOnRootViaSchedulerTask核心逻辑包括
- flushPendingEffects 清除被挂起的副作用
- performWorkOnRoot 执行渲染任务
- 基于 shouldTimeSlice 判断是 renderRootConcurrent 还是 renderRootSync 默认执行 renderRootSync
- renderRootSync 内部会触发 workLoopSync 来对 fiber 节点执行深度遍历,核心函数包括
- performUnitOfWork(unitOfWork) 从根节点开始已 fiber 节点为粒度执行
- beginWork 根据 fiber 节点类型创建子节点
- reconcileChildren 会基于 FiberNode 的 tag 类型处理子 fiber 的生成,关联 fiber.child 关系
- completeUnitOfWork 当 fiberNode 没有子节点的时候会触发该逻辑,完成状态更新
- completeWork 在该阶段会完成节点的创建绑定到 stateNode 等
- 完成了 workLoop 循环会触发 commitRootWhenReady 内部会调用 commitRoot
- 执行 commitBeforeMutationEffects getSnapshotBeforeUpdate 会在此阶段触发
- 执行 flushMutationEffects 完成
willxx
的事件, 和 effect 中的清除回调 - 执行 flushLayoutEffects 完成 useLayoutEffects 清理回调,和触发事件
- 执行 flushSpawnedWork 完成本次 commit 的相关状态清理动作, 同时触发
- 调度器触发执行异步调度的 performWorkUntilDeadline 执行 flushPassiveEffects 触发 useEffect 的清理回调和触发事件
- renderRootSync 内部会触发 workLoopSync 来对 fiber 节点执行深度遍历,核心函数包括
- 基于 shouldTimeSlice 判断是 renderRootConcurrent 还是 renderRootSync 默认执行 renderRootSync
三.运行时阶状态变更 当通过类似 setState
等改变状态后,会重新触发调度器执行
- performWorkUntilDeadline 然后内部执行 performWorkOnRootViaSchedulerTask,后续流程和首次渲染调用链相同一样会从根节点开始递归执行 render 阶段,但是对于没有变化的 fiberNode 会直接跳过,只收集变化的 fiberNode,而后触发 commit 阶段操作。
延伸阅读
- JSX 转换
- 实现笔记 官方对实现的一些说明
- A (Mostly) Complete Guide to React Rendering Behavior 详细讲解了 react 的渲染机制
- build your own react react mvp 版本
scheduler 调度机制原理
答案
React Scheduler 是 React 在 Concurrent 模式下用来管理更新任务的调度器,核心目的是:
- 解决 UI 卡顿:将大型更新拆分成小任务,在多个帧内执行;
- 实现任务优先级:高优先级更新(如用户交互)可中断低优先级任务,提升响应性;
- 支持可中断 & 恢复:防止主线程长时间被渲染卡住。
核心原理
- 采用 SchedulerPriorities 定义任务优先级, SchedulerFeatureFlags 定义时间分片和超时兜底策略
- unstable_scheduleCallback 处理调度,优先使用 MessageChannel 机制,没有则回退到 setTimeout
- 在 reconciler 中通过 shouldYield 判断是否需要重新调度
diff 算法细节?
答案
基础逻辑参考 The Diffing Algorithm
- 节点类型不同直接替换
- 节点相同类型增量 patch, 触发对应钩子
- 如果是数组比对详见 reconcileChildrenArray、reconcileChildrenIterator
- 首先执行“顺序比较”,从左到右遍历新旧列表,如果 key 和 type 都一致,则复用旧 Fiber 并继续;一旦发现不一致,停止顺序遍历,进入下一阶段。
- React 构建旧 Fiber 节点的 key → fiber 的 Map,用于新节点根据 key 快速查找是否有可复用节点,避免多次扫描旧列表。
- 对于每个新节点,React 根据 key 从旧 Map 中查找是否可复用 fiber,如果没有匹配项则创建新 fiber,并设置 Placement 标志。
- 如果找到复用的 fiber,但其在旧列表中的位置小于上一个复用节点的位置,则说明它“左移”了,React 会标记为需要移动(也打上 Placement 标志)。
- 遍历完新列表后,旧列表中未被复用的 fiber 节点会统一打上 Deletion 标志,在提交阶段进行删除。
- 整个 diff 阶段不会操作 DOM,而是通过 Placement / Update / Deletion 等 effectTag 标记所有变更操作,等待 commit 阶段批量执行。
- React diff 的核心优化点在于 key 的使用;如果没有提供 key 或 key 变化频繁,会导致大量非必要的删除与重建。
React 没有采用双端对比策略(如 Vue2 的双指针算法),只支持单向从左到右比对,原因是 fiber 的结构是一个单向链表,性能在中间插入、左移场景下不如 Vue。React 也未使用“最长递增子序列(LIS)”优化最少移动路径,所以 key 顺序发生变更时,依旧会触发多个移动操作。
react hooks 核心原理?
答案
核心原理
- useState 是 React Hooks 的基础,依赖 Fiber 架构和链表数据结构实现状态管理。
- 每次组件渲染时,React 会按顺序遍历 hooks 链表,确保每个 useState 都能正确获取和更新自己的状态。
底层机制
- 首次渲染时,useState 会在 Fiber 节点上创建一个 hook 对象,保存初始值和更新队列。
- 每次调用 setState,React 会将新的状态更新加入队列,并触发组件重新渲染。
- 渲染时,React 依次遍历 hooks 链表,取出最新状态,保证顺序一致性。
- 状态持久化依赖 Fiber 节点,卸载时清理,挂载时恢复。
代码示例
import React, { useState } from 'react'
function Counter () {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
常见误区
- 不能在条件、循环或嵌套函数中调用 useState,否则会破坏 hooks 链表顺序,导致状态错乱。
- setState 并非立即更新,而是异步批量处理,避免多次渲染。
实际开发建议
- Hooks 顺序必须保持一致,避免在条件语句中调用。
- 利用批量更新提升性能,合理拆分组件。
Hooks 本质是链表,顺序决定状态归属,务必保证每次渲染调用顺序一致。
延伸阅读
ref 是如何实现的?
答案
核心原理
- ref 是 React 提供的访问 DOM 或组件实例的机制,底层通过 Fiber 节点和 hooks 链表实现数据持久化和共享。
- useRef 返回一个稳定的对象
{ current }
,每次渲染都保持引用不变,避免因变化导致组件重渲染。
实现机制
- 函数组件中,useRef 在 Fiber 节点的 hooks 链表上创建一个 ref 对象,挂载到
memoizedState
,每次渲染都复用同一个对象。 - 类组件中,ref 属性会在组件挂载时自动赋值为实例或 DOM 节点,卸载时清空。
- forwardRef 用于将 ref 透传到子组件,支持函数组件访问 DOM。
代码示例
import React, { useRef, forwardRef } from 'react'
// useRef 用法
function Demo () {
const inputRef = useRef(null)
return <input ref={inputRef} />
}
// forwardRef 用法
const FancyInput = forwardRef((props, ref) => <input ref={ref} {...props} />)
常见误区
- useRef 变化不会触发组件重新渲染,适合存储副作用或 DOM 节点。
- ref 不是响应式数据,不能用于驱动视图更新。
实际开发建议
- 访问 DOM、保存定时器、缓存值优先用 useRef。
- 组件间 ref 传递用 forwardRef,避免 props 传递冗余。
useRef 返回的对象始终稳定,适合存储跨渲染周期的可变数据。
延伸阅读
为什么不能在循环、条件或嵌套函数中调用 Hooks?
答案
核心原理
- React Hooks(如 useState/useEffect)底层通过链表结构存储,每次渲染按顺序遍历 hooks 链表,确保每个 hook 能正确获取自己的状态。
- 如果在循环、条件或嵌套函数中调用 hooks,会导致每次渲染时 hooks 顺序不一致,链表结构错乱,状态无法正确归属,React 会直接抛错。
底层机制
- 首次渲染时,React 按代码顺序依次创建 hook 节点,追加到 Fiber 节点的 hooks 链表。
- 更新时,React 依次遍历链表,取出对应的状态和副作用。
- 顺序错乱会导致取值错位,状态混乱,甚至出现“状态丢失”或“状态错位”问题。
代码示例
// 错误用法
if (flag) {
const [a, setA] = useState(0)
}
const [b, setB] = useState(1) // 状态归属不确定,可能出错
常见误区
- Hooks 本质不是数组,而是链表,顺序决定状态归属。
- 不能在条件、循环、嵌套函数中调用 hooks,必须在组件顶层保持顺序一致。
实际开发建议
- 所有 hooks 必须在组件顶层调用,保证每次渲染顺序一致。
- 避免在 if/for/函数内部调用 hooks,必要时拆分组件。
Hooks 顺序决定状态归属,顶层调用是硬性规范,违背会直接报错。
延伸阅读
为什么要自定义合成事件
答案
核心原理
- React 合成事件系统通过事件委托和统一 API,解决浏览器兼容性、性能和安全问题。
- 合成事件对象是 React 自己实现的,具备一致的属性和行为,简化事件处理和调试。
优势说明
- 跨浏览器一致性:所有事件都通过统一接口处理,避免不同浏览器差异。
- 性能优化:事件委托到根节点,减少 DOM 监听数量,支持事件池复用,降低内存消耗。
- 简化事件处理:只需关注冒泡阶段,API一致,易于维护。
- 安全性和可控性:防止 XSS,便于事件管理和清理,生命周期集成。
- 与 React 特性集成:支持虚拟 DOM、状态管理、自动解绑,提升开发体验。
触发顺序说明
- 合成事件优先于原生事件执行,只有未阻止冒泡时才会触发原生事件。
- 早期 React 事件委托机制可能导致原生事件先执行,现代版本已优化为“先合成后原生”。
代码示例
function Demo () {
function handleClick (e) {
e.stopPropagation() // 阻止原生事件
console.log('React 合成事件')
}
return <div onClick={handleClick}>点击我</div>
}
实际开发建议
- 推荐始终使用 React 合成事件,避免直接 addEventListener,保证兼容性和性能。
- 复杂交互场景可结合原生事件,但需注意事件顺序和解绑。
合成事件统一管理,便于跨平台和调试,适合绝大多数业务场景。
延伸阅读
lazy import
答案
核心原理
- React.lazy 利用 JavaScript 的动态
import()
实现组件的异步加载和代码分割。 - 当组件首次渲染时,lazy 返回的特殊组件会触发
import()
,将目标组件代码单独打包并按需加载。
实现机制
- lazy(fn) 接收一个返回 Promise 的函数,内部会等待 Promise resolve 后获取组件定义。
- 加载期间,需用
<Suspense>
包裹,fallback 属性用于显示加载占位符。 - 加载完成后,React 自动渲染异步加载的组件,未加载时显示 fallback。
代码示例
import React, { lazy, Suspense } from 'react'
const MyComponent = lazy(() => import('./MyComponent'))
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
)
}
常见误区
- lazy 只能用于默认导出的组件,命名导出需额外处理。
- 必须配合 Suspense,否则加载期间会报错。
实际开发建议
- 适合路由、弹窗等大体积组件的按需加载,提升首屏性能。
- fallback 可自定义为骨架屏、动画等,优化用户体验。
lazy 结合 Suspense 可实现高效代码分割,建议用于大型项目的性能优化。
延伸阅读
react 优化手段有哪些?
答案
优化手段 | 主要方式/工具 | 优势/典型场景 |
---|---|---|
代码分割 | Suspense、lazy、react-loadable | 按需加载,减少首屏体积 |
组件颗粒化 | 独立请求渲染单元 | 降低父组件冗余渲染 |
性能调优 | PureComponent、React.memo | 浅比较 props/state,减少无效渲染 |
不可变数据 | immutable.js、immer.js | 高效比较,提升 diff 性能 |
函数缓存 | useMemo、useCallback、ahooks | 避免重复声明,提升子组件复用 |
状态管理优化 | connect、recoil | 精细化订阅,减少链式渲染 |
事件绑定优化 | 事件委托 | 降低 DOM 监听数量 |
详细说明
- 代码分割:通过
React.lazy
和<Suspense>
实现异步加载,react-loadable
支持自定义加载动画,适合路由、弹窗等大体积组件。 - 组件颗粒化:将依赖数据请求的组件拆分为独立渲染单元,减少父组件渲染影响,提升性能。
- 性能调优:
PureComponent
、React.memo
通过浅比较 props/state,避免无关组件渲染;shouldComponentUpdate
可自定义渲染条件。 - 不可变数据:
immutable.js
、immer.js
提供高效不可变数据结构,配合 diff 算法提升性能。 - 函数缓存:
useMemo
、useCallback
、ahooks
避免重复声明函数,提升子组件 memo 效果。 - 状态管理优化:
connect
精细化订阅,recoil
细粒度状态管理,减少全局渲染。 - 事件绑定优化:React 事件委托机制,减少 DOM 事件监听,提升整体性能。
常见误区
- 过度颗粒化可能增加维护成本,需结合实际场景权衡。
- memo 只对 props 浅比较,深层数据需配合不可变数据结构。
实际开发建议
- 优先使用 lazy/Suspense 实现代码分割,结合 memo/immutable 优化渲染。
- 复杂状态建议拆分为独立渲染单元,避免全局状态变更导致链式渲染。
性能优化需结合业务场景,建议先分析瓶颈再选用合适手段,避免过度优化。
延伸阅读
什么时候 React 会发生 re-render 如何避免?
答案
延伸阅读
- Blogged Answers: A (Mostly) Complete Guide to React Rendering Behavior 详细讲解 React 渲染逻辑
- Why React Re-Renders 交互式理解 React 渲染逻辑
- Writing Resilient Components 说明编写 React 组件的通用规则
在 React 应用中如何排查性能问题?
在 React 应用中,可以通过以下方法来排查性能问题:
一、使用 Chrome 开发者工具
- 性能分析(Performance):
- 打开 Chrome 浏览器,进入开发者工具。选择“Performance”选项卡。
- 点击“Record”按钮开始录制页面的交互过程。进行一些典型的操作,如加载页面、点击按钮、滚动页面等。
- 停止录制后,开发者工具会生成一个性能分析报告。这个报告显示了页面在录制期间的各种性能指标,包括 CPU 使用率、内存使用情况、网络请求等。
- 分析报告中的“Main”线程,可以查看在录制期间哪些操作占用了大量的 CPU 时间。常见的性能瓶颈包括长时间的 JavaScript 执行、频繁的重渲染等。
- 例如,如果发现某个函数的执行时间很长,可以点击该函数查看详细的调用栈,以确定问题的根源。
- React 开发者工具(React Developer Tools):
- 安装 React 开发者工具插件。在 Chrome 浏览器中,打开需要排查性能问题的 React 应用页面。
- 打开开发者工具,选择“React”选项卡。
- 在 React 开发者工具中,可以查看组件的层次结构、Props 和 State。这有助于确定哪些组件的状态变化频繁,或者哪些组件的渲染时间较长。
- 特别关注那些在不必要的时候触发重新渲染的组件。可以通过检查组件的
shouldComponentUpdate
方法或使用React.memo
、PureComponent
等优化手段来减少不必要的重新渲染。
二、使用 React Profiler
- 开启 Profiler:
- 在 React 应用中,可以使用
React.Profiler
组件来进行性能分析。在需要分析性能的组件树的根节点处包裹React.Profiler
。 - 例如:
import React from "react";
import ReactDOM from "react-dom";
import { Profiler } from "react";
const App = () => (
<Profiler
id="MyApp"
onRender={(id, phase, actualDuration) => {
console.log(`Component ${id} rendered in phase ${phase} with duration ${actualDuration} ms.`);
}}
>
{/* 你的应用组件 */}
</Profiler>
);
ReactDOM.render(<App />, document.getElementById("root"));
- 分析结果:
- 在应用运行过程中,
React.Profiler
会记录组件的渲染时间和其他性能指标。可以在控制台中查看输出的日志,了解每个组件的渲染时间和触发渲染的原因。 - 根据日志信息,可以确定哪些组件的渲染时间较长,以及哪些操作导致了频繁的重新渲染。这有助于针对性地进行性能优化。
三、检查代码中的潜在问题
- 避免不必要的重新渲染:
- 确保组件的
shouldComponentUpdate
方法正确实现,或者使用React.memo
和PureComponent
来避免不必要的重新渲染。检查组件的依赖项是否正确设置,避免因为不必要的状态变化而触发重新渲染。 - 例如,如果一个组件的渲染结果只依赖于某个特定的 prop,而不是所有的 props,可以使用
React.memo
并指定一个自定义的比较函数来进行更精确的比较。
- 优化大型列表渲染:
- 对于大型列表的渲染,考虑使用
React.memo
和key
属性来优化性能。确保为每个列表项设置一个唯一的key
属性,这有助于 React 更高效地识别列表项的变化。 - 避免在列表渲染中使用索引作为
key
属性,因为这可能会导致性能问题。如果列表项的顺序可能发生变化,使用一个稳定的唯一标识符作为key
。
- 减少不必要的计算和副作用:
- 检查代码中是否存在不必要的计算或副作用。例如,避免在
render
方法中进行复杂的计算或发起网络请求。将这些操作移到生命周期方法或使用useEffect
钩子中,并确保副作用的依赖项正确设置,以避免不必要的执行。 - 对于频繁执行的计算,可以考虑使用 memoization(记忆化)技术来缓存结果,避免重复计算。
- 优化网络请求:
- 检查应用中的网络请求是否高效。避免频繁的重复请求,使用缓存策略来减少请求次数。确保网络请求的响应时间合理,可以使用工具来监测网络请求的性能,并考虑优化服务器端的响应时间。
通过以上方法,可以系统地排查 React 应用中的性能问题,并采取相应的优化措施来提高应用的性能和响应速度。
如何确定哪个数据变化引起的组件渲染?
帮助开发者排查是哪个属性改变导致了组件的 rerender。
直接接受 ahooks 里面的一个方法: useWhyDidYouUpdate
源码实现:
import { useEffect, useRef } from 'react'
export type IProps = Record<string, any>;
export default function useWhyDidYouUpdate (componentName: string, props: IProps) {
const prevProps = useRef<IProps>({})
useEffect(() => {
if (prevProps.current) {
const allKeys = Object.keys({ ...prevProps.current, ...props })
const changedProps: IProps = {}
allKeys.forEach((key) => {
if (!Object.is(prevProps.current[key], props[key])) {
changedProps[key] = {
from: prevProps.current[key],
to: props[key]
}
}
})
if (Object.keys(changedProps).length) {
console.log('[why-did-you-update]', componentName, changedProps)
}
}
prevProps.current = props
})
}