對React一些原理的理解

原文地址在個人博客, 轉載請註明出處,謝謝!

前言

隨着項目開發的深刻,不可避免了遇到了一些問題。剛開始出現問題時很懵,不知道該怎麼解決,緣由就是對React的原理理解的不夠透徹,不知道問題出在哪。在解決問題的過程當中,也逐漸深刻了解了React的一些原理,這篇文章就來分享一下我對React一些原理的理解。javascript

注意 這篇文章並非教程,只是我對React原理的一些我的理解,歡迎與我一塊兒討論。文章不對的地方,還請讀者費心指出^-^

概述

本文是《使用React技術棧的一些收穫》系列文章的第二篇(第一篇在這裏,介紹如何開始構建React大型項目),簡單介紹了React一些原理,包括React合成事件系統、組件的生命週期以及setState()html

React合成事件系統

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')
);

結果以下:
剛開始
Alt text

調用父組件的setState後:
Alt text

在Jsbin上試試看

有一張圖能說明這之間的流程(圖片來源):
Alt text

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上的代碼上注意到沒有,調用setStateconsole.log(this.state.score)輸出仍然爲0,也就是this.state並未改變,而且只render了一次。

總結

學習一個框架或者工具,我以爲應該瞭解如下幾點:

  1. 它是什麼?能作什麼?
  2. 它存在的理由是什麼?解決了什麼樣的問題、知足了什麼樣的需求?
  3. 它的適用場景是什麼?優缺點是什麼?
  4. 它怎麼用?最佳實踐是什麼?
  5. 它的原理是什麼?
  6. ...

經過對React一些原理的簡單瞭解,就懂得了React爲何這麼快速的緣由之一,也會在問題出現時知道錯在什麼地方,知道合理的解決方案。

相關文章
相關標籤/搜索