React中常見的TypeScript定義實戰(zhàn)教程
一 引沿
Fiber 架構(gòu)是React16中引入的新概念,目的就是解決大型 React 應(yīng)用卡頓,React在遍歷更新每一個節(jié)點的時候都不是用的真實DOM,都是采用虛擬DOM,所以可以理解成fiber就是React的虛擬DOM,更新Fiber的過程叫做調(diào)和,每一個fiber都可以作為一個執(zhí)行單元來處理,所以每一個 fiber 可以根據(jù)自身的過期時間expirationTime,來判斷是否還有空間時間執(zhí)行更新,如果沒有時間更新,就要把主動權(quán)交給瀏覽器去渲染,做一些動畫,重排( reflow ),重繪 repaints 之類的事情,這樣就能給用戶感覺不是很卡。
二 什么是調(diào)和
調(diào)和是一種算法,就是React對比新老虛擬DOM的過程,以決定需要更新哪一部分。
三 什么是Filber
Fiber的目的是為了讓React充分利用調(diào)度,以便做到如下幾點:
- 暫停工作,稍后再回來
- 優(yōu)先考慮不同類型的工作
- 重用以前完成的工作
- 如果不再需要,則中止工作
為了實現(xiàn)上面的要求,我們需要把任務(wù)拆分成一個個可執(zhí)行的單元,這些可執(zhí)行的單元就叫做一個Fiber,一個Fiber就代表一個可執(zhí)行的單元。
一個Fiber就是一個普通的JS對象,包含一些組件的相關(guān)信息。
function FiberNode(){ this.tag = tag; // fiber 標(biāo)簽 證明是什么類型fiber。 this.key = key; // key調(diào)和子節(jié)點時候用到。 this.type = null; // dom元素是對應(yīng)的元素類型,比如div,組件指向組件對應(yīng)的類或者函數(shù)。 this.stateNode = null; // 指向?qū)?yīng)的真實dom元素,類組件指向組件實例,可以被ref獲取。 this.return = null; // 指向父級fiber this.child = null; // 指向子級fiber this.sibling = null; // 指向兄弟fiber this.index = 0; // 索引 this.ref = null; // ref指向,ref函數(shù),或者ref對象。 this.pendingProps = pendingProps;// 在一次更新中,代表element創(chuàng)建 this.memoizedProps = null; // 記錄上一次更新完畢后的props this.updateQueue = null; // 類組件存放setState更新隊列,函數(shù)組件存放 this.memoizedState = null; // 類組件保存state信息,函數(shù)組件保存hooks信息,dom元素為null this.dependencies = null; // context或是時間的依賴項 this.mode = mode; //描述fiber樹的模式,比如 ConcurrentMode 模式 this.effectTag = NoEffect; // effect標(biāo)簽,用于收集effectList this.nextEffect = null; // 指向下一個effect this.firstEffect = null; // 第一個effect this.lastEffect = null; // 最后一個effect this.expirationTime = NoWork; // 通過不同過期時間,判斷任務(wù)是否過期, 在v17版本用lane表示。 this.alternate = null; //雙緩存樹,指向緩存的fiber。更新階段,兩顆樹互相交替。 }
type 就是react的元素類型
export const FunctionComponent = 0; // 對應(yīng)函數(shù)組件 export const ClassComponent = 1; // 對應(yīng)的類組件 export const IndeterminateComponent = 2; // 初始化的時候不知道是函數(shù)組件還是類組件 export const HostRoot = 3; // Root Fiber 可以理解為跟元素 , 通過reactDom.render()產(chǎn)生的根元素 export const HostPortal = 4; // 對應(yīng) ReactDOM.createPortal 產(chǎn)生的 Portal export const HostComponent = 5; // dom 元素 比如 <div> export const HostText = 6; // 文本節(jié)點 export const Fragment = 7; // 對應(yīng) <React.Fragment> export const Mode = 8; // 對應(yīng) <React.StrictMode> export const ContextConsumer = 9; // 對應(yīng) <Context.Consumer> export const ContextProvider = 10; // 對應(yīng) <Context.Provider> export const ForwardRef = 11; // 對應(yīng) React.ForwardRef export const Profiler = 12; // 對應(yīng) <Profiler/ > export const SuspenseComponent = 13; // 對應(yīng) <Suspense> export const MemoComponent = 14; // 對應(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> }
對應(yīng)的Filber結(jié)構(gòu)如下:
有了上面的概念后我們就自己實現(xiàn)一個Fiber的更新機(jī)制
相關(guān)React實戰(zhàn)視頻講解:進(jìn)入學(xué)習(xí)
四 實現(xiàn)調(diào)和的過程
我們通過渲染一段jsx來說明React的調(diào)和過程,也就是我們要手寫實現(xiàn)ReactDOM.render()
const jsx = ( <div className="border"> <h1>hello</h1> <a rel="external nofollow" >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)度過程,我們會簡單的用requestIdleCallback代替React的調(diào)度過程。
ReactFiberWorkloop.js
let wipRoot = null // work in progress let nextUnitOfwork = null // 下一個fiber節(jié)點 export function scheduleUpdateOnFiber(fiber) { wipRoot = fiber nextUnitOfwork = fiber } function workLoop(IdleDeadline) { while(nextUnitOfwork && IdleDeadline.timeRemaining() > 0) { nextUnitOfwork = performUnitOfWork(nextUnitOfwork) } } function performUnitOfWork() {} requestIdleCallback(workLoop)
每一個 fiber 可以看作一個執(zhí)行的單元,在調(diào)和過程中,每一個發(fā)生更新的 fiber 都會作為一次 workInProgress 。那么 workLoop 就是執(zhí)行每一個單元的調(diào)度器,如果渲染沒有被中斷,那么 workLoop 會遍歷一遍 fiber 樹
performUnitOfWork 包括兩個階段:
- 是向下調(diào)和的過程,就是由 fiberRoot 按照 child 指針逐層向下調(diào)和,期間會執(zhí)行函數(shù)組件,實例類組件,diff 調(diào)和子節(jié)點
- 是向上歸并的過程,如果有兄弟節(jié)點,會返回 sibling兄弟,沒有返回 return 父級,一直返回到 fiebrRoot
這么一上一下,構(gòu)成了整個 fiber 樹的調(diào)和。
import { updateHostComponent } from './ReactFiberReconciler' function performUnitOfWork(wip) { // 1. 更新wip const { type } = wip if (isStr(type)) { // type是string,更新普通元素節(jié)點 updateHostComponent(wip) } else if (isFn(type)) { // ... } // 2. 返回下一個要更新的任務(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é)點 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é)點類型 key: vnode.key, // 標(biāo)記節(jié)點在當(dāng)前層級下的唯一性 props: vnode.props, // 屬性 stateNode: null, // 如果組件是原生標(biāo)簽則是dom節(jié)點,如果是類組件則是類實例 child: null, // 第一個子節(jié)點 return: returnFiber,// 父節(jié)點 sibling: null, // 下一個兄弟節(jié)點 }; 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é)點 commitWorker(wip.child); // 3. 提交兄弟節(jié)點 commitWorker(wip.sibling); }
五 總結(jié)
- Fiber結(jié)構(gòu),F(xiàn)iber的生成過程。
- 調(diào)和過程,以及 render 和 commit 兩大階段。
到此這篇關(guān)于React中常見的TypeScript定義實戰(zhàn)的文章就介紹到這了,更多相關(guān)React TypeScript定義內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用react-beautiful-dnd實現(xiàn)列表間拖拽踩坑
相比于react-dnd,react-beautiful-dnd更適用于列表之間拖拽的場景,本文主要介紹了使用react-beautiful-dnd實現(xiàn)列表間拖拽踩坑,感興趣的可以了解一下2021-05-05react版模擬亞馬遜人機(jī)交互菜單的實現(xiàn)
本文主要介紹了react版模擬亞馬遜人機(jī)交互菜單的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02react中如何使用定義數(shù)據(jù)并監(jiān)聽其值
這篇文章主要介紹了react中如何使用定義數(shù)據(jù)并監(jiān)聽其值問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01