當前前端三大框架(vue、react和angular),除了vue之外,國內用得最多的就是react了,之前一直對其實作原理比較好奇,在花了很多時間深入研究了其原始碼實作后,本篇開始記錄一下
同樣的功能,用vue和react都能實作,相比較vue,react的學習門檻比較高,但是好處是它非常靈活,執行的效率更高(用到了很多新的技術),我個人覺得react的代碼和vue的代碼就像linux和windows,前者很注重javascript功底(類似linux的shell命令),后者有很多現成的html擴展標簽指令( v-for、v-if等,類似windows的圖形界面),所以如果一個人的js語言研究得比較深入,和一個剛剛入門js語言的程式員來說,用React實作了同樣的需求,敲出來的代碼質量會差很多的
react更加的純粹,這里的純粹指的是什么的,在react內部,jsx模板經babel轉化后是一個物件,所有的操作都是基于這個物件和其對應的fiber結構來操作的,
vue和react有許多共同點,比如:
- 都使用了虛擬DOM
- 更新時都使用了diff演算法進行了優化
react和vue的不同之處如下
writer by:大沙漠 QQ:22969969
| vue框架 | react框架 | |
| 實作原理 | 將模板轉化成一個render函式來執行 | 將每個節點轉化為fiber物件,最終形成一個fiber樹結構,來依次渲染 |
| 更新時的原理 | 通過ES5的Object.defineproperty()來動態觸發的 | 通過兩個fiber的對比來實作更新 |
| 是否支持雙向系結 | 支持,使用v-model實作 | 不支持,需要手動配置 |
| 指定模板的位置 | 使用el指定DOM節點,或者template設定模板,又或者直接通過render來回傳子節點VNode | 在函陣列件回傳jsx,或者class組件的render方法內回傳jsx |
| 模板的格式 | 使用html標簽格式,只是加了幾個vue內置的標簽或者屬性 | 用jsx指定模板,jsx類似JavaScript的擴展 |
| 更新程序中可以否被打斷 | 不能被打斷 | 某些流程可以被打斷,讓優先級更高的任務優先執行(例如瀏覽器渲染) |
舉個例子,看看實作同樣的功能,實作的效果是,在頁面上創建如下兩個DOM節點:
- p節點 ;默認顯示hello world字串
- button ;一個按鈕,點擊后會將helllo world變為hello vue或hello react
vue代碼如下:
<div id="app"><button @click="test">測驗</button><p>{{str}}</p></div> <!--Vue的寫法--> <script> new Vue({ el:"#app", data:{ str:"hello world" }, methods:{ test(){ this.str="hello vue"; } } }) </script>
效果如下:

react代碼如下啊:
<div id="root"></div> <!--React的寫法--> <script type="text/babel"> class App extends React.Component{ constructor(props){ super(props) } state = {str:"hello world"} test = () => this.setState(val=>val.str="hello React") render(){ return <div> <button onClick={this.test}>測驗</button> <p>{this.state.str}</p> </div> } } ReactDOM.render(<App/>,root) </script>
效果如下:

整個React應用從初始化到結束可以分為四個步驟:
- 創建更新 ;進行React的初始化作業,會將reactelement物件轉化為一個fiber物件,然后形成一個fiber樹的解構,之后的所有操作都是基于這個fiber樹來進行操作的
- 異步調度 ;React中有同步任務、異步任務、這個程序用于處理異步任務當中的邏輯,當瀏覽器渲染完后有有空余時間時開始執行這個調度,也就是第三步的render階段
- render階段 ;這個階段主要用于處理fiber樹的更新,所有的事件系結、css設定、class中大部分的生命周期函式、context、ref等任務,絕大多數可以被中斷的任務都會在 這個階段執行,這個階段是可以被打斷的,最侄訓形成一個effect鏈,每個元素是一個fiber物件,供第四步使用
- commit階段 ;主要遍歷第三步render階段生成的effect鏈,依次執行每個fiber元素上的收尾作業,這個階段是同步任務,不能被打斷的,因此大部分可以被中斷的任務都在第三步render階段執行完了
創建更新階段(初始化階段)
React用jsx來指定模板, jsx類似于JavaScript的擴展語法,經過babel轉化后會轉化為一個React.createElement函式,例子里的ReactDOM.render(<App/>,root)里的<App/>里的<App/>就是一個jxs物件,經過babel轉化后為:
React.createElement(React.createElement(App, null), null)
經過babel轉化后為如下函式:
React.createElement( "div", null, React.createElement("button", {onClick: (void 0).test}, "\u6D4B\u8BD5"), React.createElement("p", null, (void 0).state.str) );
React.createElement()函式執行后會回傳一個React Element物件,這里回傳的物件如下:
{
$$typeof:Symbol(react.element), //表示當前物件是一個ReactElement物件
type:App,
key:null,
ref:null,
props:{},
_owner:null
另外對于React里的類來說,render函式也需要回傳jsx,比如例子里的render回傳的如下
<div>
<button onClick={this.test}>測驗</button>
<p>{this.state.str}</p>
</div>
經過babel轉化后轉化為如下這個ReactElement物件
{ $$typeof: Symbol(react.element), //表示當前物件是一個ReactElement物件 type: "div", //組件的型別 key: null, //組件的key ref: null, //組件的ref props:[ //組件的key { $$typeof: Symbol(react.element), type: "button", key: null, ref: null, props:{ children:'測驗',onClick:f }, _owner:{...} },{ $$typeof: Symbol(react.element), type: "p", key: null, ref: null, props:{ children:"hello world", }, _owner:{...} } ], _owner:{...} //記錄負責創建此元素的組件,可以是class組件、function組件,或者null }
回到ReactDOM.render(<App/>,root),經過babel轉化為ReactDOM.render(React.createElement(App, null),null),由于運算式是由內向外執行的,因此該函式會先執行React.createElement()函式將<App/>轉化為一個ReactElement物件后,然后再執行ReactDOM.render()函式,這樣就開始了ReactDOM的邏輯了,
在ReactDOM.render里會經過一些列的初始化,創建一個fiber樹,大致如下:

一般我們執行ReactDOM.render(<App/>,root)時在第一階段都會生成這樣的資料結構,之后React所有的操作都是基于這個fiber樹進行更新的,每個ReactDOM.render()都會生成一個ReactRoot物件,ReactRoot、FiberRoot都有其作用,RootFiber就是根節點Fiber物件了
App類對應的fiber在render的時候就會把它的render函式回傳的jsx(也就是ReactElement物件)都轉化為fiber物件,具體后面再詳解
我們可以把每個fiber理解為一個DOM物件的映射,絕大多數DOM物件都有一個其對應的fiber物件的(文本節點是沒有fiber物件的,react直接通過nodeValue來設定了),
執行完創建更新階段,之后就進入了React的任務調度截斷了
任務調度階段
React中的任務分為同步和異步任務,如果是同步任務則會直接跳過這個階段進入render階段,對于異步任務來說,它會在瀏覽器渲染完之后利用空余的時間進行更新,
任務調度會利用requestAnimationFrame這個原生的API介面,requestAnimationFrame函式會在每次瀏覽器前執行的,執行的時候react利用了postMessage函式在任務佇列里插入了一個函式,這樣等到瀏覽器重繪完成后就會執行這個任務了,然后會觸發相應的邏輯,執行第三步驟render階段的相關操作,
render階段
render階段會依次遍歷在第一步生成的fiber結構,利用深度優先遍歷的演算法,先遍歷整個fiber樹最左側的fiber物件,然后再遍歷到右側的,最侄訓到最底層的根fiber物件,中間根據不同的組件型別做不同的處理,這個階段也是整個React最難理解的一個階段,因為有非常多的處理函式,還可以被高優先級的任務給打斷,例如瀏覽器重繪,自定義事件等,這個階段完成后在最頂層的fiber的firstEffect和lastEffect上設定一個鏈表,指向所有需要在commit階段進行處理的fiber
commit階段
這個階段比較簡單,就是遍歷第三步最后生成的Effect鏈,依次在每個fiber上執行收尾的作業,
React的流程大致如此,原始碼大概將近兩萬行,比較復雜,但是細分下去大致流程就是這樣的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/3990.html
標籤:HTML5
