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
名為style
的prop
賦值秸抚;
<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è)組件,App
和Child
組件聚假,并寫兩個(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)Child
的title
值發(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)于ArrayArraySet
:有序且不可重復(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ì)象的 hashCode
或 valueOf
(對(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ū)別是文字蜒程、className
、style
中的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ì)依次將text
為First
的改為Zero
葱峡,text
為Second
改為First
,在最后再加上一個(gè)組件婚陪,text
為Second
∽逦郑現(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ù)...