網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
一 引沿
Fiber 架構(gòu)是React16中引入的新概念,目的就是解決大型 React 應(yīng)用卡頓,React在遍歷更新每一個(gè)節(jié)點(diǎn)的時(shí)候都不是用的真實(shí)DOM,都是采用虛擬DOM,所以可以理解成fiber就是React的虛擬DOM,更新Fiber的過(guò)程叫做調(diào)和,每一個(gè)fiber都可以作為一個(gè)執(zhí)行單元來(lái)處理,所以每一個(gè) fiber 可以根據(jù)自身的過(guò)期時(shí)間expirationTime,來(lái)判斷是否還有空間時(shí)間執(zhí)行更新,如果沒(méi)有時(shí)間更新,就要把主動(dòng)權(quán)交給瀏覽器去渲染,做一些動(dòng)畫(huà),重排( reflow ),重繪 repaints 之類(lèi)的事情,這樣就能給用戶(hù)感覺(jué)不是很卡。
二 什么是調(diào)和
調(diào)和是一種算法,就是React對(duì)比新老虛擬DOM的過(guò)程,以決定需要更新哪一部分。
三 什么是Filber
Fiber的目的是為了讓React充分利用調(diào)度,以便做到如下幾點(diǎn):
- 暫停工作,稍后再回來(lái)
- 優(yōu)先考慮不同類(lèi)型的工作
- 重用以前完成的工作
- 如果不再需要,則中止工作
為了實(shí)現(xiàn)上面的要求,我們需要把任務(wù)拆分成一個(gè)個(gè)可執(zhí)行的單元,這些可執(zhí)行的單元就叫做一個(gè)Fiber,一個(gè)Fiber就代表一個(gè)可執(zhí)行的單元。
一個(gè)Fiber就是一個(gè)普通的JS對(duì)象,包含一些組件的相關(guān)信息。
function FiberNode(){
this.tag = tag; // fiber 標(biāo)簽 證明是什么類(lèi)型fiber。
this.key = key; // key調(diào)和子節(jié)點(diǎn)時(shí)候用到。
this.type = null; // dom元素是對(duì)應(yīng)的元素類(lèi)型,比如div,組件指向組件對(duì)應(yīng)的類(lèi)或者函數(shù)。
this.stateNode = null; // 指向?qū)?yīng)的真實(shí)dom元素,類(lèi)組件指向組件實(shí)例,可以被ref獲取。
this.return = null; // 指向父級(jí)fiber
this.child = null; // 指向子級(jí)fiber
this.sibling = null; // 指向兄弟fiber
this.index = 0; // 索引
this.ref = null; // ref指向,ref函數(shù),或者ref對(duì)象。
this.pendingProps = pendingProps;// 在一次更新中,代表element創(chuàng)建
this.memoizedProps = null; // 記錄上一次更新完畢后的props
this.updateQueue = null; // 類(lèi)組件存放setState更新隊(duì)列,函數(shù)組件存放
this.memoizedState = null; // 類(lèi)組件保存state信息,函數(shù)組件保存hooks信息,dom元素為null
this.dependencies = null; // context或是時(shí)間的依賴(lài)項(xiàng)
this.mode = mode; //描述fiber樹(shù)的模式,比如 ConcurrentMode 模式
this.effectTag = NoEffect; // effect標(biāo)簽,用于收集effectList
this.nextEffect = null; // 指向下一個(gè)effect
this.firstEffect = null; // 第一個(gè)effect
this.lastEffect = null; // 最后一個(gè)effect
this.expirationTime = NoWork; // 通過(guò)不同過(guò)期時(shí)間,判斷任務(wù)是否過(guò)期, 在v17版本用lane表示。
this.alternate = null; //雙緩存樹(shù),指向緩存的fiber。更新階段,兩顆樹(shù)互相交替。
}
type 就是react的元素類(lèi)型
export const FunctionComponent = 0; // 對(duì)應(yīng)函數(shù)組件
export const ClassComponent = 1; // 對(duì)應(yīng)的類(lèi)組件
export const IndeterminateComponent = 2; // 初始化的時(shí)候不知道是函數(shù)組件還是類(lèi)組件
export const HostRoot = 3; // Root Fiber 可以理解為跟元素 , 通過(guò)reactDom.render()產(chǎn)生的根元素
export const HostPortal = 4; // 對(duì)應(yīng) ReactDOM.createPortal 產(chǎn)生的 Portal
export const HostComponent = 5; // dom 元素 比如 <div>
export const HostText = 6; // 文本節(jié)點(diǎn)
export const Fragment = 7; // 對(duì)應(yīng) <React.Fragment>
export const Mode = 8; // 對(duì)應(yīng) <React.StrictMode>
export const ContextConsumer = 9; // 對(duì)應(yīng) <Context.Consumer>
export const ContextProvider = 10; // 對(duì)應(yīng) <Context.Provider>
export const ForwardRef = 11; // 對(duì)應(yīng) React.ForwardRef
export const Profiler = 12; // 對(duì)應(yīng) <Profiler/ >
export const SuspenseComponent = 13; // 對(duì)應(yīng) <Suspense>
export const MemoComponent = 14; // 對(duì)應(yīng) React.memo 返回的組件
比如元素結(jié)構(gòu)如下:
export default class Parent extends React.Component{
render(){
return <div>
<h1>hello,world</h1>
<Child />
</div>
}
}
function Child() {
return <p>child</p>
}
對(duì)應(yīng)的Filber結(jié)構(gòu)如下:
有了上面的概念后我們就自己實(shí)現(xiàn)一個(gè)Fiber的更新機(jī)制
相關(guān)React實(shí)戰(zhàn)視頻講解:進(jìn)入學(xué)習(xí)
四 實(shí)現(xiàn)調(diào)和的過(guò)程
我們通過(guò)渲染一段jsx來(lái)說(shuō)明React的調(diào)和過(guò)程,也就是我們要手寫(xiě)實(shí)現(xiàn)ReactDOM.render()
const jsx = (
<div className="border">
<h1>hello</h1>
<a >React</a>
</div>
)
ReactDOM.render(
jsx,
document.getElementById('root')
);
1. 創(chuàng)建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階段
調(diào)和的核心是render和commit,本文不講調(diào)度過(guò)程,我們會(huì)簡(jiǎn)單的用requestIdleCallback代替React的調(diào)度過(guò)程。
ReactFiberWorkloop.js
let wipRoot = null // work in progress
let nextUnitOfwork = null // 下一個(gè)fiber節(jié)點(diǎn)
export function scheduleUpdateOnFiber(fiber) {
wipRoot = fiber
nextUnitOfwork = fiber
}
function workLoop(IdleDeadline) {
while(nextUnitOfwork && IdleDeadline.timeRemaining() > 0) {
nextUnitOfwork = performUnitOfWork(nextUnitOfwork)
}
}
function performUnitOfWork() {}
requestIdleCallback(workLoop)
每一個(gè) fiber 可以看作一個(gè)執(zhí)行的單元,在調(diào)和過(guò)程中,每一個(gè)發(fā)生更新的 fiber 都會(huì)作為一次 workInProgress 。那么 workLoop 就是執(zhí)行每一個(gè)單元的調(diào)度器,如果渲染沒(méi)有被中斷,那么 workLoop 會(huì)遍歷一遍 fiber 樹(shù)
performUnitOfWork 包括兩個(gè)階段:
- 是向下調(diào)和的過(guò)程,就是由 fiberRoot 按照 child 指針逐層向下調(diào)和,期間會(huì)執(zhí)行函數(shù)組件,實(shí)例類(lèi)組件,diff 調(diào)和子節(jié)點(diǎn)
- 是向上歸并的過(guò)程,如果有兄弟節(jié)點(diǎn),會(huì)返回 sibling兄弟,沒(méi)有返回 return 父級(jí),一直返回到 fiebrRoot
這么一上一下,構(gòu)成了整個(gè) fiber 樹(shù)的調(diào)和。
import { updateHostComponent } from './ReactFiberReconciler'
function performUnitOfWork(wip) {
// 1. 更新wip
const { type } = wip
if (isStr(type)) {
// type是string,更新普通元素節(jié)點(diǎn)
updateHostComponent(wip)
} else if (isFn(type)) {
// ...
}
// 2. 返回下一個(gè)要更新的任務(wù) 深度優(yōu)先遍歷
if (wip.child) {
return wip.child
}
let next = wip
while(next) {
if (next.sibling) {
return next.sibling
}
next = next.return
}
return null
}
根據(jù)type類(lèi)型區(qū)分是FunctionComponent/ClassComponent/HostComponent/...
本文中只處理HostComponent類(lèi)型,其他類(lèi)型的處理可以看文末的完整代碼鏈接。
ReactFiberReconciler.js
import { createFiber } from './createFiber'
export function updateHostComponent(wip) {
if (!wip.stateNode) {
wip.stateNode = document.createElement(wip.type);
updateNode(wip.stateNode, wip.props);
}
// 調(diào)和子節(jié)點(diǎn)
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, // 標(biāo)記節(jié)點(diǎn)類(lèi)型
key: vnode.key, // 標(biāo)記節(jié)點(diǎn)在當(dāng)前層級(jí)下的唯一性
props: vnode.props, // 屬性
stateNode: null, // 如果組件是原生標(biāo)簽則是dom節(jié)點(diǎn),如果是類(lèi)組件則是類(lèi)實(shí)例
child: null, // 第一個(gè)子節(jié)點(diǎn)
return: returnFiber,// 父節(jié)點(diǎn)
sibling: null, // 下一個(gè)兄弟節(jié)點(diǎn)
};
return newFiber;
}
至此已經(jīng)完成了render階段,下面是commit階段,commit階段就是依據(jù)Fiber結(jié)構(gòu)操作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. 提交子節(jié)點(diǎn)
commitWorker(wip.child);
// 3. 提交兄弟節(jié)點(diǎn)
commitWorker(wip.sibling);
}
五 總結(jié)
- Fiber結(jié)構(gòu),F(xiàn)iber的生成過(guò)程。
- 調(diào)和過(guò)程,以及 render 和 commit 兩大階段。
原文鏈接:https://www.cnblogs.com/xiaofeng123aa/p/16821323.html
相關(guān)推薦
- 2022-04-28 Python的命令行參數(shù)實(shí)例詳解_python
- 2022-09-10 golang之?dāng)?shù)組切片的具體用法_Golang
- 2023-11-23 寶塔數(shù)據(jù)庫(kù)過(guò)大導(dǎo)入失效解決方案
- 2023-11-26 String字符串類(lèi)
- 2022-07-18 Linux文件系統(tǒng)和日志分析
- 2022-09-27 如何在Python中利用matplotlib.pyplot畫(huà)出函數(shù)圖詳解_python
- 2022-04-08 Android如何使用GestureDetector進(jìn)行手勢(shì)檢測(cè)詳解_Android
- 2022-07-18 Uniapp中調(diào)整web-view的高度、獲取當(dāng)前的web-view頁(yè)面URL
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支