對React一些原理的理解
原文地址在我的博客, 轉載請註明出處,謝謝!
前言
隨著項目開發的深入,不可避免了遇到了一些問題。剛開始出現問題時很懵,不知道該怎麼解決,原因就是對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> )})
Thats OK,React幫你實現了事件委託。我之前因為不了解React合成事件系統,還顯示的使用了事件委託,現在看來是多此一舉的。
由於React合成事件系統模擬事件冒泡的方法是構建一個自己及父組件隊列,因此也帶來一個問題,合成事件不能阻止原生事件,原生事件可以阻止合成事件。用 event.stopPropagation() 並不能停止事件傳播,應該使用 event.preventDefault()。
如果你想詳細了解React合成事件系統,移步http://blog.csdn.net/u013510838/article/details/61224760
組件的生命周期(以父子組件為例)
為了搞清楚組件生命周期,構造一個父組件包含子組件並且重寫各生命周期函數的場景:
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後:
在Jsbin上試試看有一張圖能說明這之間的流程(圖片來源):
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為什麼這麼快速的原因之一,也會在問題出現時知道錯在什麼地方,知道合理的解決方案。
推薦閱讀:
※node中的精髓Stream(流)
※html5全部標籤
※前端系列教學(入門篇) - CSS初階(1)
※免費直播 | 2018,你最需要的前端學習指南&求職指南!飢人谷
※飢人谷的「教練模式」是什麼?