對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合成事件系統,移步blog.csdn.net/u01351083

組件的生命周期(以父子組件為例)

為了搞清楚組件生命周期,構造一個父組件包含子組件並且重寫各生命周期函數的場景:

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了一次。

總結

學習一個框架或者工具,我覺得應該了解以下幾點:

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

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


推薦閱讀:

node中的精髓Stream(流)
html5全部標籤
前端系列教學(入門篇) - CSS初階(1)
免費直播 | 2018,你最需要的前端學習指南&求職指南!飢人谷
飢人谷的「教練模式」是什麼?

TAG:React | 前端框架 | 前端入門 |