點擊這裏進入react原理專欄react
看react
源碼有一段時間了,寫篇文章總結一下,也但願能幫到你們。這是react原理系列的第一篇,主要講解一下react
的基本架構,讓你們有一個總體的認識。面試
fiber
,爲何要使用fiber
react
的三層架構模型:scheduler - reconciler - renderer
react
合成事件,當咱們點擊一個按鈕觸發click
事件時發生了什麼react
是如何觸發更新的(類組件的setState
,函數組件的useState
)react
更新的render
階段react
更新的commit
階段scheduler
調度任務的流程系列文章的react版本都爲17.0.2算法
爲了下降react
源碼新手的困惑,咱們直接從數據結構上來理解什麼是fiber
,每一個fiber
就是一個對象,每一個組件(好比App
組件),每一個真實的dom
節點都會對應一個fiber
對象,fiber
對象有不少屬性,這裏先介紹以下幾個:數組
Fiber: {
return: Fiber | null,
child: Fiber | null,
sibling: Fiber | null,
stateNode: null
}
複製代碼
return
屬性指向父級fiber
,child
屬性指向第一個子fiber
,sibling
執行本身的下一個兄弟fiber
節點,stateNode
執行這個fiber
對象對應的組件或者真實的dom
節點。瀏覽器
在採用fiber
架構以前,react
採用遞歸的方式處理虛擬dom,致使react
佔用主線程的時間過長,可能形成頁面假死。從前面的圖能夠看出,fiber
架構下的應用fiber
樹是一個相似鏈表結構的多叉樹,每一個fiber
是一個獨立的工做單元,這就爲可中斷的更新提供了便利。考慮如下兩個狀況:緩存
react
更新過程當中,用戶點擊了按鈕針對狀況1,react
但願可以在觸發點擊事件時,用戶可以儘快獲得響應,頁面動畫不會卡頓。在採用fiber
架構時,每一個fiber
都是一個獨立的單元,react
可以在事件觸發時中斷fiber
的更新,轉而處理用戶點擊事件,處理完畢後繼續進行fiber
的更新。markdown
針對狀況2,react
會以fiber
爲基本單位進行更新,併爲每一個fiber
的處理分配一個時間片,每次處理完一個fiber
後,會檢查時間片是否到期,若是時間片到期,react
就會將線程讓給瀏覽器,讓瀏覽器執行相應的頁面更新。數據結構
要想實現上面提到的兩種效果,react
使用了fiber
架構,並引入了scheduler
模塊和優先級的概念。下面介紹一下scheduler
架構
上面提到的兩種狀況,以後在
concurrent mode
下才會產生,使用ReactDOM.render
建立的應用是沒有以上特性的,因此react17也被成爲一個過渡版本。dom
react
採用了雙緩存機制來保存fiber
樹,即內存中存在兩棵fiber
樹,稱爲current
樹和workInProgress
樹。current
樹表示當前正在頁面中顯示的fiber
節點,每次觸發更新時,react
會根據current
樹,並結合觸發的更新,採用深度優先遍歷的方式建立workInProgress
樹。當workInProgress
建立完畢後,react
會切換兩棵樹,從而完成應用的更新。
在具體實現上,react
中有一個fiberRoot
節點,兩個rootFiber
節點。fiberRoot
表示整個應用的根節點,每次觸發更新時,都會從這個節點開始向下遍歷。fiberRoot
有一個current
屬性,指向current
樹。每次應用更新結束後,切換current
指針的指向,就完成了頁面的更新。
兩個rootFiber
節點就是current
樹和workInProgress
樹的根節點,而且每一個current
樹節點和對應的workInProgress
樹節點都會有一個指針指向對方:alternate
屬性。
react
對於workInProgress
的建立過程是很複雜的,這涉及到render
階段的diff
流程,以後會單獨講解。
這裏先簡單介紹一下scheduler
這個模塊,這個模塊提供的功能就是提供任務調度功能,而且爲任務提供優先級,實現高優先級任務打斷低優先級任務。這樣,react
就能在更新時及時響應用戶觸發的事件。
看下面的代碼,你能說出輸出結果嗎?
class App extends React.Component {
componentDidMount() {
outer.addEventListener('click', function() {
console.log('native event click outer')
})
inner.addEventListener('click', function() {
console.log('native event click inner')
})
}
handleClickInner = () => {
console.log('react event click inner')
}
handleClickOuter = () => {
console.log('react event click outer')
}
render() {
return (
<div className='outer' onClick={this.handleClickInner}> <div className='inner' onClick={this.handleClickOuter}></div> </div>
)
}
}
複製代碼
這裏就涉及到了react
的合成事件機制,以後會有專門的文章進行介紹。
一個經典面試題了:setState
究竟是同步仍是異步的,好比下面這段代碼的輸出結果
// 異步
handleClick = () => {
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
console.log(this.state.count)
}
// 同步
handleClick = () => {
setTimeout(() => {
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
console.log(this.state.count)
})
}
複製代碼
以後也會有專門的文章講解react
是如何建立更新,如何處理更新的。
hooks
是react16一個重要的更新,hooks
讓咱們可以在函數組件中使用state
,以後也會有一篇文章講解hooks
是如何實現的。
react
的一次更新包括兩個階段:render
階段和commit
階段
render
階段:包括了更新的計算,fiber
樹的diff算法,effectList
的處理,dom節點的建立,類組件的生命週期等等
commit
階段:包括類組件的生命週期,useEffect
的調度,setState
回調函數的執行,dom節點的插入等等
以後也會有專門的文章講解這兩個階段的流程。
因爲本身水平有限,並且react
源碼內容繁多,結構複雜,再加上react17做爲一個過渡版本,有不少爲concurrent mode
作鋪墊的代碼,因此閱讀react
源碼是比較困難的,針對不少內容,筆者的理解也並不深入,所以暫時先不作介紹,將來有機會的話會再寫文章進行介紹。
最後但願你們在看完這個系列文章以後可以對react的總體運行流程有一個比較全面的認識。
推薦@Axizs大佬的react原理系列文章