「框架篇」React 中 的 9 種優化技術

谷歌的數據代表,一個有 10 條數據 0.4 秒能夠加載完的頁面,在變成 30 條數據加載時間爲 0.9 秒後,流量和廣告收入減小了 20%。當谷歌地圖的首頁文件大小從 100kb 減小到 70~80kb 時,流量在第一週漲了 10%,接下來的三週漲了 25%。
javascript

騰訊的前端工程師根據長期的數據監控也發現頁面的一秒鐘延遲會形成 9.4% 的 PV 的降低,8.3% 跳出率的增長以及 3.5% 轉化率的降低。前端

能夠看出,性能優化商業上來講很重要。java

可是,更重要的仍是屏幕前咱們的用戶,讓用戶在使用產品時有更快更溫馨的瀏覽體驗,這算是一種前端工程師的自我修養。react

因此今天就分享一下如何去優化咱們的 React 項目,進而提高用戶體驗。express


1
使用React.Fragment 來避免向 DOM 添加額外的節點


咱們在寫 React 代碼時,會常常遇到返回一組元素的狀況,代碼像這樣:數組


class Parent extends React.Component { render() { return ( <h1>Hello there!</h1> <h1>Hello there again!</h1> ) }}

若是咱們寫成這樣,控制檯會報錯誤: JSX parent expressions must have one parent element   ,告訴咱們只能返回一個元素,因此咱們一般會在最外層包裹一個 div 元素,以下所示:

class Parent extends React.Component { render() { return (         <div>         <h1>Hello there!</h1> <h1>Hello there again!</h1>         </div> ) }}

這樣作雖然能正常執行,可是會額外建立沒必要要的 DOM 節點,這可能會致使建立許多無用的元素,而且在咱們的渲染數據來自特定順序的子組件時,某些狀況下也會生成許多無效的節點。請考慮如下代碼:

class Table extends React.Component { render() { return ( <table> <tr> <Columns /> </tr> </table> ); }}
class Columns extends React.Component { render() { return ( <div> <td>column one</td> <td>column two</td> </div> ); }}

上面的代碼將在咱們的組件中呈現如下內容:

<table> <tr> <div> <td>column one</td> <td>column two</td> </div> </tr></table>

這顯然不是咱們想看到的,React 爲咱們提供了 Fragments Fragments 容許咱們將子列表分組,而無需向 DOM 添加額外節點。咱們能夠將組件從新編寫爲:

class Columns extends React.Component { render() { return ( <React.Fragment> <td>column one</td> <td>column two</td> </React.Fragment> ); }}

2
使用 React.Lazy 延遲加載組件

有時咱們只想在請求時加載部分組件,例如,僅在單擊購物車圖標時加載購物車數據,在用戶滾動到該點時在長圖像列表的底部加載圖像等。瀏覽器


React.Lazy 幫助咱們按需加載組件,從而減小咱們應用程序的加載時間,由於只加載咱們所需的組件。緩存


React.lazy 接受一個函數,這個函數須要動態調用 import()。它必須返回一個 Promise,該 Promise 須要 resolve 一個 defalut export 的 React 組件。以下所示:性能優化


class MyComponent extends Component{ render() { return (<div>MyComponent</div>) }}const MyComponent = React.lazy(()=>import('./MyComponent.js'))function App() { return (<div><MyComponent /></div>)}

在編譯時,使用 Webpack 解析到該語法時,它會自動地開始進行代碼分割。最終,咱們的應用程序將會被分紅含有多個 UI 片斷的包,這些 UI 片斷將在須要時加載,若是你使用 Create React App,該功能已配置好,你能馬上使用 這個特性。 Next.js  也已支持該特性而無需再配置。

3
使用React.Suspense


在交換組件時,會出現一個小的時間延遲,例如在 MyComponent 組件渲染完成後,包含 OtherComponent 的模塊尚未被加載完成,這可能就會出現白屏的狀況,咱們可使用加載指示器爲此組件作優雅降級,這裏咱們使用 Suspense 組件來解決。微信


React.Suspense 用於包裝延遲組件以在加載組件時顯示後備內容。


// MyComponent.jsconst Mycomponent = React.lazy(()=>import('./component.js'))function App() { return ( <div> <Suspense fallback={<div>loading ..</div>}> <MyComponent /> </Suspense> </div>)}


上面的代碼中,fallback 屬性接受任何在組件加載過程當中你想展現的 React 元素。


你能夠將 Suspense 組件置於懶加載組件之上的任何位置,你甚至能夠用一個 Suspense 組件包裹多個懶加載組件。


const OtherComponent = React.lazy(() => import('./OtherComponent'));const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <section> <OtherComponent /> <AnotherComponent /> </section> </Suspense> </div> );}

5
使用 shouldComponentUpdate() 防止沒必要要的從新渲染


當一個組件的 propsstate 變動,React 會將最新返回的元素與以前渲染的元素進行對比,以此決定是否有必要更新真實的 DOM,當它們不相同時 React 會更新該 DOM。


即便 React 只更新改變了的 DOM 節點,從新渲染仍然花費了一些時間。在大部分狀況下它並非問題,可是若是渲染的組件很是多時,就會浮現性能上的問題,咱們能夠經過覆蓋生命週期方法 shouldComponentUpdate 來進行提速。


shouldComponentUpdate 方法會在從新渲染前被觸發。其默認實現老是返回 true,若是組件不須要更新,能夠在 shouldComponentUpdate 中返回 false 來跳過整個渲染過程。其包括該組件的 render 調用以及以後的操做。


shouldComponentUpdate(nextProps, nextState) {   return nextProps.next !== this.props.next  }

6
使用React.PureComponent 


React.PureComponent 與 React.Component 很類似。二者的區別在於 React.Component並未實現 shouldComponentUpdate(),而 React.PureComponent 中以淺層對比 prop 和 state 的方式來實現了該函數。


若是賦予 React 組件相同的 props 和 state,render() 函數會渲染相同的內容,那麼在某些狀況下使用 React.PureComponent 可提升性能。


// 使用 React.PureComponentclass MyComponent extends React.PureComponent { render() { return (<div>MyComponent</div>) }}
class MyComponent extends React.Component { render() { return (<div>MyComponent</div>) }}

React.PureComponent 中的 shouldComponentUpdate() 僅做對象的淺層比較。若是對象中包含複雜的數據結構,則有可能由於沒法檢查深層的差異,產生錯誤的比對結果。僅在你的 props 和 state 較爲簡單時,才使用 React.PureComponent,或者在深層數據結構發生變化時調用 forceUpdate() 來確保組件被正確地更新。你也能夠考慮使用 immutable 對象加速嵌套數據的比較。


7
使用 React.memo 來緩存組件

React.memo 使用了緩存,緩存技術用於經過存儲昂貴的函數調用的結果來加速程序,並在再次發生相同的輸入時返回緩存的結果。


若是你的函數組件在給定相同 props 的狀況下渲染相同的結果,那麼你能夠經過將其包裝在  React.memo  中調用,以此經過記憶組件渲染結果的方式來提升組件的性能表現。這意味着在這種狀況下,React 將跳過渲染組件的操做並直接複用最近一次渲染的結果。

默認狀況下其只會對複雜對象作淺層對比,若是你想要控制對比過程,那麼請將自定義的比較函數經過第二個參數傳入來實現。

const MyComponent = ({user}) =>{ const {name, occupation} = user; return ( <div> <h4>{name}</h4> <p>{occupation}</p> </div> )}// 比較函數function areEqual(prevProps, nextProps) { /* 若是把 nextProps 傳入 render 方法的返回結果與 將 prevProps 傳入 render 方法的返回結果一致則返回 true, 不然返回 false */}export default React.memo(MyComponent, areEqual);

8
使用 ComponentDidUnmount() 刪除未使用的DOM 元素 


有些時候,存在一些未使用的代碼會致使內存泄漏的問題,React 經過向咱們提供componentWillUnmount 方法來解決這個問題。


componentWillUnmount() 會在組件卸載及銷燬以前直接調用。在此方法中執行必要的清理操做,例如,清除 定時器,取消網絡請求或清除在 componentDidMount() 中建立的訂閱等。


例如,咱們能夠在組件銷燬以前,清除一些事件處理程序:

componentWillUnmount() { document.removeEventListener("click", this.closeMenu);}


componentWillUnmount() 中不該調用 setState(),由於該組件將永遠不會從新渲染。組件實例卸載後,將永遠不會再掛載它。


9
其餘優化技術

虛擬化長列表

若是你的應用渲染了長列表(上百甚至上千的數據),咱們推薦使用「虛擬滾動」技術。這項技術會在有限的時間內僅渲染有限的內容,並奇蹟般地下降從新渲染組件消耗的時間,以及建立 DOM 節點的數量。

react-window 和 react-virtualized 是熱門的虛擬滾動庫。它們提供了多種可複用的組件,用於展現列表、網格和表格數據。若是你想要一些針對你的應用作定製優化,你也能夠建立你本身的虛擬滾動組件,就像 Twitter 所作的。


使用 Chrome Performance 標籤分析組件


開發模式下,你能夠經過支持的瀏覽器可視化地瞭解組件是如何 掛載、更新以及卸載的。例如:



在 Chrome 中進行以下操做:

  1. 臨時禁用全部的 Chrome 擴展,尤爲是 React 開發者工具。他們會嚴重干擾度量結果!
  2. 確保你是在 React 的開發模式下運行應用。
  3. 打開 Chrome 開發者工具的 Performance 標籤並按下 Record
  4. 對你想分析的行爲進行復現。儘可能在 20 秒內完成以免 Chrome 卡住。
  5. 中止記錄。
  6. 在 User Timing 標籤下會顯示 React 歸類好的事件。



最後,咱們探索了一些能夠優化 React 應用程序的一些提升性能的方法,不侷限於此。咱們應該根據須要有針對性的優化應用程序,由於在某些簡單的場景中,過分的優化,可能會得不償失。

🎀推薦閱讀

  1. React.lazy() 和 Suspense 介紹及用法

  2. 使用 React.memo() 提高react應用性能



長按識別下方二維碼,關注咱們吧(づ ̄3 ̄)❤~


來都來了,點個【好看】再走吧~~~


本文分享自微信公衆號 - 像素搖擺(pxDance)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索