5.Optimizing Performance(性能優(yōu)化)

React版本:15.4.2
**翻譯:xiyoki **

在內部现拒,React使用幾種聰明的技術來最小化更新UI所需的昂貴的DOM操作的數(shù)量。對于許多應用程序祭芦,使用React將導致快速的用戶界面钦睡,而無需進行大量工作來專門優(yōu)化性能。然而帖努,有幾種方法可以加快你的React應用程序。

Use The Production Build(使用生產(chǎn)構建)

在你的React應用程序中粪般,如果你遇到了基準測試或性能方面的問題拼余,請確保你正使用縮小的生產(chǎn)版本進行測試:

  • 對于創(chuàng)建React應用程序,你需要運行npm run build,并按照說明進行操作亩歹。
  • 對于單文件構建匙监,我們提供production-ready min.js版本。
  • 對于Browserify小作,你用需要用NODE_ENV=production來運行它亭姥。
  • 對于Webpack,你需要在你的生產(chǎn)配置中顾稀,將其添加到插件中:
new webpack.DefinePlugin({
  'process.env': {
    NODE_ENV: JSON.stringify('production')
  }
}),
new webpack.optimize.UglifyJsPlugin()
  • 對于Rollup达罗,你需要在CommonJS插件之前使用replace插件。因此静秆,development-only 模塊不被導入粮揉。完整的設置示例see this gist
plugins: [
  require('rollup-plugin-replace')({
    'process.env.NODE_ENV': JSON.stringify('production')
  }),
  require('rollup-plugin-commonjs')(),
  // ...
]

當構建你的應用程序時抚笔,development build 包含的額外警告十分有用扶认,但它額外的bookkeeping會讓其自身變慢。

Profiling Components with Chrome Timeline(使用Chrome時間軸分析組件)

development模式下殊橙,你可以在支持的瀏覽器中使用性能工具來可視化組件的加載辐宾,更新和卸載狱从。例如:


在Chrome中執(zhí)行此操作:

  1. 通過在查詢字符串中加入?react_perf來加載你的應用程序(例如,http://localhost:3000/?react_perf)叠纹。
  2. 打開Chrome DevTools Timeline選項卡矫夯,然后按住Record
  3. 執(zhí)行你想配置的操作吊洼,記錄時間不要超過20秒训貌,否則Chrome可能會掛起。
  4. 停止記錄冒窍。
  5. React事件將在User Timing標簽下分組递沪。

請注意,數(shù)字是相對的综液,因此組件在生產(chǎn)中渲染得更快款慨。盡管如此,這應該可以幫助你了解到不相關的UI被錯誤更新谬莹,以及你的UI更新的深度和頻率檩奠。
目前,Chrome附帽,Edge和IE是唯一支持此功能的瀏覽器埠戳,但我們使用標準的User Timing API,因此我們希望更多的瀏覽器能支持此功能蕉扮。

Avoid Reconciliation

React構建并維護已渲染的UI的內部表示整胃。它包含從組件返回的React元素。此表示使React避免創(chuàng)建DOM節(jié)點和訪問現(xiàn)有的超出必要性的節(jié)點喳钟,因此它可能比Javascript對象上的操作更慢屁使。有時它被稱為‘虛擬DOM’,但是它在React Native上以相同的方式工作奔则。
當組件的props或state更改時蛮寂,React通過將新返回的元素與先前已渲染的元素進行比較,來決定是否需要實際的DOM更新易茬。當它們不相等時酬蹋,React將更新DOM。
在某些情況下疾呻,你的組件可以通過覆蓋在重新渲染過程開始前就觸發(fā)的生命周期函數(shù)shouldComponentUpdate來加快這一切除嘹。該函數(shù)的默認實現(xiàn)是返回true写半,讓React執(zhí)行更新:

shouldComponentUpdate(nextProps, nextState) {
  return true;
}

如果你知道在某些情況下你的組件不需要更新岸蜗,相反,你可以從shouldComponentUpdate返回false跳過整個渲染過程叠蝇,包括當前和之下組件上的render()調用璃岳。

shouldComponentUpdate In Action

這是一個組件的子樹年缎。對于每一個節(jié)點,SCU指示shouldComponentUpdate返回的內容铃慷,而vDOMEq指示渲染的React元素是否等效单芜。最后,圓圈的顏色表示組件是否必須reconciled 犁柜。


對于以C2為根的子樹洲鸠,由于shouldComponentUpdate返回false,React沒有嘗試渲染C2馋缅,因此甚至不必在C4和C5上調用shouldComponentUpdate扒腕。
對于C1和C3,shouldComponentUpdate返回true,所以React必須下到葉子檢查它們萤悴。對于C6瘾腰,shouldComponentUpdate返回true,并且因為渲染的元素不等價覆履,React不得不更新DOM。
最后一個有趣的例子是C8硝全。React不得不渲染這個組件,但是由于它返回的React元素等于先前已渲染的元素伟众,所以它不必更新DOM。
注意React只需要為C6做DOM更新赂鲤,這是不可避免的噪径。對于C8,它通過比較已渲染的React元素解脫(bailed out)了数初。對于C2的子樹和C7,由于我們已從shouldComponentUpdate上解脫(bailed out)车摄,它甚至沒有必要對元素進行比較,并且render也沒有被調用仑鸥。

Examples

props.colorstate.count變量的改變是你組件改變的唯一方式時吮播,你可以使用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 !== 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>
    );
  }
}

這段代碼,shouldComponentUpdate只是檢查props.colorstate.count是否有任何變化眼俊。如果這些值不更改,組件不更新环戈。如果你的組件更復雜,你可以使用一個類似的模式在propsstate的所有字段之間做一個淺比較院塞,以確定組件是否應該更新。
這個模式是常見的拦止,React提供了從React.PureComponent繼承而來的logic-just的用法。所以這段代碼以一個更簡單的方法來實現(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>
    );
  }
}

大多數(shù)時候艺玲,你可以使用React.PureComponent鞠抑,而不是自己寫shouldComponentUpdate饭聚。它只做一個淺的比較搁拙,因此如果props或state以一個淺比較會錯過的方式被改變,你就不能使用它酪碘。
這可能是更復雜的數(shù)據(jù)結構的問題盐茎。例如兴垦,假設你想要一個ListOfWords組件來渲染一個以逗號分隔的單詞列表字柠,有一個WordAdder父組件,讓你點擊按鈕添加一個單詞到單詞列表中钦幔。此代碼無法正常工作:

class ListOfWords extends React.PureComponent {
  render() {
    return <div>{this.props.words.join(',')}</div>;
  }
}

class WordAdder extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      words: ['marklar']
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    // This section is bad style and causes a bug
    const words = this.state.words;
    words.push('marklar');
    this.setState({words: words});
  }

  render() {
    return (
      <div>
        <button onClick={this.handleClick} />
        <ListOfWords words={this.state.words} />
      </div>
    );
  }
}

問題在于常柄,PureComponent將在this.props.words的新值和舊值之間做一個簡單的比較。由于該代碼在WordAdder組件的handleClick方法中改變words數(shù)組西潘,因此this.props.words的新值和舊值比較后依然相等。該ListOfWords因此將不會更新相种,即使它應該渲染新的單詞东抹。

The Power Of Not Mutating Data

避免此問題的最簡單的方法是避免使用正作為props或state使用的mutating values沃测。例如缭黔,上面的handleClick方法可以用concat重寫為:

handleClick() {
  this.setState(prevState => ({
    words: prevState.words.concat(['marklar'])
  }));
}

ES6支持數(shù)組的擴展語法,可以使這更容易别渔。如果你正在使用Create React App惧互,此語法默認可用哎媚。

handleClick() {
  this.setState(prevState => ({
    words: [...prevState.words, 'marklar'],
  }));
};

你也可以重寫代碼喊儡,以類似的方式mutates對象,以避免mutation艾猜。例如,我們有一個名為colormap的對象淤毛,我們要寫一個將colormap.right改變成'blue'的函數(shù)。我們可以寫:

function updateColorMap(colormap) {
  colormap.right = 'blue';
}

要寫這個低淡,而不改變原始對象瞬项,我們可以使用Object.assign方法:

function updateColorMap(colormap) {
  return Object.assign({}, colormap, {right: 'blue'});
}

updateColorMap現(xiàn)在返回一個新對象,而不是改變舊的對象囱淋。Object.assign是ES6新增的,需要polyfill胁孙。
有一個添加 object spread properties的Javascript 提議,使得更新對象更容易涮较,也不會有mutation:

function updateColorMap(colormap) {
  return {...colormap, right: 'blue'};
}

如果你使用Create React App冈止,默認情況下,Object.assign和object spread syntax都有效熙暴。

Using Immutable Data Structures(使用不可變數(shù)據(jù)結構)

Immutable.js 是另一個解決這個問題的方法慌盯。它提供了不可變掂器,持久的集合,通過結構共享工作:

  • 不可變:一旦創(chuàng)建国瓮,集合不能在另一個時間點更改。
  • 持久性:新集合可以從先前的集合和諸如set的mutation中創(chuàng)建而來禁漓。新集合創(chuàng)建后,原始集合仍然有效播歼。
  • 結構共享:使用與原始集合相同的結構創(chuàng)建新集合掰读,從而將復制減少到最低程度以提高性能。

不變性使跟蹤變化更便宜磷支。變化將始終導致一個新對象,因此我們只需要檢查對對象的引用是否已更改雾狈。例如,在這個常規(guī)的Javascript代碼中:

const x = { foo: "bar" };
const y = x;
y.foo = "baz";
x === y; // true

雖然y被編輯辩蛋,但由于它和x都是對同一個對象的引用移盆,這個比較返回true。你可以用immutable.js編寫類似的代碼:

const SomeRecord = Immutable.Record({ foo: null });
const x = new SomeRecord({ foo: 'bar'  });
const y = x.set('foo', 'baz');
x === y; // false

在這種情況下咒循,由于在變異時返回一個新的引用x,我們可以安全地假設x已經(jīng)改變。
另外兩個可以幫助使用不可變數(shù)據(jù)的庫是 seamless-immutableimmutability-helper.
不可變的數(shù)據(jù)結構為你提供了一種便宜的方式來跟蹤對象的更改颖医,這是我們實現(xiàn)shouldComponentUpdate所需要的裆蒸。這可以為你提供一個不錯的性能提升。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市贮缕,隨后出現(xiàn)的幾起案子俺榆,更是在濱河造成了極大的恐慌,老刑警劉巖肋演,帶你破解...
    沈念sama閱讀 222,865評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爹殊,死亡現(xiàn)場離奇詭異,居然都是意外死亡梗夸,警方通過查閱死者的電腦和手機号醉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铅碍,“玉大人线椰,你說我怎么就攤上這事『┯洌” “怎么了?”我有些...
    開封第一講書人閱讀 169,631評論 0 364
  • 文/不壞的土叔 我叫張陵径密,是天一觀的道長躺孝。 經(jīng)常有香客問我,道長植袍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,199評論 1 300
  • 正文 為了忘掉前任锉试,我火速辦了婚禮,結果婚禮上呆盖,老公的妹妹穿的比我還像新娘。我一直安慰自己宙项,他們只是感情好株扛,可當我...
    茶點故事閱讀 69,196評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著洞就,像睡著了一般。 火紅的嫁衣襯著肌膚如雪油昂。 梳的紋絲不亂的頭發(fā)上倾贰,一...
    開封第一講書人閱讀 52,793評論 1 314
  • 那天,我揣著相機與錄音匆浙,去河邊找鬼。 笑死挑庶,一個胖子當著我的面吹牛饰恕,可吹牛的內容都是我干的。 我是一名探鬼主播埋嵌,決...
    沈念sama閱讀 41,221評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼范舀!你這毒婦竟也來了了罪?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 40,174評論 0 277
  • 序言:老撾萬榮一對情侶失蹤泊藕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后玫锋,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,699評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡谦炬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,770評論 3 343
  • 正文 我和宋清朗相戀三年节沦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甫贯。...
    茶點故事閱讀 40,918評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖赖条,靈堂內的尸體忽然破棺而出常熙,到底是詐尸還是另有隱情碱茁,我是刑警寧澤,帶...
    沈念sama閱讀 36,573評論 5 351
  • 正文 年R本政府宣布纽竣,位于F島的核電站,受9級特大地震影響聋袋,放射性物質發(fā)生泄漏穴吹。R本人自食惡果不足惜幽勒,卻給世界環(huán)境...
    茶點故事閱讀 42,255評論 3 336
  • 文/蒙蒙 一啥容、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧咪惠,春花似錦淋淀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽徽缚。三九已至,卻和暖如春凿试,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背那婉。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評論 1 274
  • 我被黑心中介騙來泰國打工详炬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留盐类,地道東北人呛谜。 一個月前我還...
    沈念sama閱讀 49,364評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像猫妙,于是被迫代替她去往敵國和親聚凹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,926評論 2 361

推薦閱讀更多精彩內容

  • 深入JSX date:20170412筆記原文其實JSX是React.createElement(componen...
    gaoer1938閱讀 8,076評論 2 35
  • 自己最近的項目是基于react的彼哼,于是讀了一遍react的文檔湘今,做了一些記錄(除了REFERENCE部分還沒開始讀...
    潘逸飛閱讀 3,404評論 1 10
  • It's a common pattern in React to wrap a component in an ...
    jplyue閱讀 3,277評論 0 2
  • 以下內容是我在學習和研究React時象浑,對React的特性蔫饰、重點和注意事項的提取愉豺、精練和總結,可以做為React特性...
    科研者閱讀 8,244評論 2 21
  • 每天晚上都會和小盆友一起讀書杖剪,有時是我讀給他,有時是他讀給我盛嘿。 每次我們都會把認為最好的句子畫下來...
    半調兒貓閱讀 122評論 0 0