21 項優(yōu)化 React App 性能的技術(shù)

原文:21 Performance Optimization Techniques for React Apps
作者:Nishant
譯者:博軒

介紹

React 內(nèi)部,React 會使用幾項巧妙的小技術(shù)冗酿,來優(yōu)化計算更新 UI 時拂檩,所需要的最少的更新 DOM 的操作梯码。在大多數(shù)情況下为狸,即使你沒有針對性能進行專項優(yōu)化舆驶,React 依然很快乱陡,但是仍有一些方法可以加速 React 應用程序虽缕。本文將介紹一些可用于改進 React 代碼的有效技巧。

1.使用不可變數(shù)據(jù)結(jié)構(gòu)

數(shù)據(jù)不變性不是一種架構(gòu)或者設(shè)計模式女淑,它是一種編程思想瞭郑。它會強制您考慮如何構(gòu)建應用程序的數(shù)據(jù)流。在我看來鸭你,數(shù)據(jù)不變性是一種符合嚴格單項數(shù)據(jù)流的實踐屈张。

數(shù)據(jù)不變性,這一來自函數(shù)式編程的概念袱巨,可應用于前端應用程序的設(shè)計阁谆。它會帶來很多好處,例如:

  • 零副作用
  • 不可變的數(shù)據(jù)對象更易于創(chuàng)建瓣窄,測試,和使用纳鼎;
  • 利于解耦俺夕;
  • 更加利于追蹤變化裳凸;

React 環(huán)境中,我們使用 Component 的概念來維護組件內(nèi)部的狀態(tài)劝贸,對狀態(tài)的更改可以導致組建的重新渲染姨谷。

React 構(gòu)建并在內(nèi)部維護呈現(xiàn)的UI(Virtual DOM)。當組件的 props 或者 state 發(fā)生改變時映九,React 會將新返回的元素與先前呈現(xiàn)的元素進行比較梦湘。當兩者不相等時,React 將更新 DOM件甥。因此捌议,在改變狀態(tài)時,我們必須要小心引有。

讓我們考慮一個用戶列表組件:

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});
}

這里的關(guān)注點是瓣颅,我們正在將新的用戶添加到變量 users ,這里它對應的引用是 this.state.users譬正。

專業(yè)提示 : 應該將 React 的狀態(tài)視為不可變宫补。我們不應該直接修改 this.state,因為 setState() 之后的調(diào)用可能會覆蓋你之前的修改曾我。

那么粉怕,如果我們直接修改 state 會產(chǎn)生什么問題呢?比方說抒巢,我們添加 shouldComponentUpdate 贫贝,并對比 nextStatethis.state 來確保只有當數(shù)據(jù)改變時,才會重新渲染組件虐秦。

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

即使用戶的數(shù)組發(fā)生了改變平酿,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 的改變幸逆,我們可以考慮一下幾種處理不可變的方法:

  • 對于數(shù)組:使用 [].concat 或es6的 [ ...params]
  • 對象:使用 Object.assign({}, ...) 或 es6的 {...params}

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

但是还绘,最好使用一個提供不可變數(shù)據(jù)結(jié)構(gòu)的優(yōu)化庫。以下是您可以使用的一些庫:

  • Immutability Helper:這是一個很好的庫栖袋,他可以在不改變源的情況下拍顷,提供修改后的數(shù)據(jù)。
  • Immutable.js :這是我最喜歡的庫塘幅,因為它提供了許多持久不可變的數(shù)據(jù)昔案,包括:List尿贫,StackMap踏揣,OrderedMap庆亡,SetOrderedSetRecord捞稿。
  • Seamless-immutable:一個用于提供不可變 JavaScript 數(shù)據(jù)結(jié)構(gòu)的庫又谋,他與普通的數(shù)組和對象向后兼容。
  • React-copy-write:一個不可變的React狀態(tài)管理庫娱局,帶有一個簡單的可變API彰亥,memoized選擇器和結(jié)構(gòu)共享。

專業(yè)提示: React setState 方法是異步的铃辖。這意味著剩愧,setState() 方法會創(chuàng)建一個帶轉(zhuǎn)換的 state, 而不是立即修改 this.state娇斩。如果在調(diào)用setState() 方法之后去訪問 this.state 仁卷,則可能會返回現(xiàn)有值。為防止這種情況犬第,請setState 在調(diào)用完成后使用回調(diào)函數(shù)運行代碼锦积。

其他資源:

2.函數(shù)/無狀態(tài)組件和 React.PureComponent

React 中歉嗓,函數(shù)組件和 PureComponent 提供了兩種不同級別的組件優(yōu)化方案丰介。

函數(shù)組件防止了構(gòu)造類實例,
同時函數(shù)組件可以減少整體包的大小鉴分,因為它比類組件的的體積更小哮幢。

另一方面,為了優(yōu)化UI更新志珍,我們可以考慮將函數(shù)組件轉(zhuǎn)換為 PureComponent 類組件(或使用自定義 shouldComponentUpdate 方法的類)橙垢。但是,如果組件不使用狀態(tài)和其他生命周期方法伦糯,為了達到更快的的更新柜某,首次渲染相比函數(shù)組件會更加復雜一些。

譯注:函數(shù)組件也可以做純組件的優(yōu)化:React.memo(...) 是 React v16.6 中引入的新功能敛纲。 它與 React.PureComponent 類似喂击,它有助于控制 函數(shù)組件 的重新渲染。 React.memo(...) 對應的是函數(shù)組件淤翔,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 對狀態(tài)變化進行淺層比較(shallow comparison)琳要。這意味著它在比較時,會比較原始數(shù)據(jù)類型的值秤茅,并比較對象的引用。因此童叠,我們必須確保在使用 React.PureComponent 時符合兩個標準:

  • 組件 State / Props 是一個不可變對象;
  • State / Props 不應該有多級嵌套對象框喳。

專業(yè)提示: 所有使用 React.PureComponent 的子組件,也應該是純組件或函數(shù)組件厦坛。

3.生成多個塊文件

Multiple Chunk Files

您的應用程序始終以一些組件開始五垮。您開始添加新功能和依賴項,最終您會得到一個巨大的生產(chǎn)文件杜秸。

您可以考慮通過利用 CommonsChunkPlugin for webpack 將供應商或第三方庫代碼與應用程序代碼分開放仗,生成兩個單獨的文件。你最終會得到 vendor.bundle.jsapp.bundle.js 撬碟。通過拆分文件诞挨,您的瀏覽器會緩存較少的資源,同時并行下載資源以減少等待的加載時間呢蛤。

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

4.在 Webpack 中使用 Production 標識生產(chǎn)環(huán)境

如果您使用 webpack 4 作為應用程序的模塊捆綁程序,則可以考慮將 mode 選項設(shè)置為 production 其障。這基本上告訴 webpack 使用內(nèi)置優(yōu)化:

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

或者银室,您可以將其作為 CLI 參數(shù)傳遞:

webpack --mode=production

這樣做會限制對庫的優(yōu)化,例如縮小或刪除開發(fā)代碼励翼。它不會公開源代碼蜈敢,文件路徑等等

5.依賴優(yōu)化

在考慮優(yōu)化程序包大小的時候汽抚,檢查您的依賴項中實際有多少代碼被使用了抓狭,會很有價值。例如殊橙,如果您使用 Moment.js會包含本地化文件的多語言支持辐宾。如果您不需要支持多種語言,那么您可以考慮使用 moment-locales-webpack-plugin 來刪除不需要的語言環(huán)境膨蛮。

另一個例子是使用 lodash 叠纹。假設(shè)你使用了 100 多種方法的 20 種,那么你最終打包時其他額外的方法都是不需要的敞葛。因此誉察,可以使用 lodash-webpack-plugin 來刪除未使用的函數(shù)。

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

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

React.fragments 允許您在不添加額外節(jié)點的情況下對子列表進行分組惹谐。

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.避免在渲染函數(shù)中使用內(nèi)聯(lián)函數(shù)定義

由于在 JavaScript 中函數(shù)就是對象({} !== {})驼卖,因此當 React 進行差異檢查時,內(nèi)聯(lián)函數(shù)將始終使 prop diff 失敗鸿秆。此外酌畜,如果在JSX屬性中使用箭頭函數(shù),它將在每次渲染時創(chuàng)建新的函數(shù)實例卿叽。這可能會為垃圾收集器帶來很多工作桥胞。

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}/>
           }) 
        )
    }
}

您可以定義箭頭函數(shù),而不是為 props 定義內(nèi)聯(lián)函數(shù)考婴。

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 中事件的防抖和節(jié)流

事件觸發(fā)率代表事件處理程序在給定時間內(nèi)調(diào)用的次數(shù)贩虾。

通常,與滾動和鼠標懸停相比沥阱,鼠標點擊具有較低的事件觸發(fā)率缎罢。較高的事件觸發(fā)率有時會使應用程序崩潰,但可以對其進行控制考杉。

我們來討論一些技巧策精。

首先,明確事件處理會帶來一些昂貴的操作崇棠。例如蛮寂,執(zhí)行UI更新,處理大量數(shù)據(jù)或執(zhí)行計算昂貴任務(wù)的XHR請求或DOM操作易茬。在這些情況下酬蹋,防抖和節(jié)流技術(shù)可以成為救世主,而不會對事件監(jiān)聽器進行任何更改抽莱。

節(jié)流

簡而言之范抓,節(jié)流意味著延遲功能執(zhí)行。因此食铐,不是立即執(zhí)行事件處理程序/函數(shù)匕垫,而是在觸發(fā)事件時添加幾毫秒的延遲。例如虐呻,這可以在實現(xiàn)無限滾動時使用象泵。您可以延遲 XHR 調(diào)用,而不是在用戶滾動時獲取下一個結(jié)果集斟叼。

另一個很好的例子是基于 Ajax 的即時搜索偶惠。您可能不希望每次按鍵時,都會請求服務(wù)器獲取新的數(shù)據(jù)朗涩,因此最好節(jié)流直到輸入字段處于休眠狀態(tài)幾毫秒之后忽孽,再請求數(shù)據(jù)。

節(jié)流可以通過多種方式實現(xiàn)。您可以限制觸發(fā)的事件的次數(shù)或延遲正在執(zhí)行的事件來限制程序執(zhí)行一些昂貴的操作兄一。

防抖

與節(jié)流不同厘线,防抖是一種防止事件觸發(fā)器過于頻繁觸發(fā)的技術(shù)。如果您正在使用 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,可以使用簡單版的防抖函數(shù)骂束。

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

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

在渲染列表時费薄,您經(jīng)常會看到索引被用作鍵。

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

但是使用 index
作為 key栖雾, 被用在React虛擬DOM元素的時候,會使你的應用可能出現(xiàn)錯誤的數(shù)據(jù) 伟众。當您從列表中添加或刪除元素時析藕,如果該 key 與以前相同,則 React虛擬DOM元素表示相同的組件凳厢。

始終建議使用唯一屬性作為 key账胧,或者如果您的數(shù)據(jù)沒有任何唯一屬性,那么您可以考慮使用shortid module 生成唯一 key 的屬性先紫。

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

但是治泥,如果數(shù)據(jù)具有唯一屬性(例如ID),則最好使用該屬性遮精。

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

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

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

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

我們經(jīng)常需要將帶有 props 的初始數(shù)據(jù)傳遞給 React組件 來設(shè)置初始狀態(tài)值准脂。

考慮一下這段代碼:

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 變化會發(fā)生什么狸膏?它會映射到 state 中嘛? 如果在沒有刷新組件的情況下添怔,props 中的值被修改了湾戳,props 中的值,將永遠不會分配給 state 中的 applyCoupon广料。這是因為構(gòu)造函數(shù)僅在EditPanel 組件首次創(chuàng)建時被調(diào)用砾脑。

引用React文檔

避免將 props 的值復制給 state渴析!這是一個常見的錯誤:

constructor(props) {
 super(props);
 // 不要這樣做
 this.state = { color: props.color };
}

如此做毫無必要(你可以直接使用 this.props.color)奸鬓,同時還產(chǎn)生了 bug(更新 prop 中的 color 時,并不會影響 state)旷祸。**只有在你刻意忽略 props 更新的情況下使用。此時汹族,應將 props 重命名為 initialColordefaultColor萧求。必要時,你可以修改它的 key顶瞒,以強制“重置”其內(nèi)部 state夸政。請參閱博文:你可能不需要派生 state

解決方法:

  1. 直接使用 props 中的屬性
class EditPanelComponent extends Component {

    render(){
        return <div>{this.props.applyCoupon && 
         <>Enter Coupon:<Input/></>}</div>
    }
}
  1. props 改變時,您可以使用 componentDidUpdate 函數(shù)來更新 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>
    );
};

您可以設(shè)置特定屬性,而不是直接傳遞 Props:

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

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

ReselectRedux 的一個簡單的選擇器庫仿便,可用于構(gòu)建記憶選擇器。您可以將選擇器定義為函數(shù)攒巍,為 React 組件檢索 Redux 狀態(tài)片段嗽仪。

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);

在這段代碼中,每次評論數(shù)據(jù)在 state 中 變化時柒莉,CommentsContainerShareContainer 將會重新渲染闻坚。即使 addStaticPath 不進行任何數(shù)據(jù)更改也會發(fā)生這種情況,因為 socialDetailsaddStaticPath 函數(shù)返回的的數(shù)據(jù)每次都是一個新的對象 (請記住{} != {})【ばⅲ現(xiàn)在窿凤,如果我們用 Reselect 重寫 addStaticPath ,問題就會消失跨蟹,因為Reselect 將返回最后一個函數(shù)結(jié)果卷玉,直到它傳遞新的輸入。

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

14. 記憶化的 React 組件

Memoization是一種用于優(yōu)化程序速度的技術(shù)喷市,主要通過存儲復雜函數(shù)的計算結(jié)果相种,當再次出現(xiàn)相同的輸入,直接從緩存中返回結(jié)果品姓。memoized 函數(shù)通常更快寝并,因為如果使用與前一個函數(shù)相同的值調(diào)用函數(shù),則不會執(zhí)行函數(shù)邏輯腹备,而是從緩存中獲取結(jié)果衬潦。

讓我們考慮下面簡單的無狀態(tài)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 發(fā)生變化弦牡,這個無狀態(tài)組件就會重新渲染。如果 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 動畫

動畫可以提供更加流暢優(yōu)秀的用戶體驗。實現(xiàn)動畫的方式有很多走越,一般來說椭豫,有三種方式可以創(chuàng)建動畫:

  • CSS transitions
  • CSS animations
  • JavaScript

我們選擇哪一個取決于我們想要添加的動畫類型。

何時使用基于CSS的動畫:

  • 添加 “一次性” 的轉(zhuǎn)換效果旨指,比如切換 UI 元素的狀態(tài)赏酥。
  • 較小的自身包含狀態(tài)的 UI 元素。例如谆构,顯示氣泡提示裸扶,或者為菜單項增加懸停效果。

何時使用基于JavaScript的動畫:

  • 當你想擁有高級效果時搬素,例如彈跳呵晨,停止,暫停蔗蹋,倒帶,減速或反轉(zhuǎn);
  • 當你需要對動畫進行重度控制時;
  • 當你需要切換動畫時囱淋,如鼠標懸停猪杭,點擊等;
  • 當使用 requestAnimationFrame 來變化視覺時。

例如妥衣,假設(shè)您希望 div 在鼠標懸停時分為 4 個狀態(tài)設(shè)置動畫皂吮。div 在旋轉(zhuǎn) 90 度的過程中,四個階段將背景顏色從紅色變?yōu)樗{色税手,藍色變?yōu)榫G色蜂筹,綠色變?yōu)辄S色。在這種情況下芦倒,您需要結(jié)合使用JavaScript動畫CSS轉(zhuǎn)換來更好地控制操作和狀態(tài)更改艺挪。

16.使用CDN

CDN是一種將網(wǎng)站或移動應用程序中的靜態(tài)內(nèi)容更快速有效地傳遞給用戶的絕佳方式。

CDN取決于用戶的地理位置兵扬。最靠近用戶的CDN服務(wù)器稱為“邊緣服務(wù)器”麻裳。當用戶從您的網(wǎng)站請求通過CDN提供的內(nèi)容時,他們會連接到邊緣服務(wù)器并確保最佳的在線體驗器钟。

有一些很棒的CDN提供商津坑。例如,CloudFront傲霸,CloudFlare疆瑰,Akamai眉反,MaxCDN,Google Cloud CDN等穆役。

您也可以選擇Netlify或Surge.sh在CDN上托管您的靜態(tài)內(nèi)容寸五。Surge是一款免費的CLI工具,可將您的靜態(tài)項目部署到生產(chǎn)質(zhì)量的CDN孵睬。

17.在CPU擴展任務(wù)中使用 Web Workers

Web Workers 可以在Web應用程序的后臺線程中運行腳本操作播歼,與主執(zhí)行線程分開。通過在單獨的線程中執(zhí)行費力的處理掰读,主線程(通常是UI)能夠在不被阻塞或減速的情況下運行秘狞。

在相同的執(zhí)行上下文中,由于JavaScript是單線程的蹈集,我們需要并行計算烁试。這可以通過兩種方式實現(xiàn)。第一個選項是使用偽并行拢肆,它基于 setTimeout 函數(shù)實現(xiàn)的减响。第二種選擇是使用 Web Workers

Web Workers 在執(zhí)行計算擴展操作時效果最佳郭怪,因為它在后臺的單獨線程中獨立于其他腳本執(zhí)行代碼支示。這意味著它不會影響頁面的性能。

我們可以利用React中的Web Workers來執(zhí)行計算昂貴的任務(wù)鄙才。

這是不使用 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 個帖子時會發(fā)生什么颂鸿?由于 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 執(zhí)行圖像處理稳懒,排序闲擦,過濾和其他消耗高昂 CPU 性能的任務(wù)。

參考: 使用Web Workers

18.虛擬化長列表

虛擬化列表或窗口化是一種在呈現(xiàn)長數(shù)據(jù)列表時提高性能的技術(shù)场梆。此技術(shù)在任何時間內(nèi)只展現(xiàn)列表的一部分佛致,并且可以顯著減少重新渲染組件所花費的時間,以及創(chuàng)建 DOM 節(jié)點的總數(shù)辙谜。

有一些流行的 React 庫俺榆,如react-windowreact-virtualized,它提供了幾個可重用的組件來展示列表装哆,網(wǎng)格和表格數(shù)據(jù)罐脊。

19.分析和優(yōu)化您的 Webpack 打包

在生產(chǎn)部署之前定嗓,您應該檢查并分析應用程序包以刪除不需要的插件或模塊。

您可以考慮使用Webpack Bundle Analyzer萍桌,它允許您使用可視化的樹狀結(jié)構(gòu)來查看 webpack 輸出文件的大小宵溅。

該模塊將幫助您:

  • 了解你的打包內(nèi)容
  • 找出最大尺寸的模塊
  • 找到哪些模塊有錯誤
  • 優(yōu)化它!

最好的優(yōu)點是什么上炎?它支持壓縮模塊恃逻!他在解析他們以獲得模塊的真實大小,同時展示壓縮大信菏寇损!下面是使用 webpack-bundle-analyzer 的示例:

[圖片上傳失敗...(image-73b04-1563263103103)]

20.考慮服務(wù)端渲染

服務(wù)端渲染的好處之一是為用戶提供更好的體驗,相比客戶端渲染裳食,用戶會更快接受到可查看的內(nèi)容矛市。

近年來,像沃爾瑪和Airbnb會使用 React 服務(wù)端渲染來為用戶提供更好的用戶體驗诲祸。然而浊吏,在服務(wù)器上呈現(xiàn)擁有大數(shù)據(jù),密集型應用程序很快就會成為性能瓶頸救氯。

服務(wù)器端渲染提供了性能優(yōu)勢和一致的SEO表現(xiàn)≌姨铮現(xiàn)在,如果您在沒有服務(wù)器端渲染的情況下檢查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包含應用程序代碼的包墩衙,并在一兩秒后呈現(xiàn)整個頁面。

[圖片上傳失敗...(image-1da43c-1563263103103)]

我們可以看到客戶端渲染享扔,在到達服務(wù)器之前有兩次往返底桂,用戶可以看到內(nèi)容≈才郏現(xiàn)在惧眠,如果應用程序包含API驅(qū)動的數(shù)據(jù)呈現(xiàn),那么流程中會有一個暫停于个。

讓我們考慮用服務(wù)器端渲染來處理的同一個應用程序:

[圖片上傳失敗...(image-6a1144-1563263103102)]

我們看到在用戶獲取內(nèi)容之前氛魁,只有一次訪問服務(wù)器。那么服務(wù)器究竟發(fā)生了什么厅篓?當瀏覽器請求頁面時秀存,服務(wù)器會在內(nèi)存中加載React并獲取呈現(xiàn)應用程序所需的數(shù)據(jù)。之后羽氮,服務(wù)器將生成的HTML發(fā)送到瀏覽器或链,立即向用戶顯示內(nèi)容。

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

  • Next.js
  • Gatsby

21.在Web服務(wù)器上啟用Gzip壓縮

Gzip 壓縮允許 Web 服務(wù)器提供更小的文件大小档押,這意味著您的網(wǎng)站加載速度更快澳盐。gzip 運行良好的原因是因為JavaScript祈纯,CSSHTML文件使用了大量具有大量空白的重復文本。由于gzip壓縮常見字符串叼耙,因此可以將頁面和樣式表的大小減少多達70%腕窥,從而縮短網(wǎng)站的首次渲染時間。

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

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

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

結(jié)論

有許多方法可以優(yōu)化React應用程序,例如延遲加載組件爽撒,使用 ServiceWorkers 緩存應用程序狀態(tài)入蛆,考慮SSR,避免不必要的渲染等等匆浙。也就是說安寺,在考慮優(yōu)化之前,值得了解React組件如何工作首尼,理解 diff 算法挑庶,以及在Reactrender 的工作原理。這些都是優(yōu)化應用程序時需要考慮的重要概念软能。

我認為沒有測量的優(yōu)化幾乎都是為時過早的迎捺,這就是為什么我建議首先對性能進行基準測試和測量。您可以考慮使用 Chrome 時間線分析和可視化組件查排。這使您可以查看卸載凳枝,裝載,更新哪些組件以及它們相對于彼此的時間跋核。它將幫助您開始性能優(yōu)化之旅岖瑰。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市砂代,隨后出現(xiàn)的幾起案子蹋订,更是在濱河造成了極大的恐慌,老刑警劉巖刻伊,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件露戒,死亡現(xiàn)場離奇詭異,居然都是意外死亡捶箱,警方通過查閱死者的電腦和手機智什,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丁屎,“玉大人荠锭,你說我怎么就攤上這事〕看ǎ” “怎么了证九?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵键思,是天一觀的道長。 經(jīng)常有香客問我甫贯,道長吼鳞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任叫搁,我火速辦了婚禮赔桌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘渴逻。我一直安慰自己疾党,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布惨奕。 她就那樣靜靜地躺著雪位,像睡著了一般。 火紅的嫁衣襯著肌膚如雪梨撞。 梳的紋絲不亂的頭發(fā)上雹洗,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音卧波,去河邊找鬼时肿。 笑死,一個胖子當著我的面吹牛港粱,可吹牛的內(nèi)容都是我干的螃成。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼查坪,長吁一口氣:“原來是場噩夢啊……” “哼寸宏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起偿曙,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤氮凝,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后遥昧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體覆醇,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡朵纷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年炭臭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袍辞。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡鞋仍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出搅吁,到底是詐尸還是另有隱情威创,我是刑警寧澤落午,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站肚豺,受9級特大地震影響溃斋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吸申,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一梗劫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧截碴,春花似錦梳侨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哲虾,卻和暖如春丙躏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背束凑。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工彼哼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人湘今。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓敢朱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親摩瞎。 傳聞我的和親對象是個殘疾皇子拴签,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 作為一個合格的開發(fā)者,不要只滿足于編寫了可以運行的代碼旗们。而要了解代碼背后的工作原理蚓哩;不要只滿足于自己的程序...
    六個周閱讀 8,422評論 1 33
  • 3. JSX JSX是對JavaScript語言的一個擴展語法, 用于生產(chǎn)React“元素”上渴,建議在描述UI的時候...
    pixels閱讀 2,806評論 0 24
  • 深入JSX date:20170412筆記原文其實JSX是React.createElement(componen...
    gaoer1938閱讀 8,048評論 2 35
  • 原文鏈接:http://www.reibang.com/p/582346a54a3d 1. 前言 在 React...
    小豆soybean閱讀 588評論 0 0
  • 40岸梨、React 什么是React?React 是一個用于構(gòu)建用戶界面的框架(采用的是MVC模式):集中處理VIE...
    萌妹撒閱讀 1,005評論 0 1