react源碼閱讀:總體認識

點擊這裏進入react原理專欄react

react源碼有一段時間了,寫篇文章總結一下,也但願能幫到你們。這是react原理系列的第一篇,主要講解一下react的基本架構,讓你們有一個總體的認識。面試

  1. 什麼是fiber,爲何要使用fiber
  2. react的三層架構模型:scheduler - reconciler - renderer
  3. react合成事件,當咱們點擊一個按鈕觸發click事件時發生了什麼
  4. react是如何觸發更新的(類組件的setState,函數組件的useState)
  5. react更新的render階段
  6. react更新的commit階段
  7. scheduler調度任務的流程

系列文章的react版本都爲17.0.2算法

什麼是fiber

爲了下降react源碼新手的困惑,咱們直接從數據結構上來理解什麼是fiber,每一個fiber就是一個對象,每一個組件(好比App組件),每一個真實的dom節點都會對應一個fiber對象,fiber對象有不少屬性,這裏先介紹以下幾個:數組

Fiber: {
  return: Fiber | null,
  child: Fiber | null,
  sibling: Fiber | null,
  stateNode: null
}
複製代碼

return屬性指向父級fiberchild屬性指向第一個子fibersibling執行本身的下一個兄弟fiber節點,stateNode執行這個fiber對象對應的組件或者真實的dom節點。瀏覽器

fiber.jpg

爲何要用fiber

在採用fiber架構以前,react採用遞歸的方式處理虛擬dom,致使react佔用主線程的時間過長,可能形成頁面假死。從前面的圖能夠看出,fiber架構下的應用fiber樹是一個相似鏈表結構的多叉樹,每一個fiber是一個獨立的工做單元,這就爲可中斷的更新提供了便利。考慮如下兩個狀況:緩存

  1. react更新過程當中,用戶點擊了按鈕
  2. 頁面動畫

針對狀況1,react但願可以在觸發點擊事件時,用戶可以儘快獲得響應,頁面動畫不會卡頓。在採用fiber架構時,每一個fiber都是一個獨立的單元,react可以在事件觸發時中斷fiber的更新,轉而處理用戶點擊事件,處理完畢後繼續進行fiber的更新。markdown

針對狀況2,react會以fiber爲基本單位進行更新,併爲每一個fiber的處理分配一個時間片,每次處理完一個fiber後,會檢查時間片是否到期,若是時間片到期,react就會將線程讓給瀏覽器,讓瀏覽器執行相應的頁面更新。數據結構

要想實現上面提到的兩種效果,react使用了fiber架構,並引入了scheduler模塊和優先級的概念。下面介紹一下scheduler架構

上面提到的兩種狀況,以後在concurrent mode下才會產生,使用ReactDOM.render建立的應用是沒有以上特性的,因此react17也被成爲一個過渡版本。dom

fiber的工做流程

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屬性。

雙緩存.jpg

react對於workInProgress的建立過程是很複雜的,這涉及到render階段的diff流程,以後會單獨講解。

scheduler

這裏先簡單介紹一下scheduler這個模塊,這個模塊提供的功能就是提供任務調度功能,而且爲任務提供優先級,實現高優先級任務打斷低優先級任務。這樣,react就能在更新時及時響應用戶觸發的事件。

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是同步仍是異步

一個經典面試題了: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原理

hooks是react16一個重要的更新,hooks讓咱們可以在函數組件中使用state,以後也會有一篇文章講解hooks是如何實現的。

react更新的兩大階段

react的一次更新包括兩個階段:render階段和commit階段

render階段:包括了更新的計算,fiber樹的diff算法,effectList的處理,dom節點的建立,類組件的生命週期等等

commit階段:包括類組件的生命週期,useEffect的調度,setState回調函數的執行,dom節點的插入等等

以後也會有專門的文章講解這兩個階段的流程。

因爲本身水平有限,並且react源碼內容繁多,結構複雜,再加上react17做爲一個過渡版本,有不少爲concurrent mode作鋪墊的代碼,因此閱讀react源碼是比較困難的,針對不少內容,筆者的理解也並不深入,所以暫時先不作介紹,將來有機會的話會再寫文章進行介紹。

最後但願你們在看完這個系列文章以後可以對react的總體運行流程有一個比較全面的認識。

推薦@Axizs大佬的react原理系列文章

相關文章
相關標籤/搜索