JavaScript触发onScroll事件的函数节流详解
问题描述
常见的网站布局,顶部一个导航栏,我们假设本页面共有四个栏目:分别为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事件触发函数是这样的(忽略一长串的ifelse,这是一个解决了一下午的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;
}elseif(this.main.scrollTop<mainpeople){
//股东信息区域
changeScrollFlag(2);
if(holderinformation===-121){
//如果股东信息栏目不存在,在滚动的时候我们不应该强行把TileBox的高亮按钮设置为holderinformation
//因为holdinformation并不存在,我们跳到前一个按钮,让基础信息按钮高亮
changeScrollFlag(1);
return;
}
return;
}elseif(this.main.scrollTop<changerecord){
//主要人员区域
changeScrollFlag(3);
if(mainpeople===-121){
//如果主要人员栏目不存在,在滚动的时候我们不应该强行把TileBox的高亮按钮设置为mainpeople
//mainpeople并不存在,我们跳到前一个按钮,让基础信息按钮高亮
changeScrollFlag(2);
if(holderinformation===-121){
//如果主要人员栏目不存在,而且连股东信息栏目也没有,我们跳到高亮基础信息栏目
changeScrollFlag(1);
return;
}
return;
}
return;
}elseif(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函数节流
letprevious=0;
//previous初始设置上一次调用onScroll函数时间点为0。
lettimeout;
constwait=250;
//250毫秒触发一次
return()=>{
constnow=Date.now();
constremaining=wait-(now-previous);
if(remaining<=0){
if(timeout){
window.clearTimeout(timeout);
}
previous=now;
timeout=null;
this.handleScroll();
}elseif(!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')}
>
在此,我们完成了一次从顶层组件触发事件,并做到函数节流,将事件一层层传递至底层展示型组件的一个过程。
最近一些关于前端开发的感慨
- 不要在组件中反复调用一个函数,这样会造成巨大的消耗!我们可以通过三元运算符、模板字符串做到的事情,请勿写一个新的函数。
- jsx不要太过于冗余。我们尽量写成变量的形式,不然页面结构复杂,不易于我们捕捉bug。
- 减少后端请求,能存cookie则存cookie,能存localStorge则存localStorge。
- 简单的组件尽量自己写,请勿使用别人的组件,否则在需求更改、样式调整上会出现巨大困难并做一些无意义的事儿。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。