React進(jìn)階筆記5(性能優(yōu)化)

性能優(yōu)化

在更新UI時(shí),React在內(nèi)部使用幾種巧妙的技術(shù),以此來最小化DOM的操作量,對(duì)于許多應(yīng)用來說,React不需要做太多的優(yōu)化工作就可以快速創(chuàng)建用戶界面。

使用生產(chǎn)版本

在React中檢測(cè)性能時(shí),需要使用壓縮過的生產(chǎn)版本。

在默認(rèn)情況下React中包含很多開發(fā)過程中需要的警告模塊,然而這會(huì)導(dǎo)致React更大更慢,因此在部署發(fā)布的時(shí)候,確認(rèn)使用的是生產(chǎn)版本,不是開發(fā)版本。

如果還不確定構(gòu)建的過程是否正確,可以安裝React開發(fā)者工具(chrome)
(類似vue的開發(fā)者工具),當(dāng)你訪問一個(gè)生產(chǎn)模式的React頁(yè)面的時(shí)候,工具圖標(biāo)會(huì)有一個(gè)藍(lán)黑色的背景。
[圖片上傳失敗...(image-aefc3d-1534304297449)]
當(dāng)訪問一個(gè)開發(fā)模式的頁(yè)面的時(shí)候,會(huì)有一個(gè)紅色的背景。[圖片上傳失敗...(image-d7a398-1534304297450)]

建議在開發(fā)的時(shí)候使用開發(fā)模式,部署發(fā)布的時(shí)候使用生產(chǎn)模式。
以下是構(gòu)建生產(chǎn)應(yīng)用的流程。

Create React App 流程

如果你的項(xiàng)目是以Create React App創(chuàng)建的,運(yùn)行如下代碼:

cnpm run build //項(xiàng)目構(gòu)建

這將會(huì)在項(xiàng)目的 build/文件夾下創(chuàng)建一個(gè)生產(chǎn)版本的應(yīng)用。
注意只有發(fā)布正常版本的時(shí)候才需要這樣做,
平時(shí)正常開發(fā)只需要 cnpm start運(yùn)行就可以了。

單文件生產(chǎn)版本

使用已經(jīng)提供的React和ReactDOM文件即可

<script src="https://unpkg.com/react@15/dist/react.min.js"></script>
<script src="https://unpkg.com/react-dom@15/dist/react-dom.min.js"></script>

注意只有結(jié)尾為.min.js的React文件才是適合生產(chǎn)使用的。

Brunch

為了創(chuàng)建最高效的Brunch生產(chǎn)版本,需要安裝uglify-js-brunch 插件:

# If you use npm
npm install --save-dev uglify-js-brunch

# If you use Yarn
yarn add --dev uglify-js-brunch

接下來構(gòu)建生產(chǎn)版本,在build命令后添加-p參數(shù):

brunch build -p

注意只有生產(chǎn)版本需要這樣操作。不要在開發(fā)環(huán)境中安裝這個(gè)插件或者使用-p參數(shù),因?yàn)樗鼤?huì)隱藏掉有用的React警告并使構(gòu)建過程更慢。

為了構(gòu)建生產(chǎn)版本,務(wù)必添加這些設(shè)置指令 (參數(shù)很重要):

  • envify該插件確保正確的編譯環(huán)境,全局安裝(-g)。
  • uglifyify該插件移除了開發(fā)接口。全局安裝(-g)。
  • bundle-collapser該插件用數(shù)字替代了長(zhǎng)長(zhǎng)的模塊ID。
  • 最后,以上結(jié)果都被輸添加至uglify-js來得到整合。(了解原因).
Webpack

注意:

如果你正在使用Create React App方式,參考上述文檔。
本節(jié)只適用于直接配置Webpack的情況。

為了創(chuàng)建最高效的 webpack 生產(chǎn)版本,需要在生產(chǎn)版本的配置中添加這些插件。

new webpack.DefinePlugin({
  'process.env': {
    NODE_ENV: JSON.stringify('production')
  }
}),
new webpack.optimize.UglifyJsPlugin()

了解更多參見Webpack文檔.
注意只有在生產(chǎn)環(huán)境中需要這樣做, 不需要再開發(fā)環(huán)境中安裝UglifyJsPluginDefinePlugin,因?yàn)樗麄儠?huì)忽略掉有用的Reacr警告,并且使得構(gòu)建的過程變得更慢。

使用 Chrome Performance 歸檔組件

在開發(fā)模式中,使用支持的瀏覽器內(nèi)的性能工具可以更加直觀的了解,組件何時(shí)掛載,更新和卸載。
[圖片上傳失敗...(image-4830a9-1534304297450)]

chrome瀏覽器內(nèi):
①在項(xiàng)目地址欄內(nèi)添加查詢字符串 ?react_perf(例如, http://localhost:3000/?react_perf)。
②打開Chrome開發(fā)工具Performance 標(biāo)簽頁(yè)點(diǎn)擊Record.
③執(zhí)行你想要分析的動(dòng)作。不要記錄超過20s,不然Chrome可能會(huì)掛起。
④停止記錄
⑤React事件將會(huì)被歸類在 User Timing標(biāo)簽下。
更多的詳細(xì)操作,請(qǐng)參考 BenSchwarz 的這篇文章。

注意由于這些數(shù)字是相對(duì)的,因此組件在生產(chǎn)版本中會(huì)運(yùn)行更快。然而,這也能夠幫助你了解何時(shí)會(huì)有無關(guān)的組件被錯(cuò)誤的更新,以及你的組件更新的深度和頻率。

目前瀏覽器中僅有Chrome,Edge和IE支持此特性,但是我們使用此標(biāo)準(zhǔn)用戶Timing API,因此我們期待更多的瀏覽器對(duì)其添加支持。

避免重復(fù)渲染

React在渲染出的UI內(nèi)部 建立和維護(hù)一個(gè)內(nèi)層的實(shí)現(xiàn)方式,他包括從組件返回的React元素。
這種實(shí)現(xiàn)方式使得React避免了一些不必要的創(chuàng)建和關(guān)聯(lián)dom的節(jié)點(diǎn)。因?yàn)檫@樣做可能比直接操作javascript對(duì)象會(huì)更慢一些。有時(shí)候被稱為“虛擬DOM”,但是他其實(shí)和 React Native的工作方式是一樣的。

當(dāng)一個(gè)組件的props或者state改變時(shí),React通過比較新返回的元素和之前渲染的元素,來決定是否有必要更新實(shí)際的DOM。當(dāng)他們不相等時(shí),會(huì)更新dom。

有一些情況下,你的組件可以通過重寫這個(gè)生命周期函數(shù)shouldComponentUpdate來提升速度,他在重新渲染過程開始之前觸發(fā)。這個(gè)函數(shù)默認(rèn)返回true,可以使用React執(zhí)行更新。

shouldComponentUpdate(nextProps,nextState){
    return true;
}

如果你想要你的組件在某些情況下不需要更新,可以控制在這個(gè)生命周期函數(shù)中返回false來跳過整個(gè)渲染的過程,這個(gè)過程包括了之后對(duì)這個(gè)組件調(diào)用render()是指令。

shouldComponentUpdate()的應(yīng)用

這是一個(gè)組件子樹,對(duì)其中每個(gè)組件來說,scu 表明了每個(gè)組件 shouldComponentUpdate() 的返回內(nèi)容,vDOMEq表明了待渲染的React元素與原始元素是否相等,最后圓圈的顏色表示這個(gè)組件是否需重新要渲染。

[圖片上傳失敗...(image-7cbbeb-1534304297450)]

從圖中可知:

由于以C2為根的子樹shouldComponentUpdate返回了false,React不會(huì)渲染C2,甚至不會(huì)嘗試渲染C4和C5.

對(duì)于C1和C3來說,shouldComponentUpdate返回true,因此React會(huì)深入到分支中并檢查他們C6,C7,C8,其中C6的shouldComponentUpdate返回了true,由于待渲染的元素與原始元素不相等,React會(huì)更新這個(gè)DOM節(jié)點(diǎn)。

最后一個(gè)有趣的情況是C8,React需要渲染這個(gè)組件,但是由于組件元素的返回值與原元素相等,因此并沒有更新這個(gè)dom節(jié)點(diǎn)。

注意其中React只需要更新C6,因?yàn)樗遣豢杀苊獾?,?duì)于C8而言,他通過比較待渲染的元素和原始元素來避免了渲染。對(duì)于C2的子樹和C7,他們甚至都沒有執(zhí)行比較,因?yàn)槲覀冊(cè)O(shè)置了shouldComponentUpdate返回false,render并沒有調(diào)用。

案例:

如果只想讓組件在props.color或者state.count的值變化時(shí)重新渲染,你們可以像下面這樣設(shè)定shouldComponentUpdate

class CounterButton extends React.Component{
    constructor(props){
        super(props);
        this.state={count:1};
    }

    shouldComponentUpdate(nextProps,nextState){
        if(this.props.color!==nextProps.color){
            return true;
        }
        if(this.state.count!==next.count){
            return true;
        }
        return false;
    }
    render(){
        return(
            <button color={this.props.color} 
            onClick={()=>{this.setState(state=>({count:state.count+1}))}}
            >
            Count:{this.state.count}
            </button>
        )
    }
}

在以上代碼中,shouldComponentUpdate只檢查props.colorstate.count的變化,如果這些值都沒有變化,這些值就不會(huì)更新。

class CounterButton extends React.Component{
    constructor(props){
        super(props);
        this.state={count:1};
    }

    shouldComponentUpdate(nextProps,nextState){
        if(this.props.color!==nextProps.color){
            return true;
        }
        if(this.state.count!==nextState.count){
            return true;
        }
        return false;
    }
    render(){
        return(
            <button color={this.props.color} 
            onClick={()=>{this.setState(state=>({count:state.count+1}))}}
            >
            Count:{this.state.count}
            </button>
        )
    }
}

ReactDOM.render(
    <CounterButton/>,document.getElementById('root')
)

當(dāng)你的組件變得更加復(fù)雜時(shí),你可以用類似的模式來作一個(gè)“淺比較”,用來比較屬性和值來判定是否需要組件更新,這種模式十分常見,因此React提供一個(gè)輔助的對(duì)象來實(shí)現(xiàn)這個(gè)邏輯。繼承自來自`React.PureComponent,一下的代碼簡(jiǎn)單的實(shí)現(xiàn)相同的操作。

class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

大部分情況下,你可以使用React.PureComponent而不必寫你自己的shouldComponentUpdate,它只做一個(gè)淺比較。但是由于淺比較會(huì)忽略屬性或狀態(tài)突變的情況,此時(shí)你不能使用它。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 3. JSX JSX是對(duì)JavaScript語(yǔ)言的一個(gè)擴(kuò)展語(yǔ)法, 用于生產(chǎn)React“元素”,建議在描述UI的時(shí)候...
    pixels閱讀 2,982評(píng)論 0 24
  • 深入JSX date:20170412筆記原文其實(shí)JSX是React.createElement(componen...
    gaoer1938閱讀 8,190評(píng)論 2 35
  • 我看完了《無畏的探險(xiǎn)者》的最后一章,合上書,眼前出現(xiàn)了一幅"江海奔騰圖"。在波濤兇涌的大海上,航海家哥倫布帶著...
    68b687085527閱讀 561評(píng)論 0 0
  • 鬧哄哄的酒吧,男神頭頂海草閃亮登場(chǎng)擺渡眾人,要有多喜感就有多喜感。酒吧里多的是紙醉金迷和膚白貌美大長(zhǎng)腿的妹子...
    一瀟而過閱讀 922評(píng)論 0 1
  • 三桂一路走一路想著任老栓給自己說的話,只要拿過來一件冥器,那就算真正的出師。三桂當(dāng)時(shí)本有心說自己膽小不敢去,但一想...
    金何閱讀 2,276評(píng)論 0 2

友情鏈接更多精彩內(nèi)容