当前位置: 首页 > news >正文

React性能优化精髓之一:频繁setState导致滚动卡顿的解决方案

在开发一个 List 页面时,我们遇到了一个典型的React性能问题:页面在滚动时出现明显卡顿。这个问题的调试过程充满了误判和重新思考,最终发现了一个重要的性能优化原则。

问题现象

我们有一个监控仪表盘页面,包含多个图表组件。用户在滚动浏览图表时,页面出现了明显的卡顿现象,特别是在图表数量较多时,滚动体验极差。

// 简化的组件结构
const Dashboard = () => {const [graphs, setGraphs] = useState([]);const [graphsVisibleMap, setGraphsVisibleMap] = useState({});return (<div>{graphs.map(graph => (<GraphComponent key={graph.id}data={graph}onVisibilityChange={handleGraphVisibleChange}/>))}</div>);
};

初步分析:怀疑IntersectionObserver

最初我们怀疑是IntersectionObserver配置不当导致的性能问题,因为我们使用它来监听图表的可见性:

// 初始的 IntersectionObserver 实现
setupIntersectionObserver() {this.observer = new IntersectionObserver((entries) => {entries.forEach((entry) => {const graphId = entry.target.getAttribute('data-graph-id');if (graphId) {// 每次可见性变化都会调用这里this.props.onGraphVisibleChange(Number(graphId), entry.isIntersecting, { graphRefs: Object.keys(this.graphRefs) });}});}, {threshold: 0.1});
}

错误的优化尝试

基于这个假设,我们尝试了多种IntersectionObserver的优化方案:

  1. 减少触发频率
// 尝试1:提高阈值,减少rootMargin
{threshold: 0.5,      // 只在50%可见时触发rootMargin: '0px',   // 移除提前触发
}
  1. 添加防抖和节流
// 尝试2:使用防抖处理回调
this.observer = new IntersectionObserver(_.debounce((entries) => {// 处理逻辑}, 100), options
);
  1. 批量处理可见性变化
// 尝试3:缓存变化,批量处理
const visibilityBuffer = new Map();this.observer = new IntersectionObserver((entries) => {entries.forEach((entry) => {const graphId = entry.target.getAttribute('data-graph-id');if (graphId) {visibilityBuffer.set(graphId, entry.isIntersecting);}});// 延迟批量处理setTimeout(() => {this.processBatchedUpdates(visibilityBuffer);}, 50);
}, options);

结果:这些优化都没有解决根本问题。

关键发现:真正的罪魁祸首

// 性能杀手:滚动时频繁调用setState
handleGraphVisibleChange = (id, visible) => {const newMap = { ...this.state.graphsVisibleMap };newMap[id] = visible;this.setState({ graphsVisibleMap: newMap }); // 每秒可能调用数百次
};

影响:

  • 滚动时每秒数百次 setState 调用
  • 每次调用触发组件重渲染
  • 主线程被阻塞,造成卡顿

解决方案

将不需要触发重渲染的数据从 state 移到实例属性:

class Component extends React.Component {constructor() {super();// 移到实例属性this.graphsVisibleMap = {};this.state = {// graphsVisibleMap: {}, // 删除这行// 只保留需要触发重渲染的数据};}handleGraphVisibleChange = (id, visible) => {// 直接修改实例属性,不触发重渲染this.graphsVisibleMap[id] = visible;// 不调用 setState};
}

总结

  • state: 需要触发重渲染的UI相关数据
  • 实例属性: 高频更新但不影响UI的内部状态

人生感悟:不是所有数据都需要放在 state 中,合理的数据分层比复杂的防抖节流更有效。

http://www.lryc.cn/news/574637.html

相关文章:

  • 新增MCP接入和AutoAgent,汉得灵猿AI中台1.6版正式发布!
  • 【软考高级系统架构论文】论单元测试方法及应用
  • Linux离线安装mysql
  • 探秘深蓝 “引擎”:解码水下推进器的科技与应用
  • Flask(四) 模板渲染render_template
  • Dify×奇墨科技:开源+本土化,破解企业AI落地难题
  • Chrome MCP Server:AI驱动浏览器自动化测试实战「喂饭教程」
  • iframe窗体默认白色背景去除
  • 重点解析(软件工程)
  • 云电脑,“死”于AI时代前夕 | 数智化观察
  • 基于DE1-SoC的My_First_oneAPI(二)
  • 黑马Day01-03集开始
  • 第24篇:Linux内核深度解析与OpenEuler 24.03实践指南
  • TCP/UDP协议深度解析(一):UDP特性与TCP确认应答以及重传机制
  • 交易期权先从买方开始
  • C8BJWD8BJV美光固态闪存HSA22HSA29
  • android脱糖
  • Kubernetes生命周期管理:深入理解 Pod 生命周期
  • python有哪些常用的GUI(图形用户界面)库及选择指南
  • Unity Text-Mesh Pro无法显示中文的问题
  • Android检测当前进程或者应用是否被调试
  • 安卓android com.google.android.material.tabs.TabLayout 设置下拉图标无法正常显示
  • 国产化条码类库Spire.Barcode教程:如何使用 C# 读取 PDF 中的条码(两种方法轻松实现)
  • 【数字后端】- 什么是NDR规则?
  • vscode打开.c文件后中文乱码
  • ros(一)使用消息传递图像+launch启动文件
  • 通过Prompt提示构建思维链
  • git操作练习(3)
  • WHAT - React Native 的 Expo Router
  • 华为云Flexus+DeepSeek征文|华为云ModelArts Studio:利用New API实现大模型网关与AI资产管理的无缝对接