原文地址在個人博客, 轉載請註明出處,謝謝!
隨着項目開發的深刻,不可避免了遇到了一些問題。剛開始出現問題時很懵,不知道該怎麼解決,緣由就是對React的原理理解的不夠透徹,不知道問題出在哪。在解決問題的過程當中,也逐漸深刻了解了React的一些原理,這篇文章就來分享一下我對React一些原理的理解。javascript
注意
這篇文章並非教程,只是我對React原理的一些我的理解,歡迎與我一塊兒討論。文章不對的地方,還請讀者費心指出^-^
本文是《使用React技術棧的一些收穫》系列文章的第二篇(第一篇在這裏,介紹如何開始構建React大型項目),簡單介紹了React一些原理,包括React合成事件系統、組件的生命週期以及setState()
。html
React快速的緣由之一就是React不多直接操做DOM,瀏覽器事件也是同樣。緣由是太多的瀏覽器事件會佔用很大內存。java
React爲此本身實現了一套合成系統,在DOM事件體系基礎上作了很大改進,減小了內存消耗,簡化了事件邏輯,最大化解決瀏覽器兼容問題。瀏覽器
其基本原理就是,全部在JSX聲明的事件都會被委託在頂層document節點上,並根據事件名和組件名存儲回調函數(listenerBank
)。每次當某個組件觸發事件時,在document節點上綁定的監聽函數(dispatchEvent
)就會找到這個組件和它的全部父組件(ancestors
),對每一個組件建立對應React合成事件(SyntheticEvent
)並批處理(runEventQueueInBatch(events)
),從而根據事件名和組件名調用(invokeGuardedCallback
)回調函數。框架
所以,若是你採用下面這種寫法,而且這樣的P標籤有不少個:dom
listView = list.map((item,index) => { return ( <p onClick={this.handleClick} key={item.id}>{item.text}</p> ) })
That's OK,React幫你實現了事件委託
。我以前由於不瞭解React合成事件系統,還顯示的使用了事件委託,如今看來是畫蛇添足的。函數
因爲React合成事件系統模擬事件冒泡的方法是構建一個本身及父組件隊列,所以也帶來一個問題,合成事件不能阻止原生事件,原生事件能夠阻止合成事件。若是須要阻止事件傳播, 僅用 event.stopPropagation() 是不行的, 由於React合成事件一樣實現了stopPropagation(), 調用event.stopPropagation() 實際上調用了React的stopPropagation()(這裏的event指的是React合成事件), 只能阻止React合成事件的傳播, 要想完全阻止傳播(包括原生事件), 須要調用React合成事件暴露的原聲事件接口, 所以, 阻止事件傳播須要同時調用合成事件與原生事件的接口:工具
stopPropagation: function(e){ e.stopPropagation(); e.nativeEvent.stopImmediatePropagation(); },
若是你想詳細瞭解React合成事件系統,移步http://blog.csdn.net/u0135108...學習
爲了搞清楚組件生命週期,構造一個父組件包含子組件而且重寫各生命週期函數的場景:this
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') );
結果以下:
剛開始
調用父組件的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 ofsetState()
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爲何這麼快速的緣由之一,也會在問題出現時知道錯在什麼地方,知道合理的解決方案。