原文地址在我的博客, 轉(zhuǎn)載請(qǐng)注明出處,謝謝!
前言
隨著項(xiàng)目開(kāi)發(fā)的深入拉鹃,不可避免了遇到了一些問(wèn)題。剛開(kāi)始出現(xiàn)問(wèn)題時(shí)很懵鲫忍,不知道該怎么解決膏燕,原因就是對(duì)React的原理理解的不夠透徹,不知道問(wèn)題出在哪悟民。在解決問(wèn)題的過(guò)程中坝辫,也逐漸深入了解了React的一些原理,這篇文章就來(lái)分享一下我對(duì)React一些原理的理解射亏。
注意
這篇文章并不是教程近忙,只是我對(duì)React原理的一些個(gè)人理解,歡迎與我一起討論鸦泳。文章不對(duì)的地方银锻,還請(qǐng)讀者費(fèi)心指出-
概述
本文是《使用React技術(shù)棧的一些收獲》系列文章的第二篇(第一篇在這里,介紹如何開(kāi)始構(gòu)建React大型項(xiàng)目)做鹰,簡(jiǎn)單介紹了React一些原理击纬,包括React合成事件系統(tǒng)、組件的生命周期以及setState()
钾麸。
React合成事件系統(tǒng)
React快速的原因之一就是React很少直接操作DOM更振,瀏覽器事件也是一樣炕桨。原因是太多的瀏覽器事件會(huì)占用很大內(nèi)存。
React為此自己實(shí)現(xiàn)了一套合成系統(tǒng)肯腕,在DOM事件體系基礎(chǔ)上做了很大改進(jìn)献宫,減少了內(nèi)存消耗,簡(jiǎn)化了事件邏輯实撒,最大化解決瀏覽器兼容問(wèn)題姊途。
其基本原理就是,所有在JSX聲明的事件都會(huì)被委托在頂層document節(jié)點(diǎn)上知态,并根據(jù)事件名和組件名存儲(chǔ)回調(diào)函數(shù)(listenerBank
)捷兰。每次當(dāng)某個(gè)組件觸發(fā)事件時(shí),在document節(jié)點(diǎn)上綁定的監(jiān)聽(tīng)函數(shù)(dispatchEvent
)就會(huì)找到這個(gè)組件和它的所有父組件(ancestors
)负敏,對(duì)每個(gè)組件創(chuàng)建對(duì)應(yīng)React合成事件(SyntheticEvent
)并批處理(runEventQueueInBatch(events)
)贡茅,從而根據(jù)事件名和組件名調(diào)用(invokeGuardedCallback
)回調(diào)函數(shù)。
因此其做,如果你采用下面這種寫(xiě)法顶考,并且這樣的P標(biāo)簽有很多個(gè):
listView = list.map((item,index) => {
return (
<p onClick={this.handleClick} key={item.id}>{item.text}</p>
)
})
That's OK,React幫你實(shí)現(xiàn)了事件委托
妖泄。我之前因?yàn)椴涣私釸eact合成事件系統(tǒng)驹沿,還顯示的使用了事件委托,現(xiàn)在看來(lái)是多此一舉的蹈胡。
由于React合成事件系統(tǒng)模擬事件冒泡的方法是構(gòu)建一個(gè)自己及父組件隊(duì)列甚负,因此也帶來(lái)一個(gè)問(wèn)題,合成事件不能阻止原生事件审残,原生事件可以阻止合成事件。用 event.stopPropagation()
并不能停止事件傳播斑举,應(yīng)該使用 event.preventDefault()
搅轿。
如果你想詳細(xì)了解React合成事件系統(tǒng),移步http://blog.csdn.net/u013510838/article/details/61224760
組件的生命周期(以父子組件為例)
為了搞清楚組件生命周期富玷,構(gòu)造一個(gè)父組件包含子組件并且重寫(xiě)各生命周期函數(shù)的場(chǎng)景:
class Child extends React.Component {
constructor() {
super()
console.log('Child was created!')
}
componentWillMount(){
console.log('Child componentWillMount!')
}
componentDidMount(){
console.log('Child componentDidMount!')
}
componentWillReceiveProps(nextProps){
console.log('Child componentWillReceiveProps:'+nextProps.data )
}
shouldComponentUpdate(nextProps, nextState){
console.log('Child shouldComponentUpdate:'+ nextProps.data)
return true
}
componentWillUpdate(nextProps, nextState){
console.log('Child componentWillUpdate:'+ nextProps.data)
}
componentDidUpdate(){
console.log('Child componentDidUpdate')
}
render() {
console.log('render Child!')
return (
<h1>Child recieve props: {this.props.data}</h1>
);
}
}
class Father extends React.Component {
// ... 前面跟子組件一樣
handleChangeState(){
this.setState({randomData: Math.floor(Math.random()*50)})
}
render() {
console.log('render Father!')
return (
<div>
<Child data={this.state.randomData} />
<h1>Father State: { this.state.randomData}</h1>
<button onClick={this.handleChangeState}>切換狀態(tài)</button>
</div>
);
}
}
React.render(
<Father />,
document.getElementById('root')
);
結(jié)果如下:
剛開(kāi)始
調(diào)用父組件的setState后:
在Jsbin上試試看
有一張圖能說(shuō)明這之間的流程(圖片來(lái)源):
setState并不奇怪
有一個(gè)能反映問(wèn)題的場(chǎng)景:
...
state = {
count: 0
}
componentDidMount() {
this.setState({count: this.state.count + 1})
this.setState({count: this.state.count + 1})
this.setState({count: this.state.count + 1})
}
...
看起來(lái)state.count被增加了三次璧坟,但結(jié)果是增加了一次。這并不奇怪:
React快的原因之一就是赎懦,在執(zhí)行this.setState()
時(shí)雀鹃,React沒(méi)有忙著立即更新state
,只是把新的state
存到一個(gè)隊(duì)列(batchUpdate
)中励两。上面三次執(zhí)行setState
只是對(duì)傳進(jìn)去的對(duì)象進(jìn)行了合并,然后再統(tǒng)一處理(批處理)黎茎,觸發(fā)重新渲染過(guò)程,因此只重新渲染一次当悔,結(jié)果只增加了一次傅瞻。這樣做是非常明智的踢代,因?yàn)樵谝粋€(gè)函數(shù)里調(diào)用多個(gè)setState是常見(jiàn)的,如果每一次調(diào)用setState都要引發(fā)重新渲染嗅骄,顯然不是最佳實(shí)踐胳挎。React官方文檔里也說(shuō)了:
Think of
setState()
as a request rather than an immediate command to update the component.
把
setState()
看作是重新render的一次請(qǐng)求而不是立刻更新組件的指令。
那么調(diào)用this.setState()
后什么時(shí)候this.state才會(huì)更新溺森?
答案是即將要執(zhí)行下一次的render
函數(shù)時(shí)慕爬。
這之間發(fā)生了什么?
setState
調(diào)用后屏积,React會(huì)執(zhí)行一個(gè)事務(wù)(Transaction)医窿,在這個(gè)事務(wù)中,React將新state放進(jìn)一個(gè)隊(duì)列中肾请,當(dāng)事務(wù)完成后留搔,React就會(huì)刷新隊(duì)列,然后啟動(dòng)另一個(gè)事務(wù)铛铁,這個(gè)事務(wù)包括執(zhí)行 shouldComponentUpdate
方法來(lái)判斷是否重新渲染隔显,如果是,React就會(huì)進(jìn)行state合并(state merge
),生成新的state和props饵逐;如果不是括眠,React仍然會(huì)更新this.state
,只不過(guò)不會(huì)再render
了倍权。
開(kāi)發(fā)人員對(duì)setState
感到奇怪的原因可能就是按照上述寫(xiě)法并不能產(chǎn)生預(yù)期效果掷豺,但幸運(yùn)的是我們改動(dòng)一下就可以實(shí)現(xiàn)上述累加效果:
這歸功于setState
可以接受函數(shù)作為參數(shù):
setState(updater, [callback])
...
state = {
score: 0
}
componentDidMount() {
this.setState( (prevState) => ({score : prevState.score + 1}) )
this.setState( (prevState) => ({score : prevState.score + 1}) )
this.setState( (prevState) => ({score : prevState.score + 1}) )
}
}
這個(gè)updater
可以為函數(shù),該函數(shù)接受該組件前一刻的 state 以及當(dāng)前的 props 作為參數(shù)薄声,計(jì)算和返回下一刻的 state当船。
你會(huì)發(fā)現(xiàn)達(dá)到增加三次的目的了: 在Jsbin上試試看
這是因?yàn)镽eact會(huì)把setState
里傳進(jìn)去的函數(shù)放在一個(gè)任務(wù)隊(duì)列里,React 會(huì)依次調(diào)用隊(duì)列中的函數(shù)默辨,傳遞給它們前一刻的 state德频。
另外,不知道你在jsbin上的代碼上注意到?jīng)]有缩幸,調(diào)用setState
后console.log(this.state.score)
輸出仍然為0壹置,也就是this.state
并未改變,并且只render
了一次表谊。
總結(jié)
學(xué)習(xí)一個(gè)框架或者工具钞护,我覺(jué)得應(yīng)該了解以下幾點(diǎn):
- 它是什么?能做什么爆办?
- 它存在的理由是什么难咕?解決了什么樣的問(wèn)題、滿(mǎn)足了什么樣的需求?
- 它的適用場(chǎng)景是什么步藕??jī)?yōu)缺點(diǎn)是什么惦界?
- 它怎么用?最佳實(shí)踐是什么咙冗?
- 它的原理是什么沾歪?
- ...
通過(guò)對(duì)React一些原理的簡(jiǎn)單了解,就懂得了React為什么這么快速的原因之一雾消,也會(huì)在問(wèn)題出現(xiàn)時(shí)知道錯(cuò)在什么地方灾搏,知道合理的解決方案。