隨著專案開發的深入,不可避免了遇到了一些問題。剛開始出現問題時很懵,不知道該怎麼解決,原因就是對React的原理理解的不夠透徹,不知道問題出在哪。在解決問題的過程中,也逐漸深入瞭解了React的一些原理,這篇文章就來分享一下我對React一些原理的理解。
注意
這篇文章並不是教程,只是我對React原理的一些個人理解,歡迎與我一起討論。文章不對的地方,還請讀者費心指出^-^
概述
本文是《使用React技術棧的一些收穫》系列文章的第二篇(第一篇在這裡,介紹如何開始構建React大型專案),簡單介紹了React一些原理,包括React合成事件系統、元件的生命週期以及setState()
。
React合成事件系統
React快速的原因之一就是React很少直接操作DOM,瀏覽器事件也是一樣。原因是太多的瀏覽器事件會佔用很大記憶體。
React為此自己實現了一套合成系統,在DOM事件體系基礎上做了很大改進,減少了記憶體消耗,簡化了事件邏輯,最大化解決瀏覽器相容問題。
其基本原理就是,所有在JSX宣告的事件都會被委託在頂層document節點上,並根據事件名和元件名儲存回撥函式(listenerBank
)。每次當某個元件觸發事件時,在document節點上繫結的監聽函式(dispatchEvent
)就會找到這個元件和它的所有父元件(ancestors
),對每個元件建立對應React合成事件(SyntheticEvent
)並批處理(runEventQueueInBatch(events)
),從而根據事件名和元件名呼叫(invokeGuardedCallback
)回撥函式。
因此,如果你採用下面這種寫法,並且這樣的P標籤有很多個:
listView = list.map((item,index) => {
return (
<p onClick={this.handleClick} key={item.id}>{item.text}</p>
)})複製程式碼
That`s OK,React幫你實現了事件委託
。我之前因為不瞭解React合成事件系統,還顯示的使用了事件委託,現在看來是多此一舉的。
由於React合成事件系統模擬事件冒泡的方法是構建一個自己及父元件佇列,因此也帶來一個問題,合成事件不能阻止原生事件,原生事件可以阻止合成事件。用 event.stopPropagation()
並不能停止事件傳播,應該使用 event.preventDefault()
。
如果你想詳細瞭解React合成事件系統,移步blog.csdn.net/u013510838/…
元件的生命週期(以父子元件為例)
為了搞清楚元件生命週期,構造一個父元件包含子元件並且重寫各生命週期函式的場景:
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}>切換狀態</button>
</div>
);
}
}
React.render(
<Father />,
document.getElementById(`root`)
);複製程式碼
結果如下:剛開始![Alt text](https://ooo.0o0.ooo/2017/06/28/595398ee54b1a.png)
呼叫父元件的setState後:![Alt text](https://ooo.0o0.ooo/2017/06/28/595398ff8a44a.png)
有一張圖能說明這之間的流程(圖片來源):![Alt text](https://ooo.0o0.ooo/2017/06/28/595398b92d076.png)
setState並不奇怪
有一個能反映問題的場景:
...
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})
}
...複製程式碼
看起來state.count被增加了三次,但結果是增加了一次。這並不奇怪:
React快的原因之一就是,在執行this.setState()
時,React沒有忙著立即更新state
,只是把新的state
存到一個佇列(batchUpdate
)中。上面三次執行setState
只是對傳進去的物件進行了合併,然後再統一處理(批處理),觸發重新渲染過程,因此只重新渲染一次,結果只增加了一次。這樣做是非常明智的,因為在一個函式裡呼叫多個setState是常見的,如果每一次呼叫setState都要引發重新渲染,顯然不是最佳實踐。React官方文件裡也說了:
Think of
setState()
as a request rather than an immediate command to update the component.
把
setState()
看作是重新render的一次請求而不是立刻更新元件的指令。
那麼呼叫this.setState()
後什麼時候this.state才會更新?答案是即將要執行下一次的render
函式時。
這之間發生了什麼?setState
呼叫後,React會執行一個事務(Transaction),在這個事務中,React將新state放進一個佇列中,當事務完成後,React就會重新整理佇列,然後啟動另一個事務,這個事務包括執行 shouldComponentUpdate
方法來判斷是否重新渲染,如果是,React就會進行state合併(state merge
),生成新的state和props;如果不是,React仍然會更新this.state
,只不過不會再render
了。
開發人員對setState
感到奇怪的原因可能就是按照上述寫法並不能產生預期效果,但幸運的是我們改動一下就可以實現上述累加效果:這歸功於setState
可以接受函式作為引數:
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}) )
} 複製程式碼
}這個updater
可以為函式,該函式接受該元件前一刻的 state 以及當前的 props 作為引數,計算和返回下一刻的 state。
你會發現達到增加三次的目的了: 在Jsbin上試試看
這是因為React會把setState
裡傳進去的函式放在一個任務佇列裡,React 會依次呼叫佇列中的函式,傳遞給它們前一刻的 state。
另外,不知道你在jsbin上的程式碼上注意到沒有,呼叫setState
後console.log(this.state.score)
輸出仍然為0,也就是this.state
並未改變,並且只render
了一次。
總結
學習一個框架或者工具,我覺得應該瞭解以下幾點:
-
它是什麼?能做什麼?
-
它存在的理由是什麼?解決了什麼樣的問題、滿足了什麼樣的需求?
-
它的適用場景是什麼?優缺點是什麼?
-
它怎麼用?最佳實踐是什麼?
-
它的原理是什麼?
-
…
通過對React一些原理的簡單瞭解,就懂得了React為什麼這麼快速的原因之一,也會在問題出現時知道錯在什麼地方,知道合理的解決方案。