問題描述
常見的網站布局,頂部一個導航欄,我們假設本頁面共有四個欄目:分別為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')}
>
在此,我們完成了一次從頂層組件觸發事件,並做到函數節流,將事件一層層傳遞至底層展示型組件的一個過程。
最近一些關於前端開發的感慨
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。