網站首頁 編程語言 正文
一 引沿
Fiber 架構是React16中引入的新概念,目的就是解決大型 React 應用卡頓,React在遍歷更新每一個節點的時候都不是用的真實DOM,都是采用虛擬DOM,所以可以理解成fiber就是React的虛擬DOM,更新Fiber的過程叫做調和,每一個fiber都可以作為一個執行單元來處理,所以每一個 fiber 可以根據自身的過期時間expirationTime,來判斷是否還有空間時間執行更新,如果沒有時間更新,就要把主動權交給瀏覽器去渲染,做一些動畫,重排( reflow ),重繪 repaints 之類的事情,這樣就能給用戶感覺不是很卡。
二 什么是調和
調和是一種算法,就是React對比新老虛擬DOM的過程,以決定需要更新哪一部分。
三 什么是Filber
Fiber的目的是為了讓React充分利用調度,以便做到如下幾點:
- 暫停工作,稍后再回來
- 優先考慮不同類型的工作
- 重用以前完成的工作
- 如果不再需要,則中止工作
為了實現上面的要求,我們需要把任務拆分成一個個可執行的單元,這些可執行的單元就叫做一個Fiber,一個Fiber就代表一個可執行的單元。
一個Fiber就是一個普通的JS對象,包含一些組件的相關信息。
function FiberNode(){
this.tag = tag; // fiber 標簽 證明是什么類型fiber。
this.key = key; // key調和子節點時候用到。
this.type = null; // dom元素是對應的元素類型,比如div,組件指向組件對應的類或者函數。
this.stateNode = null; // 指向對應的真實dom元素,類組件指向組件實例,可以被ref獲取。
this.return = null; // 指向父級fiber
this.child = null; // 指向子級fiber
this.sibling = null; // 指向兄弟fiber
this.index = 0; // 索引
this.ref = null; // ref指向,ref函數,或者ref對象。
this.pendingProps = pendingProps;// 在一次更新中,代表element創建
this.memoizedProps = null; // 記錄上一次更新完畢后的props
this.updateQueue = null; // 類組件存放setState更新隊列,函數組件存放
this.memoizedState = null; // 類組件保存state信息,函數組件保存hooks信息,dom元素為null
this.dependencies = null; // context或是時間的依賴項
this.mode = mode; //描述fiber樹的模式,比如 ConcurrentMode 模式
this.effectTag = NoEffect; // effect標簽,用于收集effectList
this.nextEffect = null; // 指向下一個effect
this.firstEffect = null; // 第一個effect
this.lastEffect = null; // 最后一個effect
this.expirationTime = NoWork; // 通過不同過期時間,判斷任務是否過期, 在v17版本用lane表示。
this.alternate = null; //雙緩存樹,指向緩存的fiber。更新階段,兩顆樹互相交替。
}
type 就是react的元素類型
export const FunctionComponent = 0; // 對應函數組件
export const ClassComponent = 1; // 對應的類組件
export const IndeterminateComponent = 2; // 初始化的時候不知道是函數組件還是類組件
export const HostRoot = 3; // Root Fiber 可以理解為跟元素 , 通過reactDom.render()產生的根元素
export const HostPortal = 4; // 對應 ReactDOM.createPortal 產生的 Portal
export const HostComponent = 5; // dom 元素 比如 <div>
export const HostText = 6; // 文本節點
export const Fragment = 7; // 對應 <React.Fragment>
export const Mode = 8; // 對應 <React.StrictMode>
export const ContextConsumer = 9; // 對應 <Context.Consumer>
export const ContextProvider = 10; // 對應 <Context.Provider>
export const ForwardRef = 11; // 對應 React.ForwardRef
export const Profiler = 12; // 對應 <Profiler/ >
export const SuspenseComponent = 13; // 對應 <Suspense>
export const MemoComponent = 14; // 對應 React.memo 返回的組件
比如元素結構如下:
export default class Parent extends React.Component{
render(){
return <div>
<h1>hello,world</h1>
<Child />
</div>
}
}
function Child() {
return <p>child</p>
}
對應的Filber結構如下:
有了上面的概念后我們就自己實現一個Fiber的更新機制
相關React實戰視頻講解:進入學習
四 實現調和的過程
我們通過渲染一段jsx來說明React的調和過程,也就是我們要手寫實現ReactDOM.render()
const jsx = (
<div className="border">
<h1>hello</h1>
<a >React</a>
</div>
)
ReactDOM.render(
jsx,
document.getElementById('root')
);
1. 創建FiberRoot
react-dom.js
function createFiberRoot(element, container){
return {
type: container.nodeName.toLocaleLowerCase(),
props: { children: element },
stateNode: container
}
}
function render(element, container) {
const FibreRoot = createFiberRoot(element, container)
scheduleUpdateOnFiber(FibreRoot)
}
export default { render }
2. render階段
調和的核心是render和commit,本文不講調度過程,我們會簡單的用requestIdleCallback代替React的調度過程。
ReactFiberWorkloop.js
let wipRoot = null // work in progress
let nextUnitOfwork = null // 下一個fiber節點
export function scheduleUpdateOnFiber(fiber) {
wipRoot = fiber
nextUnitOfwork = fiber
}
function workLoop(IdleDeadline) {
while(nextUnitOfwork && IdleDeadline.timeRemaining() > 0) {
nextUnitOfwork = performUnitOfWork(nextUnitOfwork)
}
}
function performUnitOfWork() {}
requestIdleCallback(workLoop)
每一個 fiber 可以看作一個執行的單元,在調和過程中,每一個發生更新的 fiber 都會作為一次 workInProgress 。那么 workLoop 就是執行每一個單元的調度器,如果渲染沒有被中斷,那么 workLoop 會遍歷一遍 fiber 樹
performUnitOfWork 包括兩個階段:
- 是向下調和的過程,就是由 fiberRoot 按照 child 指針逐層向下調和,期間會執行函數組件,實例類組件,diff 調和子節點
- 是向上歸并的過程,如果有兄弟節點,會返回 sibling兄弟,沒有返回 return 父級,一直返回到 fiebrRoot
這么一上一下,構成了整個 fiber 樹的調和。
import { updateHostComponent } from './ReactFiberReconciler'
function performUnitOfWork(wip) {
// 1. 更新wip
const { type } = wip
if (isStr(type)) {
// type是string,更新普通元素節點
updateHostComponent(wip)
} else if (isFn(type)) {
// ...
}
// 2. 返回下一個要更新的任務 深度優先遍歷
if (wip.child) {
return wip.child
}
let next = wip
while(next) {
if (next.sibling) {
return next.sibling
}
next = next.return
}
return null
}
根據type類型區分是FunctionComponent/ClassComponent/HostComponent/...
本文中只處理HostComponent類型,其他類型的處理可以看文末的完整代碼鏈接。
ReactFiberReconciler.js
import { createFiber } from './createFiber'
export function updateHostComponent(wip) {
if (!wip.stateNode) {
wip.stateNode = document.createElement(wip.type);
updateNode(wip.stateNode, wip.props);
}
// 調和子節點
reconcileChildren(wip, wip.props.children);
}
function reconcileChildren(returnFiber, children) {
if (isStr(children)) {
return
}
const newChildren = isArray(children) ? children : [children];
let previousNewFiber = null
for(let i = 0; i < newChildren.length; i++) {
const newChild = newChildren[i];
const newFiber = createFiber(newChild, returnFiber)
if (previousNewFiber === null) {
returnFiber.child = newFiber
} else {
previousNewFiber.sibling = newFiber
}
previousNewFiber = newFiber
}
}
function updateNode(node, nextVal) {
Object.keys(nextVal).forEach((k) => {
if (k === "children") {
if (isStringOrNumber(nextVal[k])) {
node.textContent = nextVal[k];
}
} else {
node[k] = nextVal[k];
}
});
}
createFiber.js
export function createFiber(vnode, returnFiber) {
const newFiber = {
type: vnode.type, // 標記節點類型
key: vnode.key, // 標記節點在當前層級下的唯一性
props: vnode.props, // 屬性
stateNode: null, // 如果組件是原生標簽則是dom節點,如果是類組件則是類實例
child: null, // 第一個子節點
return: returnFiber,// 父節點
sibling: null, // 下一個兄弟節點
};
return newFiber;
}
至此已經完成了render階段,下面是commit階段,commit階段就是依據Fiber結構操作DOM
function workLoop(IdleDeadline) {
while(nextUnitOfwork && IdleDeadline.timeRemaining() > 0) {
nextUnitOfwork = performUnitOfWork(nextUnitOfwork)
}
// commit
if (!nextUnitOfwork && wipRoot) {
commitRoot();
}
}
function commitRoot() {
commitWorker(wipRoot.child)
wipRoot = null;
}
function commitWorker(wip) {
if (!wip) {
return
}
// 1. 提交自己
const { stateNode } = wip
let parentNode = wip.return.stateNode
if (stateNode) {
parentNode.appendChild(stateNode);
}
// 2. 提交子節點
commitWorker(wip.child);
// 3. 提交兄弟節點
commitWorker(wip.sibling);
}
五 總結
- Fiber結構,Fiber的生成過程。
- 調和過程,以及 render 和 commit 兩大階段。
原文鏈接:https://www.cnblogs.com/xiaofeng123aa/p/16821323.html
相關推薦
- 2022-06-19 python?rsa和Crypto.PublicKey.RSA?模塊詳解_python
- 2022-05-22 基于jQuery排序及應用實現Tab欄特效_jquery
- 2023-02-25 pytest-fixture簡介及其用法講解_python
- 2023-10-16 el-popover在原生table中,彈出多個以及內部取消按鈕無效問題
- 2022-09-25 BeanFactory與ApplicationContext的區別
- 2022-06-15 golang如何用type-switch判斷interface變量的實際存儲類型_Golang
- 2023-03-23 Python中win32com模塊的使用_python
- 2022-04-16 C語言線性表之雙鏈表詳解_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支