React學(xué)習(xí)之前端開發(fā)性能優(yōu)化

1. 單個(gè)react組件性能優(yōu)化

1.1 render里面盡量減少新建變量和bind函數(shù)的使用嘱根,盡量減少傳遞參數(shù)的數(shù)量

render中綁定函數(shù),無非就是下面三種:

render() {
  return (
    <div className='app'>
      <span onClick={this.handleClick}>1</span>
      <span onClick={this.handleClick.bind(this)}>2</span>
      <span onClick={()=>this.handleClick()}>3</span>
    </div>
  )
}

第一種是在構(gòu)造函數(shù)中綁定this巷懈,第二種是在render()函數(shù)里面綁定this该抒,第三種就是使用箭頭函數(shù),上述方法都能實(shí)現(xiàn)this的綁定顶燕。

但是哪一種方法的性能最好凑保,是我們要考慮的問題。毫無疑問第一種的性能最好涌攻。

第一種方法欧引,構(gòu)造函數(shù)每渲染一次便會(huì)執(zhí)行一遍;

第二種方法恳谎,在每次render()的時(shí)候都會(huì)重新執(zhí)行一遍函數(shù)芝此;

第三種方法,每一次render()的時(shí)候因痛,都會(huì)生成一個(gè)新的箭頭函數(shù)婚苹,即使兩個(gè)箭頭函數(shù)的內(nèi)容是一樣的。

react判斷是否需要進(jìn)行render淺層比較鸵膏,簡(jiǎn)單來說就是通過===來判斷的膊升,如果state或者prop的類型是字符串或者數(shù)字,只要值相同谭企,那么淺層比較就會(huì)認(rèn)為其相同廓译;

但是如果前者的類型是復(fù)雜的對(duì)象的時(shí)候评肆,我們知道對(duì)象是引用類型,淺層比較只會(huì)認(rèn)為這兩個(gè)prop是不是同一個(gè)引用责循,如果不是糟港,哪怕這兩個(gè)對(duì)象中的內(nèi)容完全一樣,也會(huì)被認(rèn)為是兩個(gè)不同的prop院仿。

舉個(gè)例子:

當(dāng)我們給組件App名為styleprop賦值秸抚;

<App style={{ color:"green" }}

使用這種方法,每一次渲染都會(huì)被認(rèn)為是一個(gè)style這個(gè)prop發(fā)生了變化歹垫,因?yàn)槊恳淮味紩?huì)產(chǎn)生一個(gè)對(duì)象給style剥汤。

如果想要讓react渲染的時(shí)候認(rèn)為前后對(duì)象類型prop相同,則必須要保證prop指向同一個(gè)javascript對(duì)象排惨,改進(jìn)如下:

const appStyle = { color: "red" }; //這個(gè)初始化只執(zhí)行一次吭敢,不要放在render中,可以放在構(gòu)造函數(shù)中
<App style={appStyle} />

1.2 定制shouldComponentUpdate函數(shù)

生命周期函數(shù)shouldComponentUpdate是決定react組件什么時(shí)候能夠重新渲染的函數(shù)暮芭,但是這個(gè)函數(shù)默認(rèn)的實(shí)現(xiàn)方式就是簡(jiǎn)單的返回一個(gè)true鹿驼。也就是說,默認(rèn)每次更新的時(shí)候都要調(diào)用所用的生命周期函數(shù)辕宏,包括render函數(shù)畜晰,重新渲染。

看看下面這個(gè)例子:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count = 2,
      name = 'apple',
    }
    this.handleClick = this.handleClick.bind(this);
    this.handleName = this.handleName.bind(this);
  }
  this.handleClick() {
  // ...
  }
  this.handleName() {
  // ...
  }
  render() {
    return (
      <div className='app'>
        <span>數(shù)量瑞筐,{this.state.count}</span>
        <button onClick={this.handleClick}>改變數(shù)量</button>
        <button onClick={this.handleName}>改變名字</button>
        <Child title={this.state.name}></Child>
      </div>
    );
  }
}


class Child extends React.Component {
  render() {
    console.log('render了一次');
    return (
      <h3>我想吃凄鼻,{this.props.title}</h3>
    );
  }
}

我們寫了兩個(gè)組件,AppChild組件聚假,并寫兩個(gè)方法块蚌,一個(gè)改變App中的count的值,一個(gè)是改變name膘格,我們?cè)?code>Child的render中打印了每次是否執(zhí)行峭范。

不出意外,雖然Child組件里的title值沒有改變闯袒,但是還是render了虎敦。

為了進(jìn)一步優(yōu)化這個(gè)問題,我們這樣改Child組件:

class Child extends React.Component {
  shouldComponentUpdate(nextProps,nextState) {
    if(nextProps.title == this.props.title) {
      return false;
    }
    return true;
  }

  render() {
    console.log('render了一次');
    return (
      <h3>我想吃政敢,{this.props.title}</h3>
    );
  }
}

只有當(dāng)Childtitle值發(fā)生改變的時(shí)候其徙,組件才會(huì)去render

在最新的react中喷户,react給我們提供了React.PureComponent唾那,官方也在早期提供了名為react-addons-pure-render-mixin插件來重新實(shí)現(xiàn)shouldComponentUpdate生命周期方法。

class Child extends React.PureComponent {
  // shouldComponentUpdate(nextProps,nextState) {
  //   if(nextProps.title == this.props.title) {
  //     return false;
  //   }
  //   return true;
  // }

  render() {
    console.log('render了一次');
    return (
      <h3>我想吃,{this.props.title}</h3>
    );
  }
}

通過上述的方法的效果也是和我們先前定制shouldComponentUpdate的效果是一致的闹获。

但是我們要注意的是期犬,這里的PureRender是淺比較的,因?yàn)樯畋容^的場(chǎng)景是相當(dāng)昂貴的避诽。所以我們要注意我們?cè)?code>1.1中說到的一些注意點(diǎn):不要直接為props設(shè)置對(duì)象或者數(shù)組龟虎、不要將方法直接綁定在元素上,因?yàn)槠鋵?shí)函數(shù)也是對(duì)象沙庐。

1.3 Immutable.js

javascript中的對(duì)象一般都是可變的鲤妥,因?yàn)槭褂昧艘觅x值,新的對(duì)象簡(jiǎn)單的引用了原始對(duì)象拱雏,改變新對(duì)象將影響到原始對(duì)象棉安。

舉個(gè)例子:

student = { age : 1 };
school = student;
school.age = 2;

當(dāng)我們給school.age賦值后,會(huì)發(fā)現(xiàn)student.a也變成了2铸抑,雖然我們可以通過深拷貝與淺拷貝解決這個(gè)問題贡耽,但是這樣做非常的昂貴,對(duì)cpu和內(nèi)存會(huì)造成浪費(fèi)鹊汛。

這里就需要用到Immutable蒲赂,通過Immutable創(chuàng)建的Immutable Data一旦被創(chuàng)建,就不能再更改刁憋。對(duì)Immutable對(duì)象進(jìn)行修改凳宙、添加或刪除操作,都會(huì)返回一個(gè)新的Immutable對(duì)象职祷。

下面是三個(gè)比較重要且用到的數(shù)據(jù)結(jié)構(gòu)

  • Map:鍵值對(duì)集合,對(duì)應(yīng)Object届囚,ES6中也有專門的Map對(duì)象
  • List:有序可重復(fù)列表有梆,對(duì)應(yīng)于Array
  • ArraySet:有序且不可重復(fù)的列表

我們可以看一個(gè)例子:

使用Map生成一個(gè)immutable對(duì)象:

import { Map, is } from 'immutable';

let a = Map({
  'name': 'apple',
  'list': Map({name: 'orange'})
})

let b = a.set('name','banana');

console.log(a.get('course') === b.get('course')); // 返回true
console.log(a === b); // 返回false

Immutable.is 比較的是兩個(gè)對(duì)象的 hashCodevalueOf(對(duì)于JavaScript對(duì)象)。由于immutable內(nèi)部使用了Trie數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)意系,只要兩個(gè)對(duì)象的 hashCode 相等泥耀,值就是一樣的。這樣的算法避免了深度遍歷比較蛔添,性能非常好痰催。

Immutable優(yōu)點(diǎn)

  • 減少內(nèi)存的使用
  • 并發(fā)安全
  • 降低項(xiàng)目的復(fù)雜度
  • 便于比較復(fù)雜數(shù)據(jù),定制shouldComponentUpdate方便
  • 時(shí)間旅行功能
  • 函數(shù)式編程

Immutable缺點(diǎn)

  • 學(xué)習(xí)成本
  • 庫的大杏啤(建議使用seamless-immutable
  • 對(duì)現(xiàn)有項(xiàng)目入侵嚴(yán)重
  • 容易與原生的對(duì)象進(jìn)行混淆

2. 多個(gè)react組件性能優(yōu)化

react組件在裝載過程中夸溶,react通過在render方法在內(nèi)存中產(chǎn)生一個(gè)樹形結(jié)構(gòu),樹上的節(jié)點(diǎn)代表一個(gè)react組件或者原生的Dom元素凶硅,這個(gè)樹形結(jié)構(gòu)就是我們所謂的Vitural Dom缝裁,react根據(jù)這個(gè)來渲染產(chǎn)生瀏覽器的Dom樹。

react在更新階段對(duì)比原有的Vitural Dom和新生成的Vitural Dom足绅,找出不同之處捷绑,在根據(jù)不同來渲染Dom樹韩脑。

react為了追求高性能,采用了時(shí)間復(fù)雜度為O(N)來比較兩個(gè)屬性結(jié)構(gòu)的區(qū)別粹污,因?yàn)橐_切比較兩個(gè)樹形結(jié)構(gòu)段多,需要通過O(N^3),這會(huì)降低性能壮吩。

  • 節(jié)點(diǎn)類型不同
// A組件
<div>
  <Todos />
</div>

// B組件
<span>
  <Todos />
</span>

我們想把A組件更新成B組件进苍,react在做比較的時(shí)候,發(fā)現(xiàn)最外面的根結(jié)點(diǎn)完全不一樣粥航,直接銷毀之前的<div>節(jié)點(diǎn)琅捏,包括里面的子節(jié)點(diǎn)也一并銷毀,這是一個(gè)巨大的浪費(fèi)递雀,但是為了避免O(N^3)的時(shí)間復(fù)雜度柄延,只能采用這種方式。

所以在開發(fā)過程中缀程,我們應(yīng)該盡量避免上面的情況搜吧,不要將包裹節(jié)點(diǎn)的類型隨意改變景用。

  • 兩個(gè)節(jié)點(diǎn)類型一樣

這里包括兩種情況肛冶,一種是節(jié)點(diǎn)是Dom類型,還有一種react組件顿苇。

對(duì)于dom類型撩满,我們舉個(gè)例子:

// A組件
<div style={{color: 'red',fontSize:15}} className="welcome">
  Hello World!!!
</div>

// B組件
<div style={{color: 'green',fontSize:15}} className="react">
  Good Bye!!!
</div>

上述A和B組件的區(qū)別是文字蜒程、classNamestyle中的color發(fā)生改變伺帘,因?yàn)?code>Dom元素沒變昭躺,React只會(huì)修改他變化的部分。

針對(duì)react組件類型伪嫁,渲染無非就是再執(zhí)行一遍組件實(shí)例的更新過程领炫,最主要的就是定制shouldComponentUpdate

  • 多個(gè)子組件情況

例子一:

// A
<ul>
  <TodoItem text="First" complete={false} />
  <TodoItem text="Second" complete={false} />
</ul>

// B
<ul>
  <TodoItem text="First" complete={false} />
  <TodoItem text="Second" complete={false} />
  <TodoItem text="Third" complete={false} />
</ul>

從A變到B张咳,如果shouldComponentUpdate處理得當(dāng)帝洪,我們只需要更新裝載third的那一次就行。

我們來看看下一個(gè)例子:

// A
<ul>
  <TodoItem text="First" complete={false} />
  <TodoItem text="Second" complete={false} />
</ul>

// B
<ul>
  <TodoItem text="Zero" complete={false} />
  <TodoItem text="First" complete={false} />
  <TodoItem text="Second" complete={false} />
</ul>

這里因?yàn)?code>react是采用O(n)的時(shí)間復(fù)雜度脚猾,所以會(huì)依次將textFirst的改為Zero葱峡,textSecond改為First,在最后再加上一個(gè)組件婚陪,textSecond∽逦郑現(xiàn)存的兩個(gè)的text的屬性都被改變了,所以會(huì)依次渲染。

如果我們這里有100個(gè)實(shí)例脆淹,那么就會(huì)發(fā)生100次更新常空。

這里我們就要用到Key

簡(jiǎn)單來說,其實(shí)這一個(gè)Key就是react組件的身份證號(hào)盖溺。

我們將上一個(gè)例子改成如下漓糙,就可以避免上面的問題了,react就能夠知道其實(shí)B里面的第二個(gè)和第三個(gè)組件其實(shí)就是A中的第一個(gè)和第二個(gè)實(shí)例烘嘱。

// A
<ul>
  <TodoItem key={1} text="First" complete={false} />
  <TodoItem key={2} text="Second" complete={false} />
</ul>

// B
<ul>
  <TodoItem key={0} text="Zero" complete={false} />
  <TodoItem key={1} text="First" complete={false} />
  <TodoItem key={2} text="Second" complete={false} />
</ul>

不過現(xiàn)在昆禽,react也會(huì)提醒我們不要忘記使用key,如果沒有加蝇庭,瀏覽器中會(huì)報(bào)錯(cuò)醉鳖。

關(guān)于key的使用我們要注意的是,這個(gè)key值要穩(wěn)定不變的哮内,就如同身份證號(hào)對(duì)于我們是穩(wěn)定不變的一樣盗棵。

一個(gè)常見的錯(cuò)誤就是,拿數(shù)組的的下標(biāo)值去當(dāng)做key北发,這個(gè)是很危險(xiǎn)的纹因,代碼如下,我們一定要避免

<ul>
  {
    todos.map((item, index) => {
      <TodoItem
        key={index}
        text={item.text}
        completed={item.completed}
      >
    })
  }
</ul>

未完待續(xù)...

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末琳拨,一起剝皮案震驚了整個(gè)濱河市瞭恰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌狱庇,老刑警劉巖惊畏,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異密任,居然都是意外死亡陕截,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門批什,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人社搅,你說我怎么就攤上這事驻债。” “怎么了形葬?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵合呐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我笙以,道長(zhǎng)淌实,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮拆祈,結(jié)果婚禮上恨闪,老公的妹妹穿的比我還像新娘。我一直安慰自己放坏,他們只是感情好咙咽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著淤年,像睡著了一般钧敞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上麸粮,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天溉苛,我揣著相機(jī)與錄音,去河邊找鬼弄诲。 笑死愚战,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的威根。 我是一名探鬼主播凤巨,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼洛搀!你這毒婦竟也來了敢茁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤留美,失蹤者是張志新(化名)和其女友劉穎彰檬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谎砾,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡逢倍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了景图。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片较雕。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖挚币,靈堂內(nèi)的尸體忽然破棺而出亮蒋,到底是詐尸還是另有隱情,我是刑警寧澤妆毕,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布慎玖,位于F島的核電站,受9級(jí)特大地震影響笛粘,放射性物質(zhì)發(fā)生泄漏趁怔。R本人自食惡果不足惜湿硝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望润努。 院中可真熱鬧关斜,春花似錦、人聲如沸任连。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽随抠。三九已至裁着,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拱她,已是汗流浹背二驰。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秉沼,地道東北人桶雀。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像唬复,于是被迫代替她去往敵國(guó)和親矗积。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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