1. 程式人生 > 實用技巧 >React 中 的 9 種優化技術

React 中 的 9 種優化技術

谷歌的資料表明,一個有 10 條資料0.4 秒可以載入完的頁面,在變成 30 條資料載入時間為 0.9 秒後,流量和廣告收入減少了 20%。當谷歌地圖的首頁檔案大小從 100kb 減少到 70~80kb 時,流量在第一週漲了 10%,接下來的三週漲了 25%。

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

可以看出,效能優化商業上來說很重要。

但是,更重要的還是螢幕前我們的使用者,讓使用者在使用產品時有更快更舒適的瀏覽體驗,這算是一種前端工程師的自我修養。

所以今天就分享一下如何去優化我們的react專案,進而提升使用者體驗。

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>,告訴我們只能返回一個元素,所以我們通常會在最外層包裹一個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.js
const 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() 防止不必要的重新渲染

當一個元件的props或state變更,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.PureComponent
class 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 所做的。

廣州品牌設計公司https://www.houdianzi.com

使用 Chrome Performance 標籤分析元件

在開發模式下,你可以通過支援的瀏覽器視覺化地瞭解元件是如何 掛載、更新以及解除安裝的。例如:

在 Chrome 中進行如下操作:

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

最後,我們探索了一些可以優化 React 應用程式的一些提高效能的方法,不侷限於此。我們應該根據需要有針對性的優化應用程式,因為在某些簡單的場景中,過度的優化,可能會得不償失。