React技術揭密學習(一)

學習React技術揭祕

React15架構

  • Reconciler: 協調器 - render 找出有變化的組件 - diff
  • Renderer: 渲染器 - commit 渲染有變化的組件

15 - Reconciler

  • 觸發更新api
    • this.setState
    • this.forceUpdate
    • ReactDom.render
  • 更新流程:
    • 調用函數組件, 或者class組件的render, 轉換jsx爲虛擬dom
    • 虛擬dom與上次更新的dom對比 (diff1)
    • 通過對比找出本次更新中變化的dom (diff2)
    • 通過Renderer將渲染變化的dom

15 - Renderer

  • Reconciler通知Renderer進行更新
  • 也就是render後調用commit函數

缺點

  • mount調用mountComponent
  • update調用updateComponent
  • 兩個方法都是遞歸更新組件
  • 遞歸一旦開始就無法, 中途無法中斷, 超過16ms, 用戶感到卡頓
  • 使用異步可中斷的更新代替同步的更新
  • Reconciler和Renderer交替執行.
  • 第一個組件的Renderer工作完成後, 再交給第二個組件的Reconciler
  • 整個過程是同步的

React16

  • Scheduler: 調度任務的優先級, 高任務優先進入Reconciler -- schedule
  • Reconciler: 找出變化的組件 -- render
  • Renderer: 渲染變化的組件 -- commit

16 - Scheduler

  • 以瀏覽器是否有剩餘時間爲標準, 當瀏覽器有剩餘時間的時候, 通知我們.
  • 類似requestIdleCallback:
    • 瀏覽器兼容性
    • 觸發頻率不穩定: 例如切換tab後, 之前的tabrequestIdleCallback處罰頻率變得很低
  • React自己實現了一套scheduler
    • 瀏覽器空閒時間處罰回調
    • 多種調度優先級

16 - Reconciler

  • 遞歸調用變爲可中斷的循環過程
  • 每次循環判斷當前是否有剩餘時間
  • Reconciler和Renderer不再是交替工作
    • Scheduler將任務交給Reconciler
    • Reconciler將變化的虛擬dom打上標記 增/刪/更新
    • 所有組件的都完成了Scheduler和Reconciler後, 再交給Renderer
  • Renderer根據tag同步執行dom操作
  • Scheduler和Reconciler可能被打斷的原因:
    • 有其他高優先級任務需要更新
    • 當前幀沒有剩餘時間
    • 因爲是在內存中工作, 反覆被打斷也不會顯示在頁面中

Fiber心智模型

  • 代數效應: 函數式編程的一個概念. 將副作用從函數中抽離, 使函數關注點保持純粹
  • React中的: useState, useReducer, useRef
    • 不需要關注react中如何保存的
    • 只需要useState返回的是我們想要的state即可
  • 異步可中斷更新:
    • 更新在執行過程中可能會被打斷(時間分片用盡/更高任務插入)
    • 可以繼續執行時恢復之前執行中間態
  • Generator:
    • 具有傳染性
    • 執行的中間態上下關聯
    • 只適合處理單一優先級任務的中斷和執行
  • 纖程(fiber), 進程(process), 線程(thread), 協程(Coroutine).程序執行過程
  • 協程實現(Generator), 纖程實現(fiber), 代數效應在js中的實現
  • React Fiber
    • 內部實現一套狀態更新機制.
    • 支持任務不同優先級, 可中斷和恢復.
    • 恢復之後複用之前的中間狀態.
    • 每個任務單元爲ReactElement對應的Fiber節點

Fiber實現原理

  • React15使用採用遞歸的方式創建和更新虛擬dom, 遞歸的狀態不能中斷. 如果組件層次深的話, 遞歸會佔用線程很多的時間.

  • React16使用Fiber架構, 將遞歸的無法中斷的更新重構爲異步可中斷更新

  • 含義

    • React15, 數據保存在遞歸調用棧中, 稱爲stack reconciler, React16中的Reconciler基於fiber節點, 稱爲Reconciler Fiber
    • 靜態數據結構: 每個Fiber, 對應一個ReactElement, 保存了該組件的類型(FunctionComponent/ClassComponent/HostComponent), 對應的DOM節點信息
    • 動態工作單元: 每個Fiber節點保存了本次更新中該組件改變的狀態, 要執行的工作(被刪除/被插入到頁面中/被更新).
  • 作爲架構:

// 指向父級Fiber節點
/**
 * return 指的是當前節點執行完`completeWork`後, 返回的下一個工作單元
 */
this.return = null;
// 指向子Fiber節點
this.child = null;
// 指向右邊第一個兄弟Fiber節點
this.sibling = null;

function App() {
  return (
    <div>
      i am
      <span>KaSong</span>
    </div>
  )
}
  • 作爲靜態工作單元:
// Fiber對應組件的類型 Function/Class/Host...
this.tag = tag;
// key屬性
this.key = key;
// 大部分情況同type,某些情況不同,比如FunctionComponent使用React.memo包裹
this.elementType = null;
// 對於 FunctionComponent,指函數本身,對於ClassComponent,指class,對於HostComponent,指DOM節點tagName
this.type = null;
// Fiber對應的真實DOM節點
this.stateNode = null;
  • 作爲動態的工作單元:
// 保存本次更新造成的狀態改變相關信息
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;

this.mode = mode;

// 保存本次更新會造成的DOM操作
/**
 * 形成effectList鏈表, 遞歸調用所有需要改變的組件
 */
this.effectTag = NoEffect;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;

// 調度優先級相關
this.lanes = NoLanes;
this.childLanes = NoLanes;

Fiber工作原理

  • Fiber節點通過stateNode保存對應的dom
  • Fiber節點構成的fiber樹, 對應dom樹
  • 更新dom時, 使用雙緩存技術: 在內存中構建並直接替換的技術
    • 當前幀計算繪製量比較大的時候, 清除上一幀, 到繪製當前幀會有空白
    • 直接在內存繪製當前幀的動畫, 繪製完成後直接替換. 不會有空白
    • 對應着DOM樹的創建和更新
  • 雙緩存fiber樹
    • 屏幕上對應的fiber樹爲current fiber樹, 內存中對應的fiber樹爲workInProcess fiber樹
    • current fiberworkInProcess fiber 通過alternate鏈接
    • wipFiber構建完成後, 交給renderer渲染, 渲染完成後, current指向wipFiber
    • 每次狀態更新, 都會產生新的wipFiber樹, 通過currentwipFiber樹的替換, 完成DOM更新

mount時

  1. 首次執行ReactDOM.render創建FiberRootNode(源碼中稱爲fiberRoot), 和rootFiber
    1. fiberRootNode爲整個應用的根節點, rootFiber爲當前render函數中傳入組件的根節點
    2. current指向的rootFiber沒有任何子fiber節點, current fiber樹爲空.
  2. render階段, 根據組件返回的jsx, 在內存中以此創建fiber節點, 並鏈接在一起構成fiber樹, 稱爲workInProcess fiber樹
    1. 構建workInProcess fiber樹時, 會嘗試複用current fiber樹中已有的fiber節點的內在屬性
    2. 首屏渲染時只有rootFiber存在對應的current fiber. rootFiber.alternate => current rootFiber
  3. 構建完成的workInProcess fiber樹, 在commit階段渲染到頁面上
    1. 此時dom樹, 就變爲了workInProcess fiber樹對應的樣子
    2. fiberRootNode更改current指針, 到workInProcess fiber
    3. workInProcess fiber樹變爲了current fiber

update時

  1. 我們點擊p節點, 觸發狀態更新, 開啓一次新的render階段, 並構建一顆新的workInProcess fiber樹
    1. workInProcess fiber的創建, 可以複用current fiber樹對應的數據節點
    2. 是否複用的過程就是diff算法
  2. workInProcess Fiber樹render階段完成構建後進入到commit階段渲染到頁面上
    1. 渲染完畢後workInProcess Fiber樹變爲current Fiber樹
  3. Fiber樹在構建和替換的過程中, 完成dom的更新操作

JSX

  • jsx在編譯時, 會被babel編譯爲React.createElement
  • ReactElement工作後, 會返回一個ReactElement, 並使用$$typeof進行標記
  • 所有jsx運行後的返回結果都是ReactElement.
  • ClassComponent對應的Element中的type字段爲AppClass本身
  • FunctionComponent對應的Element中的type字段爲AppFunc本身
  • React通過ClassComponent.prototype.isReactComponent = {};判斷是否爲ClassComponent
  • mount時, Reconciler根據jsx描述的組件內容, 組成組件對應的fiber節點
  • update時, Reconciler將jsx與Fiber節點保存的數據, 進行對比, 生成組件對應的節點. 並根據對比結果爲Fiber節點打上tag
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章