React中onScroll事件函数节流与状态同步实现

作者:晴川时间:2026-10-30 18:47:57浏览:459

本文详解在React组件化开发中,如何通过函数节流优化onScroll滚动事件的性能,并结合Redux将滚动状态同步至子组件。重点解析了节流函数的时间戳控制逻辑,以及在componentWillReceiveProps中正确接收props变化以更新高亮样式的关键步骤,避免无效渲染。

问题背景与解决方案概述

问题描述

常见的网站布局通常包含顶部导航栏,假设页面分为A、B、C、D四个栏目。当点击导航栏的A按钮时,页面锚点跳转至A栏目,同时导航栏上的A按钮显示高亮状态;同理,点击B按钮则跳转至B栏目并高亮B按钮。此外,当用户在Main组件区域内滚动页面至B模块时,导航栏上的B按钮也应自动高亮。这是前端开发中常见的交互模型,过去使用jQuery实现时较为熟悉。

解决方案

本文主要探讨在React组件化开发背景下,针对滚动事件的性能优化方法。

页面结构与事件绑定

页面结构如下所示:


<div
 className={style.main}
 id="main"
 ref={(main) => { this.main = main; }}
 onScroll={
 ((/detail/.test(this.props.location.pathname))) ? (() => this.throttle()()) : null
 }
>
 {this.props.children}
 <Footer />

在Main组件中绑定onScroll事件,该事件触发相应的action,并通过Redux将状态变化传递给子组件。

具体的scroll事件触发函数如下(忽略冗长的if-else逻辑,这是解决一个耗时一下午的Bug的最终方案,此处不作详细展开):


 handleScroll() {
 const { changeScrollFlag } = this.props.actions;
 // 根据滚动距离修改TitleBox的样式
 const { basicinformation, holderinformation, mainpeople, changerecord } = {
 basicinformation: document.getElementById('basicinformation').offsetTop - 121,
 holderinformation: document.getElementById('holderinformation').offsetTop - 121,
 mainpeople: document.getElementById('mainpeople').offsetTop - 121,
 changerecord: document.getElementById('changerecord').offsetTop - 121,
 };
 if (window.screen.availHeight > this.main.scrollTop) {
 document.getElementById('gototop').style.display = 'none';
 } else {
 document.getElementById('gototop').style.display = 'block';
 }
 // 得到基础信息区域、股东信息区域、主要人员区域、变更记录区域的offsetTop,我们把它用来跟main的scrollTop比较
 // 比较的结果触发action,改变TitleBox组件样式
 if (this.main.scrollTop < holderinformation) {
 // 基础信息区域
 if (basicinformation === -121) {
 // 如果基础信息模块不存在,我们什么也不做(当然理论上基础信息模块应该是会有的)
 return;
 }
 changeScrollFlag(1);
 return;
 } else if (this.main.scrollTop < mainpeople) {
 // 股东信息区域
 changeScrollFlag(2);
 if (holderinformation === -121) {
 // 如果股东信息栏目不存在,在滚动的时候我们不应该强行把TileBox的高亮按钮设置为holderinformation
 // 因为holdinformation并不存在,我们跳到前一个按钮,让基础信息按钮高亮
 changeScrollFlag(1);
 return;
 }
 return;
 } else if (this.main.scrollTop < changerecord) {
 // 主要人员区域
 changeScrollFlag(3);
 if (mainpeople === -121) {
 // 如果主要人员栏目不存在,在滚动的时候我们不应该强行把TileBox的高亮按钮设置为mainpeople
 // mainpeople并不存在,我们跳到前一个按钮,让基础信息按钮高亮
 changeScrollFlag(2);
 if (holderinformation === -121) {
 // 如果主要人员栏目不存在,而且连股东信息栏目也没有,我们跳到高亮基础信息栏目
 changeScrollFlag(1);
 return;
 }
 return;
 }
 return;
 } else if (this.main.scrollTop > changerecord) {
 // 与上面同理
 // 变更记录区域
 changeScrollFlag(4);
 if (changerecord === -121) {
 changeScrollFlag(3);
 if (mainpeople === -121) {
 changeScrollFlag(2);
 if (holderinformation === -121) {
  changeScrollFlag(1);
  return;
 }
 return;
 }
 return;
 }
 return;
 }
}

其中,changeScrollFlag()函数负责处理action。

函数节流实现机制

接下来介绍函数节流的具体实现。


throttle() {
 // onScroll函数节流
 let previous = 0;
 // previous初始设置上一次调用 onScroll 函数时间点为 0。
 let timeout;
 const wait = 250;
 // 250毫秒触发一次
 return () => {
 const now = Date.now();
 const remaining = wait - (now - previous);
 if (remaining <= 0) {
 if (timeout) {
 window.clearTimeout(timeout);
 }
 previous = now;
 timeout = null;
 this.handleScroll();
 } else if (!timeout) {
 timeout = window.setTimeout(this.handleScroll, wait);
 }
 };
}

我们的节流函数返回一个新的函数,该函数设定一个时间戳。如果当前时间与上次调用时间的差值较小,则不执行任何操作;如果差值较大,则清除定时器并触发scroll函数。从逻辑上看,这似乎相当简单。

子组件状态同步与生命周期

那么在子组件中还需要进行哪些操作呢?

子组件需要接收action。

二级容器型组件接收action,并通过传递props至三级展示型组件。

必须在componentWillReceiveProps生命周期中接收这个props。

需要注意的是,在componentWillReceiveProps中使用this.props并不能直接接收到props的变化!!!组件生命周期函数包含一个自己的参数。


 componentWillReceiveProps(nextProps) {
 // 在compoWillReceiveProps里接收到Main组件里所触发onScroll事件的改变activebtn样式的index
 // 并且设置为本组件的state
 this.setState({
 activebtn: nextProps.scrollFlag.scrollIndex,
 });
}

我们的state用于控制高亮按钮的索引,它是一个数字。

接下来更改导航条的样式。

在此处,我使用了React生态库classnames,具体用法可参考其API文档。


<span
 className={classnames({
 [style.informationactive]: (this.state.activebtn === 1),
 })}
 onClick={() => this.handleClick(1, 'basicinformation')}
>

开发建议与总结

至此,我们完成了一次从顶层组件触发事件,经过函数节流处理,并将事件层层传递至底层展示型组件的全过程。 近期关于前端开发的一些思考

  1. 避免在组件中反复调用同一个函数,这会造成巨大的性能消耗!如果可以通过三元运算符或模板字符串实现的功能,请勿编写新的函数。
  2. JSX代码不应过于冗余。尽量将页面结构写成变量形式,否则页面结构复杂,不利于排查Bug。
  3. 减少后端请求,能存储Cookie则存储Cookie,能使用localStorage则使用localStorage。
  4. 简单的组件尽量自行编写,请勿随意使用第三方组件,否则在需求变更或样式调整时会遇到巨大困难,并导致大量无意义的工作。

总结

以上就是本文的全部内容,希望本文的内容对大家的学习或工作能带来一定的帮助。如果有疑问,欢迎留言交流。

作者最新文章
相关文章
更多