react性能優(yōu)化

React性能優(yōu)化

hepeguo 2016年08月12日發(fā)布

當(dāng)大家考慮在項目中使用 React 的時候旺隙,第一個問題往往是他們的應(yīng)用的速度和響應(yīng)是否能和非 React 版一樣,每當(dāng)狀態(tài)改變的時候就重新渲染組件的整個子樹昭躺,讓大家懷疑這會不會對性能造成負面影響则果。React 用了一些黑科技來減少 UI 更新需要的花費較大的 DOM 操作幔翰。

使用 production 版本

如果你在你的 React app 中進行性能測試或在尋找性能問題,一定要確定你在使用 minified production build西壮。開發(fā)者版本包括額外的警告信息遗增,這對你在開發(fā)你的 app 的時候很有用,但是因為要進行額外的處理款青,所以它也會比較慢做修。

避免更新 DOM

React 使用虛擬 DOM,它是在瀏覽器中的 DOM 子樹的渲染描述抡草,這個平行的描述讓 React 避免創(chuàng)建和操作 DOM 節(jié)點饰及,這些遠比操作一個 JavaScript 對象慢。當(dāng)一個組件的 props 或 state 改變渠牲,React 會構(gòu)造一個新的虛擬 DOM 和舊的進行對比來決定真實 DOM 更新的必要性旋炒,只有在它們不相等的時候,React 才會使用盡量少的改動更新 DOM签杈。

在此之上瘫镇,React 提供了生命周期函數(shù) shouldComponentUpdate,在重新渲染機制回路(虛擬 DOM 對比和 DOM 更新)之前會被觸發(fā)答姥,賦予開發(fā)者跳過這個過程的能力铣除。這個函數(shù)默認(rèn)返回 true,讓 React 執(zhí)行更新鹦付。

shouldComponentUpdate: function(nextProps, nextState) {
  return true;
}

一定要記住尚粘,React 會非常頻繁的調(diào)用這個函數(shù),所以要確保它的執(zhí)行速度夠快敲长。

假如你有個帶有多個對話的消息應(yīng)用郎嫁,如果只有一個對話發(fā)生改變秉继,如果我們在 ChatThread 組件執(zhí)行 shouldComponentUpdate,React 可以跳過其他對話的重新渲染步驟泽铛。

shouldComponentUpdate: function(nextProps, nextState) {
  // TODO: return whether or not current chat thread is
  // different to former one.
}

因此尚辑,總的說,React 通過讓用戶使用 shouldComponentUpdate 減短重新渲染回路盔腔,避免進行昂貴的更新 DOM 子樹的操作杠茬,而且這些必要的更新,需要對比虛擬 DOM弛随。

shouldComponentUpdate 實戰(zhàn)

這里有個組件的子樹瓢喉,每一個都指明了 shouldComponentUpdate 返回值和虛擬 DOM 是否相等,最后舀透,圓圈的顏色表示組件是否需要更新栓票。

478926453-57adb1fb25515_articlex.png

在上面的示例中,因為 C2 的 shouldComponentUpdate 返回 false盐杂,React 就不需要生成新的虛擬 DOM逗载,也就不需要更新 DOM,注意 React 甚至不需要調(diào)用 C4 和 C5 的 shouldComponentUpdate链烈。

C1 和 C3 的 shouldComponentUpdate 返回 true厉斟,所以 React 需要向下到葉子節(jié)點檢查它們,C6 返回 true强衡,因為虛擬 DOM 不相等擦秽,需要更新 DOM。最后感興趣的是 C8漩勤,對于這個節(jié)點感挥,React 需要計算虛擬 DOM,但是因為它和舊的相等越败,所以不需要更新 DOM触幼。

注意 React 只需要對 C6 進行 DOM 轉(zhuǎn)換,這是必須的究飞。對于 C8置谦,通過虛擬 DOM 的對比確定它是不需要的,C2 的子樹和 C7亿傅,它們甚至不需要計算虛擬 DOM媒峡,因為 shouldComponentUpdate

那么葵擎,我們怎么實現(xiàn) shouldComponentUpdate 呢谅阿?比如說你有一個組件僅僅渲染一個字符串:

React.createClass({
  propTypes: {
    value: React.PropTypes.string.isRequired
  },

  render: function() {
    return <div>{this.props.value}</div>;
  }
});

我們可以簡單的實現(xiàn) shouldComponentUpdate 如下:

shouldComponentUpdate: function(nextProps, nextState) {
  return this.props.value !== nextProps.value;
}

非常好!處理這樣簡單結(jié)構(gòu)的 props/state 很簡單,我門甚至可以歸納出一個基于淺對比的實現(xiàn)签餐,然后把它 Mixin 到組件中寓涨。實際上 React 已經(jīng)提供了這樣的實現(xiàn): PureRenderMixin

但是如果你的組件的 props 或者 state 是可變的數(shù)據(jù)結(jié)構(gòu)呢?比如說氯檐,組件接收的 prop 不是一個像 'bar' 這樣的字符串缅茉,而是一個包涵字符串的 JavaScript 對象,比如 { foo: 'bar' }:

React.createClass({
  propTypes: {
    value: React.PropTypes.object.isRequired
  },

  render: function() {
    return <div>{this.props.value.foo}</div>;
  }
});

前面的 shouldComponentUpdate 實現(xiàn)就不會一直和我們期望的一樣工作:

// assume this.props.value is { foo: 'bar' }
// assume nextProps.value is { foo: 'bar' },
// but this reference is different to this.props.value
this.props.value !== nextProps.value; // true

這個問題是當(dāng) prop 沒有改變的時候 shouldComponentUpdate 也會返回 true男摧。為了解決這個問題,我們有了這個替代實現(xiàn):

shouldComponentUpdate: function(nextProps, nextState) {
  return this.props.value.foo !== nextProps.value.foo;
}

基本上译打,我們結(jié)束了使用深度對比來確保改變的正確跟蹤耗拓,這個方法在性能上的花費是很大的,因為我們需要為每個 model 寫不同的深度對比代碼奏司。就算這樣乔询,如果我們沒有處理好對象引用,它甚至不能工作韵洋,比如說這個父組件:

React.createClass({
  getInitialState: function() {
    return { value: { foo: 'bar' } };
  },

  onClick: function() {
    var value = this.state.value;
    value.foo += 'bar'; // ANTI-PATTERN!
    this.setState({ value: value });
  },

  render: function() {
    return (
      <div>
        <InnerComponent value={this.state.value} />
        <a onClick={this.onClick}>Click me</a>
      </div>
    );
  }
});

內(nèi)部組件第一次渲染的時候竿刁,它會獲取 { foo: 'bar' } 作為 value 的值。如果用戶點擊了 a 標(biāo)簽搪缨,父組件的 state 會更新成 { value: { foo: 'barbar' } }食拜,觸發(fā)內(nèi)部組件的重新渲染過程,內(nèi)部組件會收到 { foo: 'barbar' } 作為 value 的新的值副编。

這里的問題是因為父組件和內(nèi)部組件共享同一個對象的引用负甸,當(dāng)對象在 onClick 函數(shù)的第二行發(fā)生改變的時候,內(nèi)部組件的屬性也發(fā)生了改變痹届,所以當(dāng)重新渲染過程開始呻待,shouldComponentUpdate 被調(diào)用的時候,this.props.value.foonextProps.value.foo 是相等的队腐,因為實際上 this.props.valuenextProps.value 是同一個對象的引用蚕捉。

因此,我們會丟失 prop 的改變柴淘,縮短重新渲染過程迫淹,UI 也不會從 'bar' 更新到 'barbar'

Immutable-js 來救贖

Immutable-js 是 Lee Byron 寫的 JavaScript 集合類型的庫,最近被 Facebook 開源悠就,它通過結(jié)構(gòu)共享提供不可變持久化集合類型千绪。一起看下這些特性的含義:

  • Immutable: 一旦創(chuàng)建,集合就不能再改變梗脾。

  • Persistent: 新的集合類型可以通過之前的集合創(chuàng)建荸型,比如 set 產(chǎn)生改變的集合。創(chuàng)建新的集合之后源集合仍然有效。

  • Structural Sharing: 新的集合會使用盡量多的源集合的結(jié)構(gòu)瑞妇,減少復(fù)制來節(jié)省空間和性能友好稿静。如果新的集合和源集合相等,一般會返回源結(jié)構(gòu)辕狰。

不可變讓跟蹤改變非常簡單改备;每次改變都是產(chǎn)生新的對象,所以我們僅需要對象的引用是否改變蔓倍,比如這段簡單的 JavaScript 代碼:

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

盡管 y 被改變悬钳,因為它和 x 引用的是同一個對象,這個對比返回 true偶翅。然而默勾,這個代碼可以使用 immutable-js 改寫如下:

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

這個例子中,因為改變 x 的時候返回了新的引用聚谁,我們就可以安全的認(rèn)為 x 已經(jīng)改變母剥。

臟檢測可以作為另外的可行的方式追蹤改變,給 setters 一個標(biāo)示形导。這個方法的問題是环疼,它強制你使用 setters,而且要寫很多額外的代碼朵耕,影響你的類炫隶。或者你可以在改變之前深拷貝對象阎曹,然后進行深對比來確定是不是發(fā)生了改變等限。這個方法的問題是,深拷貝和深對比都是很花性能的操作芬膝。

因此望门,不可變數(shù)據(jù)結(jié)構(gòu)給你提供了一個高效、簡潔的方式來跟蹤對象的改變锰霜,而跟蹤改變是實現(xiàn) shouldComponentUpdate 的關(guān)鍵筹误。所以,如果我們使用 immutable-js 提供的抽象創(chuàng)建 props 和 state 模型癣缅,我們就可以使用 PureRenderMixin厨剪,而且能夠獲得很好的性能增強。

Immutable-js 和 Flux

如果你在使用 Flux友存,你應(yīng)該開始使用 immutable-js 寫你的 stores祷膳,看一下 full API

讓我們看一個可行的方式屡立,使用不可變數(shù)據(jù)結(jié)構(gòu)來給消息示例創(chuàng)建數(shù)據(jù)結(jié)構(gòu)直晨。首先我們要給每個要建模的實體定義一個 Record。Records 僅僅是一個不可變?nèi)萜鳎锩姹4嬉幌盗芯唧w數(shù)據(jù):

var User = Immutable.Record({
  id: undefined,
  name: undefined,
  email: undefined
});

var Message = Immutable.Record({
  timestamp: new Date(),
  sender: undefined,
  text: ''
});

Record 方法接收一個對象勇皇,來定義字段和對應(yīng)的默認(rèn)數(shù)據(jù)罩句。

消息的 store 可以使用兩個 list 來跟蹤 users 和 messages:

this.users = Immutable.List();
this.messages = Immutable.List();

實現(xiàn)函數(shù)處理每個 payload 類型應(yīng)該是比較簡單的,比如敛摘,當(dāng) store 看到一個代表新消息的 payload 時门烂,我們就創(chuàng)建一個新的 record,并放入消息列表:

this.messages = this.messages.push(new Message({
  timestamp: payload.timestamp,
  sender: payload.sender,
  text: payload.text
});

注意:因為數(shù)據(jù)結(jié)構(gòu)不可變兄淫,我們需要把 push 方法的結(jié)果賦給 this.messages屯远。

在 React 里,如果我們也使用 immutable-js 數(shù)據(jù)結(jié)構(gòu)來保存組件的 state捕虽,我門可以把 PureRenderMixin 混入到我門所有的組件來縮短重新渲染回路氓润。

這篇文章是翻譯React官方文檔

https://segmentfault.com/a/1190000006254212

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市薯鳍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌挨措,老刑警劉巖挖滤,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異浅役,居然都是意外死亡斩松,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門觉既,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惧盹,“玉大人,你說我怎么就攤上這事瞪讼【” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵符欠,是天一觀的道長嫡霞。 經(jīng)常有香客問我,道長希柿,這世上最難降的妖魔是什么诊沪? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮曾撤,結(jié)果婚禮上端姚,老公的妹妹穿的比我還像新娘。我一直安慰自己挤悉,他們只是感情好渐裸,可當(dāng)我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般橄仆。 火紅的嫁衣襯著肌膚如雪剩膘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天盆顾,我揣著相機與錄音怠褐,去河邊找鬼。 笑死您宪,一個胖子當(dāng)著我的面吹牛奈懒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宪巨,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼磷杏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了捏卓?” 一聲冷哼從身側(cè)響起极祸,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎怠晴,沒想到半個月后遥金,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡蒜田,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年稿械,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冲粤。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡美莫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梯捕,到底是詐尸還是另有隱情厢呵,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布傀顾,位于F島的核電站述吸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏锣笨。R本人自食惡果不足惜蝌矛,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望错英。 院中可真熱鬧入撒,春花似錦、人聲如沸椭岩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至献雅,卻和暖如春碉考,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挺身。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工侯谁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人章钾。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓墙贱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親贱傀。 傳聞我的和親對象是個殘疾皇子惨撇,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,678評論 2 354

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