網(wǎng)站首頁 編程語言 正文
一 引沿
Fiber 架構(gòu)是React16中引入的新概念,目的就是解決大型 React 應(yīng)用卡頓,React在遍歷更新每一個(gè)節(jié)點(diǎn)的時(shí)候都不是用的真實(shí)DOM,都是采用虛擬DOM,所以可以理解成fiber就是React的虛擬DOM,更新Fiber的過程叫做調(diào)和,每一個(gè)fiber都可以作為一個(gè)執(zhí)行單元來處理,所以每一個(gè) fiber 可以根據(jù)自身的過期時(shí)間expirationTime,來判斷是否還有空間時(shí)間執(zhí)行更新,如果沒有時(shí)間更新,就要把主動(dòng)權(quán)交給瀏覽器去渲染,做一些動(dòng)畫,重排( reflow ),重繪 repaints 之類的事情,這樣就能給用戶感覺不是很卡。
二 什么是調(diào)和
調(diào)和是一種算法,就是React對(duì)比新老虛擬DOM的過程,以決定需要更新哪一部分。
三 什么是Filber
Fiber的目的是為了讓React充分利用調(diào)度,以便做到如下幾點(diǎn):
- 暫停工作,稍后再回來
- 優(yōu)先考慮不同類型的工作
- 重用以前完成的工作
- 如果不再需要,則中止工作
為了實(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)簽 證明是什么類型fiber。
this.key = key; // key調(diào)和子節(jié)點(diǎn)時(shí)候用到。
this.type = null; // dom元素是對(duì)應(yīng)的元素類型,比如div,組件指向組件對(duì)應(yīng)的類或者函數(shù)。
this.stateNode = null; // 指向?qū)?yīng)的真實(shí)dom元素,類組件指向組件實(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; // 類組件存放setState更新隊(duì)列,函數(shù)組件存放
this.memoizedState = null; // 類組件保存state信息,函數(shù)組件保存hooks信息,dom元素為null
this.dependencies = null; // context或是時(shí)間的依賴項(xiàng)
this.mode = mode; //描述fiber樹的模式,比如 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; // 通過不同過期時(shí)間,判斷任務(wù)是否過期, 在v17版本用lane表示。
this.alternate = null; //雙緩存樹,指向緩存的fiber。更新階段,兩顆樹互相交替。
}
type 就是react的元素類型
export const FunctionComponent = 0; // 對(duì)應(yīng)函數(shù)組件
export const ClassComponent = 1; // 對(duì)應(yīng)的類組件
export const IndeterminateComponent = 2; // 初始化的時(shí)候不知道是函數(shù)組件還是類組件
export const HostRoot = 3; // Root Fiber 可以理解為跟元素 , 通過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ī)制
四 實(shí)現(xiàn)調(diào)和的過程
我們通過渲染一段jsx來說明React的調(diào)和過程,也就是我們要手寫實(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 }
參考React實(shí)戰(zhàn)視頻講解:進(jìn)入學(xué)習(xí)
2. render階段
調(diào)和的核心是render和commit,本文不講調(diào)度過程,我們會(huì)簡單的用requestIdleCallback代替React的調(diào)度過程。
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)和過程中,每一個(gè)發(fā)生更新的 fiber 都會(huì)作為一次 workInProgress 。那么 workLoop 就是執(zhí)行每一個(gè)單元的調(diào)度器,如果渲染沒有被中斷,那么 workLoop 會(huì)遍歷一遍 fiber 樹
performUnitOfWork 包括兩個(gè)階段:
- 是向下調(diào)和的過程,就是由 fiberRoot 按照 child 指針逐層向下調(diào)和,期間會(huì)執(zhí)行函數(shù)組件,實(shí)例類組件,diff 調(diào)和子節(jié)點(diǎn)
- 是向上歸并的過程,如果有兄弟節(jié)點(diǎn),會(huì)返回 sibling兄弟,沒有返回 return 父級(jí),一直返回到 fiebrRoot
這么一上一下,構(gòu)成了整個(gè) fiber 樹的調(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類型區(qū)分是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);
}
// 調(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)類型
key: vnode.key, // 標(biāo)記節(jié)點(diǎn)在當(dāng)前層級(jí)下的唯一性
props: vnode.props, // 屬性
stateNode: null, // 如果組件是原生標(biāo)簽則是dom節(jié)點(diǎn),如果是類組件則是類實(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的生成過程。
- 調(diào)和過程,以及 render 和 commit 兩大階段。
原文鏈接:https://blog.csdn.net/xiaofeng123aazz/article/details/127878600
相關(guān)推薦
- 2022-07-03 DatePicker動(dòng)態(tài)設(shè)置picker-options 中的disabledDate屬性操作;
- 2022-07-08 Pytest如何使用mark的方法_python
- 2023-02-02 C語言多文件編程問題解析_C 語言
- 2022-09-12 nginx訪問報(bào)403錯(cuò)誤的幾種情況詳解_nginx
- 2022-08-15 linux下一些c語言的知識(shí)
- 2023-01-13 解決安裝torch后,torch.cuda.is_available()結(jié)果為false的問題_py
- 2022-04-09 Python調(diào)用win10toast框架實(shí)現(xiàn)定時(shí)調(diào)起系統(tǒng)通知_python
- 2022-10-10 conda創(chuàng)建環(huán)境、安裝包、刪除環(huán)境步驟詳細(xì)記錄_python
- 最近更新
-
- 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)證過濾器
- Spring Security概述快速入門
- 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)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支