【譯】21 項優化 React App 效能的技術

joking_zhang發表於2019-07-06
原文:21 Performance Optimization Techniques for React Apps
作者:Nishant
譯者:博軒

圖片描述

介紹

React 內部,React 會使用幾項巧妙的小技術,來優化計算更新 UI 時,所需要的最少的更新 DOM 的操作。在大多數情況下,即使你沒有針對效能進行專項優化,React 依然很快,但是仍有一些方法可以加速 React 應用程式。本文將介紹一些可用於改進 React 程式碼的有效技巧。

1.使用不可變資料結構

資料不變性不是一種架構或者設計模式,它是一種程式設計思想。它會強制您考慮如何構建應用程式的資料流。在我看來,資料不變性是一種符合嚴格單項資料流的實踐。

資料不變性,這一來自函數語言程式設計的概念,可應用於前端應用程式的設計。它會帶來很多好處,例如:

  • 零副作用
  • 不可變的資料物件更易於建立,測試,和使用;
  • 利於解耦;
  • 更加利於追蹤變化;

React 環境中,我們使用 Component 的概念來維護元件內部的狀態,對狀態的更改可以導致組建的重新渲染。

React 構建並在內部維護呈現的UI(Virtual DOM)。當元件的 props 或者 state 發生改變時,React 會將新返回的元素與先前呈現的元素進行比較。當兩者不相等時,React 將更新 DOM。因此,在改變狀態時,我們必須要小心。

讓我們考慮一個使用者列表元件:

state = {
   users: []
}

addNewUser = () =>{
   /**
    *  OfCourse not correct way to insert
    *  new user in user list
    */
   const users = this.state.users;
   users.push({
       userName: "robin",
       email: "email@email.com"
   });
   this.setState({users: users});
}

這裡的關注點是,我們正在將新的使用者新增到變數 users ,這裡它對應的引用是 this.state.users

專業提示 : 應該將 React 的狀態視為不可變。我們不應該直接修改 this.state,因為 setState() 之後的呼叫可能會覆蓋你之前的修改。

那麼,如果我們直接修改 state 會產生什麼問題呢?比方說,我們新增 shouldComponentUpdate ,並對比 nextStatethis.state 來確保只有當資料改變時,才會重新渲染元件。

shouldComponentUpdate(nextProps, nextState) {
    if (this.state.users !== nextState.users) {
        return true;
    }
    return false;
}

即使使用者的陣列發生了改變,React 也不會重新渲染UI了,因為他們的引用是相同的。

避免此類問題最簡單的方法,就是避免直接修改 propsstate。所以,我們可以使用 concat 來重寫 addNewUser 方法:

addNewUser = () => {
   this.setState(state => ({
     users: state.users.concat({
       timeStamp: new Date(),
       userName: "robin",
       email: "email@email.com"
     })
   }));
};

為了處理 React 元件中 props 或者 state 的改變,我們可以考慮一下幾種處理不可變的方法:

  • 對於陣列:使用 [].concat 或es6的 [ ...params]
  • 物件:使用 Object.assign({}, ...) 或 es6的 {...params}

在向程式碼庫引入不變性時,這兩種方法有很長的路要走。

但是,最好使用一個提供不可變資料結構的優化庫。以下是您可以使用的一些庫:

  • Immutability Helper:這是一個很好的庫,他可以在不改變源的情況下,提供修改後的資料。
  • Immutable.js :這是我最喜歡的庫,因為它提供了許多持久不可變的資料,包括:ListStackMapOrderedMapSetOrderedSetRecord
  • Seamless-immutable:一個用於提供不可變 JavaScript 資料結構的庫,他與普通的陣列和物件向後相容。
  • React-copy-write:一個不可變的React狀態管理庫,帶有一個簡單的可變API,memoized選擇器和結構共享。

專業提示: React setState 方法是非同步的。這意味著,setState() 方法會建立一個帶轉換的 state, 而不是立即修改 this.state。如果在呼叫setState() 方法之後去訪問 this.state ,則可能會返回現有值。為防止這種情況,請setState 在呼叫完成後使用回撥函式執行程式碼。

其他資源:

2.函式/無狀態元件和 React.PureComponent

React 中,函式元件和 PureComponent 提供了兩種不同級別的元件優化方案。

函式元件防止了構造類例項,
同時函式元件可以減少整體包的大小,因為它比類元件的的體積更小。

另一方面,為了優化UI更新,我們可以考慮將函式元件轉換為 PureComponent 類元件(或使用自定義 shouldComponentUpdate 方法的類)。但是,如果元件不使用狀態和其他生命週期方法,為了達到更快的的更新,首次渲染相比函式元件會更加複雜一些。

譯註:函式元件也可以做純元件的優化:React.memo(...) 是 React v16.6 中引入的新功能。 它與 React.PureComponent 類似,它有助於控制 函式元件 的重新渲染。 React.memo(...) 對應的是函式元件,React.PureComponent 對應的是類元件。React Hooks 也提供了許多處理這種情況的方法:useCallback, useMemo。推薦兩個延伸閱讀:
A Closer Look at React Memoize Hooks: useRef, useCallback, and useMemoReact Hooks API ⎯ 不只是 useState 或 useEffect

我們應該何時使用 React.PureComponent

React.PureComponent 對狀態變化進行淺層比較(shallow comparison)。這意味著它在比較時,會比較原始資料型別的值,並比較物件的引用。因此,我們必須確保在使用 React.PureComponent 時符合兩個標準:

  • 元件 State / Props 是一個不可變物件;
  • State / Props 不應該有多級巢狀物件。

專業提示: 所有使用 React.PureComponent 的子元件,也應該是純元件或函式元件。

3.生成多個塊檔案

Multiple Chunk Files

您的應用程式始終以一些元件開始。您開始新增新功能和依賴項,最終您會得到一個巨大的生產檔案。

您可以考慮通過利用 CommonsChunkPlugin for webpack 將供應商或第三方庫程式碼與應用程式程式碼分開,生成兩個單獨的檔案。你最終會得到 vendor.bundle.jsapp.bundle.js 。通過拆分檔案,您的瀏覽器會快取較少的資源,同時並行下載資源以減少等待的載入時間。

注意: 如果您使用的是最新版本的webpack,還可以考慮使用 SplitChunksPlugin

4.在 Webpack 中使用 Production 標識生產環境

如果您使用 webpack 4 作為應用程式的模組捆綁程式,則可以考慮將 mode 選項設定為 production 。這基本上告訴 webpack 使用內建優化:

module.exports = {
    mode: 'production'
};

或者,您可以將其作為 CLI 引數傳遞:

webpack --mode=production

這樣做會限制對庫的優化,例如縮小或刪除開發程式碼。它不會公開原始碼,檔案路徑等等

5.依賴優化

在考慮優化程式包大小的時候,檢查您的依賴項中實際有多少程式碼被使用了,會很有價值。例如,如果您使用 Moment.js 會包含本地化檔案的多語言支援。如果您不需要支援多種語言,那麼您可以考慮使用 moment-locales-webpack-plugin 來刪除不需要的語言環境。

另一個例子是使用 lodash 。假設你使用了 100 多種方法的 20 種,那麼你最終打包時其他額外的方法都是不需要的。因此,可以使用 lodash-webpack-plugin 來刪除未使用的函式。

以下是一些使用 Webpack 打包時可選的依賴項優化列表

6. React.Fragments 用於避免額外的 HTML 元素包裹

React.fragments 允許您在不新增額外節點的情況下對子列表進行分組。

class Comments extends React.PureComponent{
    render() {
        return (
            <React.Fragment>
                <h1>Comment Title</h1>
                <p>comments</p>
                <p>comment time</p>
            </React.Fragment>
        );
    } 
}

等等!我們還可以使用更加簡潔的語法代替 React.fragments

class Comments extends React.PureComponent{
    render() {
        return (
            <>
                <h1>Comment Title</h1>
                <p>comments</p>
                <p>comment time</p>
            </>
        );
    } 
}

7.避免在渲染函式中使用行內函數定義

由於在 JavaScript 中函式就是物件({} !== {}),因此當 React 進行差異檢查時,行內函數將始終使 prop diff 失敗。此外,如果在JSX屬性中使用箭頭函式,它將在每次渲染時建立新的函式例項。這可能會為垃圾收集器帶來很多工作。

default class CommentList extends React.Component {
    state = {
        comments: [],
        selectedCommentId: null
    }

    render(){
        const { comments } = this.state;
        return (
           comments.map((comment)=>{
               return <Comment onClick={(e)=>{
                    this.setState({selectedCommentId:comment.commentId})
               }} comment={comment} key={comment.id}/>
           }) 
        )
    }
}

您可以定義箭頭函式,而不是為 props 定義行內函數。

default class CommentList extends React.Component {
    state = {
        comments: [],
        selectedCommentId: null
    }

    onCommentClick = (commentId)=>{
        this.setState({selectedCommentId:commentId})
    }

    render(){
        const { comments } = this.state;
        return (
           comments.map((comment)=>{
               return <Comment onClick={this.onCommentClick} 
                comment={comment} key={comment.id}/>
           }) 
        )
    }
}

8. JavaScript 中事件的防抖和節流

事件觸發率代表事件處理程式在給定時間內呼叫的次數。

通常,與滾動和滑鼠懸停相比,滑鼠點選具有較低的事件觸發率。較高的事件觸發率有時會使應用程式崩潰,但可以對其進行控制。

我們來討論一些技巧。

首先,明確事件處理會帶來一些昂貴的操作。例如,執行UI更新,處理大量資料或執行計算昂貴任務的XHR請求或DOM操作。在這些情況下,防抖和節流技術可以成為救世主,而不會對事件監聽器進行任何更改。

節流

簡而言之,節流意味著延遲功能執行。因此,不是立即執行事件處理程式/函式,而是在觸發事件時新增幾毫秒的延遲。例如,這可以在實現無限滾動時使用。您可以延遲 XHR 呼叫,而不是在使用者滾動時獲取下一個結果集。

另一個很好的例子是基於 Ajax 的即時搜尋。您可能不希望每次按鍵時,都會請求伺服器獲取新的資料,因此最好節流直到輸入欄位處於休眠狀態幾毫秒之後,再請求資料。

節流可以通過多種方式實現。您可以限制觸發的事件的次數或延遲正在執行的事件來限制程式執行一些昂貴的操作。

防抖

與節流不同,防抖是一種防止事件觸發器過於頻繁觸發的技術。如果您正在使用 lodash ,則可以使用 lodash’s debounce function 來包裝你的方法。

這是一個搜尋評論的演示程式碼:

import debouce from 'lodash.debounce';

class SearchComments extends React.Component {
 constructor(props) {
   super(props);
   this.state = { searchQuery: “” };
 }

 setSearchQuery = debounce(e => {
   this.setState({ searchQuery: e.target.value });

   // Fire API call or Comments manipulation on client end side
 }, 1000);

 render() {
   return (
     <div>
       <h1>Search Comments</h1>
       <input type="text" onChange={this.setSearchQuery} />
     </div>
   );
 }
}

如果您不使用 lodash,可以使用簡單版的防抖函式。

function debounce(a,b,c){var d,e;return function(){function h(){d=null,c||(e=a.apply(f,g))}var f=this,g=arguments;return clearTimeout(d),d=setTimeout(h,b),c&&!d&&(e=a.apply(f,g)),e}}

9.避免在 map 方法中使用 Index 作為元件的 Key

在渲染列表時,您經常會看到索引被用作鍵。

{
    comments.map((comment, index) => {
        <Comment 
            {..comment}
            key={index} />
    })
}

但是使用 index
作為 key, 被用在React虛擬DOM元素的時候,會使你的應用可能出現錯誤的資料 。當您從列表中新增或刪除元素時,如果該 key 與以前相同,則 React虛擬DOM元素表示相同的元件。

始終建議使用唯一屬性作為 key,或者如果您的資料沒有任何唯一屬性,那麼您可以考慮使用shortid module 生成唯一 key 的屬性。

import shortid from  "shortid";
{
   comments.map((comment, index) => {
       <Comment 
           {..comment}
           key={shortid.generate()} />
   })
}

但是,如果資料具有唯一屬性(例如ID),則最好使用該屬性。

{
   comments.map((comment, index) => {
       <Comment 
           {..comment}
           key={comment.id} />
   })
}

在某些情況下,將 index 用作 key 是完全可以的,但僅限於以下條件成立時:

  • 列表和子元素是靜態的
  • 列表中的子元素沒有ID,列表永遠不會被重新排序或過濾
  • 列表是不可變的

10.避免使用 props 來初始化 state (直接賦值)

我們經常需要將帶有 props 的初始資料傳遞給 React元件 來設定初始狀態值。

考慮一下這段程式碼:

class EditPanelComponent extends Component {
    
    constructor(props){
        super(props);

        this.state ={
            isEditMode: false,
            applyCoupon: props.applyCoupon
        }
    }

    render(){
        return <div>
                    {this.state.applyCoupon && 
                    <>Enter Coupon: <Input/></>}
               </div>
    }
}

片段中的一切看起來都不錯,對吧?

但是 props.applyCoupon 變化會發生什麼?它會對映到 state 中嘛? 如果在沒有重新整理元件的情況下,props 中的值被修改了,props 中的值,將永遠不會分配給 state 中的 applyCoupon。這是因為建構函式僅在EditPanel 元件首次建立時被呼叫。

引用React文件

避免將 props 的值複製給 state!這是一個常見的錯誤:
constructor(props) {
 super(props);
 // 不要這樣做
 this.state = { color: props.color };
}
如此做毫無必要(你可以直接使用 this.props.color),同時還產生了 bug(更新 prop 中的 color 時,並不會影響 state)。**只有在你刻意忽略 props 更新的情況下使用。此時,應將 props 重新命名為 initialColordefaultColor。必要時,你可以修改它的 key,以強制“重置”其內部 state。請參閱博文:你可能不需要派生 state

解決方法:

  1. 直接使用 props 中的屬性
class EditPanelComponent extends Component {
    
    render(){
        return <div>{this.props.applyCoupon && 
         <>Enter Coupon:<Input/></>}</div>
    }
}
  1. props 改變時,您可以使用 componentDidUpdate 函式來更新 state
class EditPanelComponent extends Component {
    
    constructor(props){
        super(props);

        this.state ={
            applyCoupon: props.applyCoupon
        }
    }

    // reset state if the seeded prop is updated
    componentDidUpdate(prevProps){
        if (prevProps.applyCoupon !== this.props.applyCoupon) {
            this.setState({ applyCoupon: this.props.applyCoupon })
            // or do something
        }
    }

    render(){
        return <div>{this.state.applyCoupon && 
          <>Enter Coupon: <Input/></>}</div>
    }
}

11.在 DOM 元素上傳遞 Props

您應該避免將屬性傳播到 DOM 元素中,因為它會新增未知的 HTML 屬性,這是不必要的,也是一種不好的做法。

const CommentsText = props => {
    return (
      <div {...props}>
        {props.text}
      </div>
    );
};

您可以設定特定屬性,而不是直接傳遞 Props:

const CommentsText = props => {
    return (
      <div specificAttr={props.specificAttr}>
        {props.text}
      </div>
    );
};

12.在使用 Redux Connect 時,同時使用 Reselect 來避免元件的頻繁重新渲染

ReselectRedux 的一個簡單的選擇器庫,可用於構建記憶選擇器。您可以將選擇器定義為函式,為 React 元件檢索 Redux 狀態片段。

const App = ({ comments, socialDetails }) => (
    <div>
      <CommentsContainer data={comments} />
      <ShareContainer socialDetails={socialDetails} />
    </div>
);
  
const addStaticPath = social => ({
    iconPath: `../../image/${social.iconPath}`
});
  
App = connect(state => {
    return {
        comments: state.comments,
        socialDetails: addStaticPath(state.social)
    };
})(App);

在這段程式碼中,每次評論資料在 state 中 變化時,CommentsContainerShareContainer 將會重新渲染。即使 addStaticPath 不進行任何資料更改也會發生這種情況,因為 socialDetailsaddStaticPath 函式返回的的資料每次都是一個新的物件 (請記住{} != {})。現在,如果我們用 Reselect 重寫 addStaticPath ,問題就會消失,因為 Reselect 將返回最後一個函式結果,直到它傳遞新的輸入。

import { createSelector } from "reselect";
const socialPathSelector = state => state.social;
const addStaticPath = createSelector(
  socialPathSelector,
  social => ({
    iconPath: `../../image/${social.iconPath}`
  })
);

14. 記憶化的 React 元件

Memoization是一種用於優化程式速度的技術,主要通過儲存複雜函式的計算結果,當再次出現相同的輸入,直接從快取中返回結果。memoized 函式通常更快,因為如果使用與前一個函式相同的值呼叫函式,則不會執行函式邏輯,而是從快取中獲取結果。

讓我們考慮下面簡單的無狀態UserDetails元件。

const UserDetails = ({user, onEdit}) => {
    const {title, full_name, profile_img} = user;

    return (
        <div className="user-detail-wrapper">
            <img src={profile_img} />
            <h4>{full_name}</h4>
            <p>{title}</p>
        </div>
    )
}

在這裡,所有的孩子 UserDetails 都基於 props。只要 props 發生變化,這個無狀態元件就會重新渲染。如果 UserDetails 元件屬性不太可能改變,那麼它是使用元件的 memoize 版本的一個很好的選擇:

const UserDetails = ({user, onEdit}) =>{
    const {title, full_name, profile_img} = user;

    return (
        <div className="user-detail-wrapper">
            <img src={profile_img} />
            <h4>{full_name}</h4>
            <p>{title}</p>
        </div>
    )
}

export default React.memo(UserDetails)

此方法將基於嚴格相等對元件的 props 和上下文進行淺層比較。

15.使用 CSS 動畫代替 JS 動畫

動畫可以提供更加流暢優秀的使用者體驗。實現動畫的方式有很多,一般來說,有三種方式可以建立動畫:

  • CSS transitions
  • CSS animations
  • JavaScript

我們選擇哪一個取決於我們想要新增的動畫型別。

何時使用基於CSS的動畫:

  • 新增 “一次性” 的轉換效果,比如切換 UI 元素的狀態。
  • 較小的自身包含狀態的 UI 元素。例如,顯示氣泡提示,或者為選單項增加懸停效果。

何時使用基於JavaScript的動畫:

  • 當你想擁有高階效果時,例如彈跳,停止,暫停,倒帶,減速或反轉;
  • 當你需要對動畫進行重度控制時;
  • 當你需要切換動畫時,如滑鼠懸停,點選等;
  • 當使用 requestAnimationFrame 來變化視覺時。

例如,假設您希望 div 在滑鼠懸停時分為 4 個狀態設定動畫。div 在旋轉 90 度的過程中,四個階段將背景顏色從紅色變為藍色,藍色變為綠色,綠色變為黃色。在這種情況下,您需要結合使用JavaScript動畫CSS轉換來更好地控制操作和狀態更改。

16.使用CDN

CDN是一種將網站或移動應用程式中的靜態內容更快速有效地傳遞給使用者的絕佳方式。

CDN取決於使用者的地理位置。最靠近使用者的CDN伺服器稱為“邊緣伺服器”。當使用者從您的網站請求通過CDN提供的內容時,他們會連線到邊緣伺服器並確保最佳的線上體驗。

有一些很棒的CDN提供商。例如,CloudFront,CloudFlare,Akamai,MaxCDN,Google Cloud CDN等。

您也可以選擇Netlify或Surge.sh在CDN上託管您的靜態內容。Surge是一款免費的CLI工具,可將您的靜態專案部署到生產質量的CDN。

17.在CPU擴充套件任務中使用 Web Workers

Web Workers 可以在Web應用程式的後臺執行緒中執行指令碼操作,與主執行執行緒分開。通過在單獨的執行緒中執行費力的處理,主執行緒(通常是UI)能夠在不被阻塞或減速的情況下執行。

在相同的執行上下文中,由於JavaScript是單執行緒的,我們需要平行計算。這可以通過兩種方式實現。第一個選項是使用偽並行,它基於 setTimeout 函式實現的。第二種選擇是使用 Web Workers

Web Workers 在執行計算擴充套件操作時效果最佳,因為它在後臺的單獨執行緒中獨立於其他指令碼執行程式碼。這意味著它不會影響頁面的效能。

我們可以利用React中的Web Workers來執行計算昂貴的任務。

這是不使用 Web Workers 的程式碼:

// Sort Service for sort post by the number of comments 
function sort(posts) {
    for (let index = 0, len = posts.length - 1; index < len; index++) {
        for (let count = index+1; count < posts.length; count++) {
            if (posts[index].commentCount > posts[count].commentCount) {
                const temp = posts[index];
                posts[index] = users[count];
                posts[count] = temp;
            }
        }
    }
    return posts;
}

export default Posts extends React.Component{

    constructor(props){
        super(posts);
    }

    state = {
        posts: this.props.posts
    }

    doSortingByComment = () => {
        if(this.state.posts && this.state.posts.length){
            const sortedPosts = sort(this.state.posts);
            this.setState({
                posts: sortedPosts
            });
        }
    }

    render(){
        const posts = this.state.posts;
        return (
            <React.Fragment>
                <Button onClick={this.doSortingByComment}>
                    Sort By Comments
                </Button>
                <PostList posts={posts}></PostList>
            </React.Fragment>
        )
    }
}

當我們有 20,000 個帖子時會發生什麼?由於 sort 方法時間複雜度 O(n^2) ,它將減慢渲染速度,因為它們在同一個執行緒中執行。

下面是修改後的程式碼,它使用 Web Workers 來處理排序:

// sort.worker.js

// In-Place Sort function for sort post by number of comments
export default  function sort() {
    
    self.addEventListener('message', e =>{
        if (!e) return;
        let posts = e.data;
        
        for (let index = 0, len = posts.length - 1; index < len; index++) {
            for (let count = index+1; count < posts.length; count++) {
                if (posts[index].commentCount > posts[count].commentCount) {
                    const temp = posts[index];
                    posts[index] = users[count];
                    posts[count] = temp;
                }
            }
        }
        postMessage(posts);
    });
}

export default Posts extends React.Component{

    constructor(props){
        super(posts);
    }
    state = {
        posts: this.props.posts
    }
    componentDidMount() {
        this.worker = new Worker('sort.worker.js');
        
        this.worker.addEventListener('message', event => {
            const sortedPosts = event.data;
            this.setState({
                posts: sortedPosts
            })
        });
    }

    doSortingByComment = () => {
        if(this.state.posts && this.state.posts.length){
            this.worker.postMessage(this.state.posts);
        }
    }

    render(){
        const posts = this.state.posts;
        return (
            <React.Fragment>
                <Button onClick={this.doSortingByComment}>
                    Sort By Comments
                </Button>
                <PostList posts={posts}></PostList>
            </React.Fragment>
        )
    }
}

在這段程式碼中,我們sort在單獨的執行緒中執行該方法,這將確保我們不會阻塞主執行緒。

您可以考慮使用 Web Workers 執行影象處理,排序,過濾和其他消耗高昂 CPU 效能的任務。

參考: 使用Web Workers

18.虛擬化長列表

虛擬化列表或視窗化是一種在呈現長資料列表時提高效能的技術。此技術在任何時間內只展現列表的一部分,並且可以顯著減少重新渲染元件所花費的時間,以及建立 DOM 節點的總數。

有一些流行的 React 庫,如react-windowreact-virtualized,它提供了幾個可重用的元件來展示列表,網格和表格資料。

19.分析和優化您的 Webpack 打包

在生產部署之前,您應該檢查並分析應用程式包以刪除不需要的外掛或模組。

您可以考慮使用Webpack Bundle Analyzer,它允許您使用視覺化的樹狀結構來檢視 webpack 輸出檔案的大小。

該模組將幫助您:

  • 瞭解你的打包內容
  • 找出最大尺寸的模組
  • 找到哪些模組有錯誤
  • 優化它!

最好的優點是什麼?它支援壓縮模組!他在解析他們以獲得模組的真實大小,同時展示壓縮大小!下面是使用 webpack-bundle-analyzer 的示例:

123

20.考慮服務端渲染

服務端渲染的好處之一是為使用者提供更好的體驗,相比客戶端渲染,使用者會更快接受到可檢視的內容。

近年來,像沃爾瑪和Airbnb會使用 React 服務端渲染來為使用者提供更好的使用者體驗。然而,在伺服器上呈現擁有大資料,密集型應用程式很快就會成為效能瓶頸。

伺服器端渲染提供了效能優勢和一致的SEO表現。現在,如果您在沒有伺服器端渲染的情況下檢查React應用程式頁面源,它將如下所示:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <link rel="shortcut icon" href="/favicon.ico">
    <title>React App</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="/app.js"></script>
  </body>
</html>

瀏覽器還將獲取app.js包含應用程式程式碼的包,並在一兩秒後呈現整個頁面。

123

我們可以看到客戶端渲染,在到達伺服器之前有兩次往返,使用者可以看到內容。現在,如果應用程式包含API驅動的資料呈現,那麼流程中會有一個暫停。

讓我們考慮用伺服器端渲染來處理的同一個應用程式:

123

我們看到在使用者獲取內容之前,只有一次訪問伺服器。那麼伺服器究竟發生了什麼?當瀏覽器請求頁面時,伺服器會在記憶體中載入React並獲取呈現應用程式所需的資料。之後,伺服器將生成的HTML傳送到瀏覽器,立即向使用者顯示內容。

以下是一些為React應用程式提供SSR的流行解決方案:

  • Next.js
  • Gatsby

21.在Web伺服器上啟用Gzip壓縮

Gzip 壓縮允許 Web 伺服器提供更小的檔案大小,這意味著您的網站載入速度更快。gzip 執行良好的原因是因為JavaScriptCSSHTML檔案使用了大量具有大量空白的重複文字。由於gzip壓縮常見字串,因此可以將頁面和樣式表的大小減少多達70%,從而縮短網站的首次渲染時間。

如果您使用的是 Node / Express 後端,則可以使用 Gzipping 來解決這個問題。

const express = require('express');
const compression = require('compression');
const app = express();

// Pass `compression` as a middleware!
app.use(compression());

結論

有許多方法可以優化React應用程式,例如延遲載入元件,使用 ServiceWorkers 快取應用程式狀態,考慮SSR,避免不必要的渲染等等。也就是說,在考慮優化之前,值得了解React元件如何工作,理解 diff 演算法,以及在Reactrender 的工作原理。這些都是優化應用程式時需要考慮的重要概念。

我認為沒有測量的優化幾乎都是為時過早的,這就是為什麼我建議首先對效能進行基準測試和測量。您可以考慮使用 Chrome 時間線分析和視覺化元件。這使您可以檢視解除安裝,裝載,更新哪些元件以及它們相對於彼此的時間。它將幫助您開始效能優化之旅。

如果您有任何其他基於 React 的應用程式優化建議,歡迎評論區討論鴨。

本文已經聯絡原文作者,並授權翻譯,轉載請保留原文連結

相關文章