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
- 調用函數組件, 或者class組件的
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後, 之前的tab
requestIdleCallback
處罰頻率變得很低
- 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節點保存了本次更新中該組件改變的狀態, 要執行的工作(被刪除/被插入到頁面中/被更新).
- React15, 數據保存在遞歸調用棧中, 稱爲
-
作爲架構:
// 指向父級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 fiber
和workInProcess fiber
通過alternate
鏈接wipFiber
構建完成後, 交給renderer
渲染, 渲染完成後,current
指向wipFiber
- 每次狀態更新, 都會產生新的
wipFiber樹
, 通過current
與wipFiber樹
的替換, 完成DOM
更新
- 屏幕上對應的fiber樹爲
mount時
- 首次執行
ReactDOM.render
創建FiberRootNode
(源碼中稱爲fiberRoot
), 和rootFiber
fiberRootNode
爲整個應用的根節點,rootFiber
爲當前render
函數中傳入組件的根節點current
指向的rootFiber
沒有任何子fiber節點,current fiber樹
爲空.
- render階段, 根據組件返回的jsx, 在內存中以此創建fiber節點, 並鏈接在一起構成fiber樹, 稱爲
workInProcess fiber樹
- 構建
workInProcess fiber
樹時, 會嘗試複用current fiber樹
中已有的fiber節點的內在屬性 - 首屏渲染時只有
rootFiber
存在對應的current fiber
.rootFiber.alternate => current rootFiber
- 構建
- 構建完成的
workInProcess fiber樹
, 在commit階段
渲染到頁面上- 此時dom樹, 就變爲了
workInProcess fiber樹
對應的樣子 fiberRootNode
更改current
指針, 到workInProcess fiber
樹workInProcess fiber樹
變爲了current fiber
樹
- 此時dom樹, 就變爲了
update時
- 我們點擊
p節點
, 觸發狀態更新, 開啓一次新的render階段
, 並構建一顆新的workInProcess fiber樹
workInProcess fiber
的創建, 可以複用current fiber樹
對應的數據節點- 是否複用的過程就是
diff
算法
workInProcess Fiber樹
在render
階段完成構建後進入到commit階段
渲染到頁面上- 渲染完畢後
workInProcess Fiber樹
變爲current Fiber樹
- 渲染完畢後
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