詳解React?Fiber架構(gòu)原理
一、概述
在 React 16 之前,VirtualDOM 的更新采用的是Stack架構(gòu)實(shí)現(xiàn)的,也就是循環(huán)遞歸方式。不過(guò),這種對(duì)比方式有明顯的缺陷,就是一旦任務(wù)開(kāi)始進(jìn)行就無(wú)法中斷,如果遇到應(yīng)用中組件數(shù)量比較龐大,那么VirtualDOM 的層級(jí)就會(huì)比較深,帶來(lái)的結(jié)果就是主線程被長(zhǎng)期占用,進(jìn)而阻塞渲染、造成卡頓現(xiàn)象。
為了避免出現(xiàn)卡頓等問(wèn)題,我們必須保障在執(zhí)行更新操作時(shí)計(jì)算時(shí)不能超過(guò)16ms,如果超過(guò)16ms,就需要先暫停,讓給瀏覽器進(jìn)行渲染,后續(xù)再繼續(xù)執(zhí)行更新計(jì)算。而Fiber架構(gòu)就是為了支持“可中斷渲染”而創(chuàng)建的。
在React中,F(xiàn)iber使用了一種新的數(shù)據(jù)結(jié)構(gòu)fiber tree,它可以把虛擬dom tree轉(zhuǎn)換成一個(gè)鏈表,然后再執(zhí)行遍歷操作,而鏈表在執(zhí)行遍歷操作時(shí)是支持?jǐn)帱c(diǎn)重啟的,示意圖如下。

二、Fiber架構(gòu)
2.1 執(zhí)行單元
官方介紹中,F(xiàn)iber 被理解為是一種數(shù)據(jù)結(jié)構(gòu),但是我們也可以將它理解為是一個(gè)執(zhí)行單元。
Fiber 可以理解為一個(gè)執(zhí)行單元,每次執(zhí)行完一個(gè)執(zhí)行單元,React Fiber就會(huì)檢查還剩多少時(shí)間,如果沒(méi)有時(shí)間則將控制權(quán)讓出去,然后由瀏覽器執(zhí)行渲染操作。React Fiber 與瀏覽器的交互流程如下圖。

可以看到,React 首先向?yàn)g覽器請(qǐng)求調(diào)度,瀏覽器在執(zhí)行完一幀后如果還有空閑時(shí)間,會(huì)去判斷是否存在待執(zhí)行任務(wù),不存在就直接將控制權(quán)交給瀏覽器;如果存在就會(huì)執(zhí)行對(duì)應(yīng)的任務(wù),執(zhí)行完一個(gè)新的任務(wù)單元之后會(huì)繼續(xù)判斷是否還有時(shí)間,有時(shí)間且有待執(zhí)行任務(wù)則會(huì)繼續(xù)執(zhí)行下一個(gè)任務(wù),否則將控制權(quán)交給瀏覽器執(zhí)行渲染,這個(gè)流程是循環(huán)進(jìn)行的。
所以,我們可以將Fiber 理解為一個(gè)執(zhí)行單元,并且這個(gè)執(zhí)行單元必須是一次完成的,不能出現(xiàn)暫停。并且,這個(gè)小的執(zhí)行單元在執(zhí)行完后計(jì)算之后,可以移交控制權(quán)給瀏覽器去響應(yīng)用戶,從而提升了渲染的效率。
2.2 數(shù)據(jù)結(jié)構(gòu)
在官方的文檔中,F(xiàn)iber 被解釋為是一種數(shù)據(jù)結(jié)構(gòu),即鏈表結(jié)構(gòu)。在鏈表結(jié)構(gòu)中,每個(gè) Virtual DOM 都可以表示為一個(gè) fiber,如下圖所示。

通常,一個(gè) fiber包括了 child(第一個(gè)子節(jié)點(diǎn))、sibling(兄弟節(jié)點(diǎn))、return(父節(jié)點(diǎn))等屬性,React Fiber 機(jī)制的實(shí)現(xiàn),就是依賴于上面的數(shù)據(jù)結(jié)構(gòu)。
2.3 Fiber鏈表結(jié)構(gòu)
通過(guò)介紹,我們知道Fiber使用的是鏈表結(jié)構(gòu),準(zhǔn)確的說(shuō)是單鏈表樹(shù)結(jié)構(gòu),詳見(jiàn)ReactFiber.js源碼。為了放便理解 Fiber 的遍歷過(guò)程,下面我們就看下Fiber鏈表結(jié)構(gòu)。

在上面的例子中,每一個(gè)單元都包含了payload(數(shù)據(jù))和nextUpdate(指向下一個(gè)單元的指針)兩個(gè)元素,定義結(jié)構(gòu)如下:
class Update {
constructor(payload, nextUpdate) {
this.payload = payload //payload 數(shù)據(jù)
this.nextUpdate = nextUpdate //指向下一個(gè)節(jié)點(diǎn)的指針
}
}
接下來(lái)定義一個(gè)隊(duì)列,把每個(gè)單元串聯(lián)起來(lái)。為此,我們需要定義兩個(gè)指針:頭指針firstUpdate和尾指針lastUpdate,作用是指向第一個(gè)單元和最后一個(gè)單元,然后再加入baseState屬性存儲(chǔ)React中的state狀態(tài)。
class UpdateQueue {
constructor() {
this.baseState = null // state
this.firstUpdate = null // 第一個(gè)更新
this.lastUpdate = null // 最后一個(gè)更新
}
}接下來(lái),再定義兩個(gè)方法:用于插入節(jié)點(diǎn)單元的enqueueUpdate()和用于更新隊(duì)列的forceUpdate()。并且,插入節(jié)點(diǎn)單元時(shí)需要考慮是否已經(jīng)存在節(jié)點(diǎn),如果不存在直接將firstUpdate、lastUpdate指向此節(jié)點(diǎn)即可。更新隊(duì)列是遍歷這個(gè)鏈表,根據(jù)payload中的內(nèi)容去更新state的值
class UpdateQueue {
//.....
enqueueUpdate(update) {
// 當(dāng)前鏈表是空鏈表
if (!this.firstUpdate) {
this.firstUpdate = this.lastUpdate = update
} else {
// 當(dāng)前鏈表不為空
this.lastUpdate.nextUpdate = update
this.lastUpdate = update
}
}
// 獲取state,然后遍歷這個(gè)鏈表,進(jìn)行更新
forceUpdate() {
let currentState = this.baseState || {}
let currentUpdate = this.firstUpdate
while (currentUpdate) {
// 判斷是函數(shù)還是對(duì)象,是函數(shù)則需要執(zhí)行,是對(duì)象則直接返回
let nextState = typeof currentUpdate.payload === 'function' ? currentUpdate.payload(currentState) : currentUpdate.payload
currentState = { ...currentState, ...nextState }
currentUpdate = currentUpdate.nextUpdate
}
// 更新完成后清空鏈表
this.firstUpdate = this.lastUpdate = null
this.baseState = currentState
return currentState
}
}最后,我們寫(xiě)一個(gè)測(cè)試的用例:實(shí)例化一個(gè)隊(duì)列,向其中加入很多節(jié)點(diǎn),再更新這個(gè)隊(duì)列。
let queue = new UpdateQueue()
queue.enqueueUpdate(new Update({ name: 'www' }))
queue.enqueueUpdate(new Update({ age: 10 }))
queue.enqueueUpdate(new Update(state => ({ age: state.age + 1 })))
queue.enqueueUpdate(new Update(state => ({ age: state.age + 1 })))
queue.forceUpdate()
console.log(queue.baseState); //輸出{ name:'www',age:12 }2.4 Fiber節(jié)點(diǎn)
Fiber 框架的拆分單位是 fiber(fiber tree上的一個(gè)節(jié)點(diǎn)),實(shí)際上拆分的節(jié)點(diǎn)就是虛擬DOM的節(jié)點(diǎn),我們需要根據(jù)虛擬dom去生成 fiber tree。 Fiber節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)如下:
{
type: any, //對(duì)于類組件,它指向構(gòu)造函數(shù);對(duì)于DOM元素,它指定HTML tag
key: null | string, //唯一標(biāo)識(shí)符
stateNode: any, //保存對(duì)組件的類實(shí)例,DOM節(jié)點(diǎn)或與fiber節(jié)點(diǎn)關(guān)聯(lián)的其他React元素類型的引用
child: Fiber | null, //大兒子
sibling: Fiber | null, //下一個(gè)兄弟
return: Fiber | null, //父節(jié)點(diǎn)
tag: WorkTag, //定義fiber操作的類型, 詳見(jiàn)https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactWorkTags.js
nextEffect: Fiber | null, //指向下一個(gè)節(jié)點(diǎn)的指針
updateQueue: mixed, //用于狀態(tài)更新,回調(diào)函數(shù),DOM更新的隊(duì)列
memoizedState: any, //用于創(chuàng)建輸出的fiber狀態(tài)
pendingProps: any, //已從React元素中的新數(shù)據(jù)更新,并且需要應(yīng)用于子組件或DOM元素的props
memoizedProps: any, //在前一次渲染期間用于創(chuàng)建輸出的props
// ……
}最終, 所有的fiber 節(jié)點(diǎn)通過(guò)以下屬性:child,sibling 和 return來(lái)構(gòu)成一個(gè)樹(shù)鏈表。
其他的屬性還有memoizedState(創(chuàng)建輸出的 fiber 的狀態(tài))、pendingProps(將要改變的 props )、memoizedProps(上次渲染創(chuàng)建輸出的 props )、pendingWorkPriority(定義 fiber 工作優(yōu)先級(jí))等等就不在過(guò)多的介紹了。
2.5 API
2.5.1 requestAnimationFrame
requestAnimationFrame是瀏覽器提供的繪制動(dòng)畫(huà)的 API ,它要求瀏覽器在下次重繪之前(即下一幀)調(diào)用指定的回調(diào)函數(shù)以更新動(dòng)畫(huà)。
例如,使用requestAnimationFrame實(shí)現(xiàn)正方形的寬度加1px,直到寬度達(dá)到100px停止,代碼如下。
<body>
<div id="div" class="progress-bar "></div>
<button id="start">開(kāi)始動(dòng)畫(huà)</button>
</body>
<script>
let btn = document.getElementById('start')
let div = document.getElementById('div')
let start = 0
let allInterval = []
const progress = () => {
div.style.width = div.offsetWidth + 1 + 'px'
div.innerHTML = (div.offsetWidth) + '%'
if (div.offsetWidth < 100) {
let current = Date.now()
allInterval.push(current - start)
start = current
requestAnimationFrame(progress)
}
}
btn.addEventListener('click', () => {
div.style.width = 0
let currrent = Date.now()
start = currrent
requestAnimationFrame(progress)
})
</script>運(yùn)行上面的代碼,就可以看到瀏覽器會(huì)在每一幀運(yùn)行結(jié)束后,將div的寬度加1px,直到100px為止。
2.5.2 requestIdleCallback
requestIdleCallback 也是 Fiber 的基礎(chǔ) API 。requestIdleCallback能使開(kāi)發(fā)者在主事件循環(huán)上執(zhí)行后臺(tái)和低優(yōu)先級(jí)的工作,而不會(huì)影響延遲關(guān)鍵事件,如動(dòng)畫(huà)和輸入響應(yīng)。正常幀任務(wù)完成后沒(méi)超過(guò)16ms,說(shuō)明有多余的空閑時(shí)間,此時(shí)就會(huì)執(zhí)行requestIdleCallback里注冊(cè)的任務(wù)。
具體的執(zhí)行流程是,開(kāi)發(fā)者采用requestIdleCallback方法注冊(cè)對(duì)應(yīng)的任務(wù),告知瀏覽器任務(wù)的優(yōu)先級(jí)不高,如果每一幀內(nèi)存在空閑時(shí)間,就可以執(zhí)行注冊(cè)的這個(gè)任務(wù)。另外,開(kāi)發(fā)者是可以傳入timeout參數(shù)去定義超時(shí)時(shí)間的,如果到了超時(shí)時(shí)間,那么瀏覽器必須立即執(zhí)行,使用方法如下:
window.requestIdleCallback(callback, { timeout: 1000 })瀏覽器執(zhí)行完方法后,如果沒(méi)有剩余時(shí)間了,或者已經(jīng)沒(méi)有下一個(gè)可執(zhí)行的任務(wù)了,React應(yīng)該歸還控制權(quán),并同樣使用requestIdleCallback去申請(qǐng)下一個(gè)時(shí)間片。具體的流程如下圖:

其中,requestIdleCallback的callback中會(huì)接收到默認(rèn)參數(shù) deadline ,其中包含了以下兩個(gè)屬性:
- timeRamining:返回當(dāng)前幀還剩多少時(shí)間供用戶使用。
- didTimeout:返回 callback 任務(wù)是否超時(shí)。
三、Fiber執(zhí)行流程
Fiber的執(zhí)行流程總體可以分為渲染和調(diào)度兩個(gè)階段,即render階段和commit 階段。其中,render 階段是可中斷的,需要找出所有節(jié)點(diǎn)的變更;而commit 階段是不可中斷的,只會(huì)執(zhí)行操作。
3.1 render階段
此階段的主要任務(wù)就是找出所有節(jié)點(diǎn)產(chǎn)生的變更,如節(jié)點(diǎn)的新增、刪除、屬性變更等。這些變更, React 統(tǒng)稱為副作用,此階段會(huì)構(gòu)建一棵Fiber tree,以虛擬Dom節(jié)點(diǎn)的維度對(duì)任務(wù)進(jìn)行拆分,即一個(gè)虛擬Dom節(jié)點(diǎn)對(duì)應(yīng)一個(gè)任務(wù),最后產(chǎn)出的結(jié)果是副作用列表(effect list)。
3.1.1 遍歷流程
在此階段,React Fiber會(huì)將虛擬DOM樹(shù)轉(zhuǎn)化為Fiber tree,這個(gè)Fiber tree是由節(jié)點(diǎn)構(gòu)成的,每個(gè)節(jié)點(diǎn)都有child、sibling、return屬性,遍歷Fiber tree時(shí)采用的是后序遍歷方法,遍歷的流程如下:
從頂點(diǎn)開(kāi)始遍歷;
如果有大兒子,先遍歷大兒子;如果沒(méi)有大兒子,則表示遍歷完成;
大兒子: a. 如果有弟弟,則返回弟弟,跳到2 b. 如果沒(méi)有弟弟,則返回父節(jié)點(diǎn),并標(biāo)志完成父節(jié)點(diǎn)遍歷,跳到2 d. 如果沒(méi)有父節(jié)點(diǎn)則標(biāo)志遍歷結(jié)束
下面是后序遍歷的示意圖:

此時(shí),樹(shù)結(jié)構(gòu)的定義如下:
const A1 = { type: 'div', key: 'A1' }
const B1 = { type: 'div', key: 'B1', return: A1 }
const B2 = { type: 'div', key: 'B2', return: A1 }
const C1 = { type: 'div', key: 'C1', return: B1 }
const C2 = { type: 'div', key: 'C2', return: B1 }
const C3 = { type: 'div', key: 'C3', return: B2 }
const C4 = { type: 'div', key: 'C4', return: B2 }
A1.child = B1
B1.sibling = B2
B1.child = C1
C1.sibling = C2
B2.child = C3
C3.sibling = C4
module.exports = A13.1.2 收集effect list
接下來(lái),就是收集節(jié)點(diǎn)產(chǎn)生的變更,并將結(jié)果轉(zhuǎn)化成一個(gè)effect list,步驟如下:
- 如果當(dāng)前節(jié)點(diǎn)需要更新,則打tag更新當(dāng)前節(jié)點(diǎn)狀態(tài)(props, state, context等);
- 為每個(gè)子節(jié)點(diǎn)創(chuàng)建fiber。如果沒(méi)有產(chǎn)生child fiber,則結(jié)束該節(jié)點(diǎn),把effect list歸并到return,把此節(jié)點(diǎn)的sibling節(jié)點(diǎn)作為下一個(gè)遍歷節(jié)點(diǎn);否則把child節(jié)點(diǎn)作為下一個(gè)遍歷節(jié)點(diǎn);
- 如果有剩余時(shí)間,則開(kāi)始下一個(gè)節(jié)點(diǎn),否則等下一次主線程空閑再開(kāi)始下一個(gè)節(jié)點(diǎn);
- 如果沒(méi)有下一個(gè)節(jié)點(diǎn)了,進(jìn)入pendingCommit狀態(tài),此時(shí)effect list收集完畢,結(jié)束。
如果用代碼來(lái)實(shí)現(xiàn)的話,首先需要遍歷子虛擬DOM元素?cái)?shù)組,為每個(gè)虛擬DOM元素創(chuàng)建子fiber。
const reconcileChildren = (currentFiber, newChildren) => {
let newChildIndex = 0
let prevSibling // 上一個(gè)子fiber
// 遍歷子虛擬DOM元素?cái)?shù)組,為每個(gè)虛擬DOM元素創(chuàng)建子fiber
while (newChildIndex < newChildren.length) {
let newChild = newChildren[newChildIndex]
let tag
// 打tag,定義 fiber類型
if (newChild.type === ELEMENT_TEXT) { // 這是文本節(jié)點(diǎn)
tag = TAG_TEXT
} else if (typeof newChild.type === 'string') { // 如果type是字符串,則是原生DOM節(jié)點(diǎn)
tag = TAG_HOST
}
let newFiber = {
tag,
type: newChild.type,
props: newChild.props,
stateNode: null, // 還未創(chuàng)建DOM元素
return: currentFiber, // 父親fiber
effectTag: INSERT, // 副作用標(biāo)識(shí),包括新增、刪除、更新
nextEffect: null, // 指向下一個(gè)fiber,effect list通過(guò)nextEffect指針進(jìn)行連接
}
if (newFiber) {
if (newChildIndex === 0) {
currentFiber.child = newFiber // child為大兒子
} else {
prevSibling.sibling = newFiber // 讓大兒子的sibling指向二兒子
}
prevSibling = newFiber
}
newChildIndex++
}
}該方法會(huì)收集 fiber 節(jié)點(diǎn)下所有的副作用,并組成effect list。每個(gè) fiber 有兩個(gè)屬性:
- firstEffect:指向第一個(gè)有副作用的子fiber。
- lastEffect:指向最后一個(gè)有副作用的子fiber。
而我們需要收集的就是中間nextEffect,最終形成一個(gè)單鏈表。
// 在完成的時(shí)候要收集有副作用的fiber,組成effect list
const completeUnitOfWork = (currentFiber) => {
// 后續(xù)遍歷,兒子們完成之后,自己才能完成。最后會(huì)得到以上圖中的鏈條結(jié)構(gòu)。
let returnFiber = currentFiber.return
if (returnFiber) {
// 如果父親fiber的firstEffect沒(méi)有值,則將其指向當(dāng)前fiber的firstEffect
if (!returnFiber.firstEffect) {
returnFiber.firstEffect = currentFiber.firstEffect
}
// 如果當(dāng)前fiber的lastEffect有值
if (currentFiber.lastEffect) {
if (returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = currentFiber.firstEffect
}
returnFiber.lastEffect = currentFiber.lastEffect
}
const effectTag = currentFiber.effectTag
if (effectTag) { // 說(shuō)明有副作用
// 每個(gè)fiber有兩個(gè)屬性:
// 1)firstEffect:指向第一個(gè)有副作用的子fiber
// 2)lastEffect:指向最后一個(gè)有副作用的子fiber
// 中間的使用nextEffect做成一個(gè)單鏈表
if (returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = currentFiber
} else {
returnFiber.firstEffect = currentFiber
}
returnFiber.lastEffect = currentFiber
}
}
}最后,再定義一個(gè)遞歸函數(shù),從根節(jié)點(diǎn)出發(fā),把全部的 fiber 節(jié)點(diǎn)遍歷一遍,最終產(chǎn)出一個(gè)effect list。
const performUnitOfWork = (currentFiber) => {
beginWork(currentFiber)
if (currentFiber.child) {
return currentFiber.child
}
while (currentFiber) {
completeUnitOfWork(currentFiber)
if (currentFiber.sibling) {
return currentFiber.sibling
}
currentFiber = currentFiber.return
}
}3.2 commit階段
commit 階段需要將上階段計(jì)算出來(lái)的需要處理的副作用一次性執(zhí)行,此階段不能暫停,否則會(huì)出現(xiàn)UI更新不連續(xù)的現(xiàn)象。此階段需要根據(jù)effect list,將所有更新都 commit 到DOM樹(shù)上。
3.2.1 根據(jù)effect list 更新視圖
此階段,根據(jù)一個(gè) fiber 的effect list列表去更新視圖,此次只列舉了新增節(jié)點(diǎn)、刪除節(jié)點(diǎn)、更新節(jié)點(diǎn)的三種操作 。
const commitWork = currentFiber => {
if (!currentFiber) return
let returnFiber = currentFiber.return
let returnDOM = returnFiber.stateNode // 父節(jié)點(diǎn)元素
if (currentFiber.effectTag === INSERT) { // 如果當(dāng)前fiber的effectTag標(biāo)識(shí)位INSERT,則代表其是需要插入的節(jié)點(diǎn)
returnDOM.appendChild(currentFiber.stateNode)
} else if (currentFiber.effectTag === DELETE) { // 如果當(dāng)前fiber的effectTag標(biāo)識(shí)位DELETE,則代表其是需要?jiǎng)h除的節(jié)點(diǎn)
returnDOM.removeChild(currentFiber.stateNode)
} else if (currentFiber.effectTag === UPDATE) { // 如果當(dāng)前fiber的effectTag標(biāo)識(shí)位UPDATE,則代表其是需要更新的節(jié)點(diǎn)
if (currentFiber.type === ELEMENT_TEXT) {
if (currentFiber.alternate.props.text !== currentFiber.props.text) {
currentFiber.stateNode.textContent = currentFiber.props.text
}
}
}
currentFiber.effectTag = null
}寫(xiě)一個(gè)遞歸函數(shù),從根節(jié)點(diǎn)出發(fā),根據(jù)effect list完成全部更新。
/**
* 根據(jù)一個(gè) fiber 的 effect list 更新視圖
*/
const commitRoot = () => {
let currentFiber = workInProgressRoot.firstEffect
while (currentFiber) {
commitWork(currentFiber)
currentFiber = currentFiber.nextEffect
}
currentRoot = workInProgressRoot // 把當(dāng)前渲染成功的根fiber賦給currentRoot
workInProgressRoot = null
}3.2.2 視圖更新
接下來(lái),就是循環(huán)執(zhí)行工作,當(dāng)計(jì)算完成每個(gè) fiber 的effect list后,調(diào)用 commitRoot 完成視圖更新。
const workloop = (deadline) => {
let shouldYield = false // 是否需要讓出控制權(quán)
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
shouldYield = deadline.timeRemaining() < 1 // 如果執(zhí)行完任務(wù)后,剩余時(shí)間小于1ms,則需要讓出控制權(quán)給瀏覽器
}
if (!nextUnitOfWork && workInProgressRoot) {
console.log('render階段結(jié)束')
commitRoot() // 沒(méi)有下一個(gè)任務(wù)了,根據(jù)effect list結(jié)果批量更新視圖
}
// 請(qǐng)求瀏覽器進(jìn)行再次調(diào)度
requestIdleCallback(workloop, { timeout: 1000 })
}到此,根據(jù)收集到的變更信息完成了視圖的刷新操作,F(xiàn)iber的整個(gè)刷新流程也就實(shí)現(xiàn)了。
四、總結(jié)
相比傳統(tǒng)的Stack架構(gòu),F(xiàn)iber 將工作劃分為多個(gè)工作單元,每個(gè)工作單元在執(zhí)行完成后依據(jù)剩余時(shí)間決定是否讓出控制權(quán)給瀏覽器執(zhí)行渲染。 并且它設(shè)置每個(gè)工作單元的優(yōu)先級(jí),暫停、重用和中止工作單元。 每個(gè)Fiber節(jié)點(diǎn)都是fiber tree上的一個(gè)節(jié)點(diǎn),通過(guò)子、兄弟和返回引用連接,形成一個(gè)完整的fiber tree。
到此這篇關(guān)于React Fiber架構(gòu)原理剖析的文章就介紹到這了,更多相關(guān)React Fiber原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React實(shí)現(xiàn)倒計(jì)時(shí)功能組件
這篇文章主要為大家詳細(xì)介紹了如何通過(guò)React實(shí)現(xiàn)一個(gè)倒計(jì)時(shí)功能組件,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解下2023-09-09
react中如何使用定義數(shù)據(jù)并監(jiān)聽(tīng)其值
這篇文章主要介紹了react中如何使用定義數(shù)據(jù)并監(jiān)聽(tīng)其值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
詳解React Native頂|底部導(dǎo)航使用小技巧
本篇文章主要介紹了詳解React Native頂|底部導(dǎo)航使用小技巧 ,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09
理解react中受控組件和非受控組件及應(yīng)用場(chǎng)景
當(dāng)涉及到React框架時(shí),了解受控組件和非受控組件是非常重要的概念,本文主要介紹了理解react中受控組件和非受控組件及應(yīng)用場(chǎng)景,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
關(guān)于useEffect執(zhí)行兩次的問(wèn)題及解決
這篇文章主要介紹了關(guān)于useEffect執(zhí)行兩次的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09

