沒看幾天的React柔袁,我居然就敢寫進(jìn)階鞠绰、寫原理了厢洞,我一定是膨脹了...
React原理
這個(gè)標(biāo)題起的有點(diǎn)大......
-
虛擬DOM
在前端開發(fā)的過(guò)程中,我們經(jīng)常會(huì)做的一件事就是將變化的數(shù)據(jù)實(shí)時(shí)更新到UI上西饵,這時(shí)就需要對(duì)DOM進(jìn)行更新和重新渲染酝掩,而頻繁的DOM操作通常是性能瓶頸產(chǎn)生的原因之一,有時(shí)候我們會(huì)遇到這樣一種尷尬的情況:比如有一個(gè)列表數(shù)據(jù)眷柔,當(dāng)用戶執(zhí)行刷新操作時(shí)期虾,Ajax會(huì)重新從后臺(tái)請(qǐng)求數(shù)據(jù),即使新請(qǐng)求的數(shù)據(jù)和上次完全相同驯嘱,DOM也會(huì)被全部更新一遍并進(jìn)行重新渲染镶苞,這樣就產(chǎn)生了不必要的性能開銷。
React為此引入了虛擬DOM(Virtual DOM)機(jī)制:對(duì)于每一個(gè)組件鞠评,React會(huì)在內(nèi)存中構(gòu)建一個(gè)相對(duì)應(yīng)的DOM樹茂蚓,基于React開發(fā)時(shí)所有的DOM構(gòu)造都是通過(guò)虛擬DOM進(jìn)行,每當(dāng)組件的狀態(tài)發(fā)生變化時(shí)剃幌,React都會(huì)重新構(gòu)建整個(gè)DOM數(shù)據(jù)聋涨,然后將當(dāng)前的整個(gè)DOM樹和上一次的DOM樹進(jìn)行對(duì)比,得出DOM結(jié)構(gòu)變化的部分(Patchs)锥忿,然后將這些Patchs 再更新到真實(shí)DOM中牛郑。整個(gè)過(guò)程都是在內(nèi)存中進(jìn)行怠肋,因此是非常高效的敬鬓。借用一張圖可以清晰的表示虛擬DOM的工作機(jī)制:這里要注意,React只能平級(jí)的去對(duì)比樹結(jié)構(gòu)上的每個(gè)元素笙各。
接下來(lái)我們看看React是如何創(chuàng)建虛擬DOM的呢:本質(zhì)上就是通過(guò)獲取類型钉答、配置、子元素來(lái)生成虛擬DOM節(jié)點(diǎn)杈抢,我們可以通過(guò)Babel來(lái)看看創(chuàng)建一個(gè)虛擬DOM節(jié)點(diǎn)是怎樣的:
//這是我們通過(guò)JSX寫出來(lái)的標(biāo)簽
<h2 data-id='test'>
<p>p node</p>
h2 node
</h2>
對(duì)應(yīng)的数尿,Babel把它轉(zhuǎn)化成了:
React.createElement(
'h2', //type
{ 'data-id': 'test' }, //config
React.createElement( //children
'p',
null,
'p node'
),
'h2 node'
);
我們可以看到創(chuàng)建一個(gè)虛擬DOM節(jié)點(diǎn)的所需參數(shù):類型、配置惶楼、子元素右蹦。
然后return
了一個(gè)ReactElement
诊杆,那么,什么是ReactElement
呢何陆?
最終我們可以獲取這么一個(gè)對(duì)象晨汹,JSX就以這樣一個(gè)對(duì)象遞歸的存在內(nèi)存中,只要有數(shù)據(jù)發(fā)生變化贷盲,就會(huì)把新的數(shù)據(jù)和老的數(shù)據(jù)取出來(lái)做對(duì)比淘这,從而獲取
Diff
,再去執(zhí)行ReactMultiChild.js
文件中的_updateChildren
方法去執(zhí)行Patchs
更新到真實(shí)DOM中巩剖。ReactMultiChild.js
文件中的_updateChildren
方法代碼太長(zhǎng)了铝穷,這里就不貼了,有興趣的可以自己去看看佳魔。
-
生命周期
關(guān)于React的生命周期曙聂,還是先貼圖,方便理解:
Initial render
步驟下鞠鲜,這些生命周期相信對(duì)React有基礎(chǔ)的同學(xué)筹陵,也基本都了解了,作為初始渲染镊尺,其實(shí)并沒有太多可以優(yōu)化的地方朦佩,該執(zhí)行的還是都得執(zhí)行。所以對(duì)于優(yōu)化庐氮,我們關(guān)注的還是另一個(gè)生命周期
shouldComponentUpdate()
方法语稠,通過(guò)定制它來(lái)優(yōu)化我們的組件。我們的組件在更新渲染的時(shí)候弄砍,會(huì)調(diào)用
shouldComponentUpdate()
方法仙畦,當(dāng)其返回true
的時(shí)候才能正常往下更新渲染,反之就不更新渲染音婶。觸發(fā)組件更新渲染的主要有三種情況:
1慨畸、
this.setState()
狀態(tài)變更會(huì)觸發(fā)組件更新渲染2、組件外部發(fā)生變化衣式,特別是用了
redux
的connect
方法的時(shí)候寸士,父組件props
發(fā)生變化時(shí),就會(huì)調(diào)用componentWillReceiveProps()
方法碴卧,從而觸發(fā)往下的一系列更新弱卡。3、
this.forceUpdate()
這個(gè)方法我們很少用住册,它直接調(diào)用了componentWillUpdate()
方法婶博,強(qiáng)制的更新。
來(lái)一個(gè)簡(jiǎn)單的例子:
import React ,{Component} from 'react'
class Demo extends Component {
constructor(props) {
super(props)
this.state={ num:1 }
this.handleClick = this.handleClick.bind(this)
}
handleClick(){
this.setState({
num:this.state.num+1
})
}
shouldComponentUpdate(nextProps, nextState){
console.log('nextProps:',nextProps,'\n','nextState:',nextState);
return false
}
render(){
return(
<div>
<p>{this.state.num}</p>
<button onClick={this.handleClick}>click</button>
</div>
)
}
}
export default Demo
當(dāng)shouldComponentUpdate()
返回為false
的時(shí)候荧飞,更新渲染自然被拒絕了凡人。同時(shí)我們能夠看到shouldComponentUpdate()
自帶了nextProps
,nextState
兩個(gè)下次的狀態(tài)和屬性名党,是不是嗅到了一些可以優(yōu)化的氣息了?這里先留給大家思考挠轴,后續(xù)會(huì)再出文章專門講優(yōu)化兑巾。
-
setState
this.setState()
算是很經(jīng)常用的一個(gè)方法了吧,應(yīng)該都不陌生了忠荞,setState
會(huì)自動(dòng)調(diào)用render()
方法去重新渲染蒋歌,所以這里有幾點(diǎn)要注意:
1、不要寫this.state.num=1
這種寫法委煤,因?yàn)檫@種寫法不會(huì)更新任何顯示堂油,是一種不符合規(guī)范的錯(cuò)誤寫法。
2碧绞、this.setState()
是有隊(duì)列機(jī)制的府框,簡(jiǎn)單的來(lái)說(shuō),就是異步執(zhí)行的讥邻,并且你在一個(gè)方法中多次的去執(zhí)行setState
迫靖,它也只執(zhí)行最終那一次的更新渲染,不會(huì)次次都更新渲染兴使。
3系宜、不要在render()
執(zhí)行this.setState()
,因?yàn)闀?huì)導(dǎo)致死循環(huán)发魄。