淺談React Hook采用環(huán)形鏈表的原因
React Hooks 更新采用環(huán)形鏈表的原因
React Hooks 的更新隊(duì)列采用環(huán)形鏈表結(jié)構(gòu),這是一個(gè)精心設(shè)計(jì)的決策。讓我從源碼層面解釋為什么。
1. 環(huán)形鏈表的核心優(yōu)勢(shì)
優(yōu)勢(shì)一:O(1) 時(shí)間的合并操作
// 環(huán)形鏈表結(jié)構(gòu)
type UpdateQueue<T> = {
pending: Update<T> | null, // 指向最后一個(gè)更新
}
type Update<T> = {
action: T | ((T) => T),
next: Update<T> | null,
}
// 添加新更新 - O(1) 時(shí)間復(fù)雜度
function appendUpdate(queue, update) {
const pending = queue.pending
if (pending === null) {
// 第一個(gè)更新,指向自己形成環(huán)
update.next = update
} else {
// 插入到環(huán)形鏈表的頭部
// pending 指向最后一個(gè)節(jié)點(diǎn)
// pending.next 指向第一個(gè)節(jié)點(diǎn)
update.next = pending.next // 新節(jié)點(diǎn)的 next 指向第一個(gè)節(jié)點(diǎn)
pending.next = update // 最后一個(gè)節(jié)點(diǎn)的 next 指向新節(jié)點(diǎn)
}
queue.pending = update // 更新 pending 指向新節(jié)點(diǎn)(新的最后一個(gè))
}
// 如果是單向鏈表(非環(huán)形)
function appendUpdateLinear(head, update) {
// 需要遍歷到末尾才能添加 - O(n) 時(shí)間復(fù)雜度
if (head === null) {
return update
}
let current = head
while (current.next !== null) { // 遍歷!
current = current.next
}
current.next = update
return head
}
實(shí)際性能對(duì)比:
// React 中頻繁的批量更新場(chǎng)景
function handleClick() {
// 同一個(gè)狀態(tài)連續(xù)更新多次
setCount(1)
setCount(2)
setCount(3)
setCount(4)
setCount(5)
}
// 環(huán)形鏈表:每次 O(1),5次操作 = 5個(gè)單位時(shí)間
// 單向鏈表:第1次 O(1),第2次 O(2),第3次 O(3)... 總計(jì) O(n2)
優(yōu)勢(shì)二:高效的雙向遍歷能力
// React 處理更新時(shí)的遍歷
function processUpdateQueue(queue) {
const pending = queue.pending
if (pending !== null) {
// 關(guān)鍵:通過 pending.next 獲取第一個(gè)更新
const first = pending.next // O(1) 獲取頭部
let newState = currentState
// 正向遍歷所有更新
let update = first
do {
newState = applyUpdate(newState, update.action)
update = update.next
} while (update !== first) // 回到起點(diǎn),遍歷完成
// 如果需要反向遍歷(比如優(yōu)先級(jí)調(diào)度)
// 也可以輕松實(shí)現(xiàn)
let last = pending
let prev = first
while (prev.next !== last) {
// 反向遍歷邏輯
}
}
}
2. 解決并發(fā)渲染中的問題
問題場(chǎng)景:高優(yōu)先級(jí)更新打斷
// 環(huán)形鏈表在并發(fā)渲染中的優(yōu)勢(shì)
function concurrentUpdateExample() {
const [count, setCount] = useState(0)
// 場(chǎng)景:用戶快速點(diǎn)擊,產(chǎn)生多個(gè)更新
setCount(1) // 低優(yōu)先級(jí)更新 A
setCount(2) // 高優(yōu)先級(jí)更新 B(打斷 A)
setCount(3) // 低優(yōu)先級(jí)更新 C
// 環(huán)形鏈表的處理方式:
// pending → [C] → [B] → [A] → (回到 [C])
// ↑_______________|
//
// 渲染時(shí)可以從任意節(jié)點(diǎn)開始,靈活調(diào)整優(yōu)先級(jí)順序
}
React 的實(shí)際實(shí)現(xiàn)
// React 源碼中的環(huán)形鏈表實(shí)現(xiàn)(簡(jiǎn)化)
function dispatchSetState(fiber, queue, action) {
const update = {
action,
next: null,
priority: getCurrentPriorityLevel(),
}
// 獲取當(dāng)前待處理的更新環(huán)
const pending = queue.pending
if (pending === null) {
// 第一個(gè)更新,形成環(huán)
update.next = update
} else {
// 插入到環(huán)中
update.next = pending.next
pending.next = update
}
queue.pending = update
// 并發(fā)渲染時(shí)可以安全地 fork 更新隊(duì)列
if (fiber.lanes !== NoLanes) {
// 如果正在進(jìn)行渲染,創(chuàng)建 interleaved 隊(duì)列
const interleaved = queue.interleaved
if (interleaved === null) {
queue.interleaved = update
} else {
update.next = interleaved.next
interleaved.next = update
}
queue.interleaved = update
}
scheduleUpdateOnFiber(fiber)
}
// 處理并發(fā)更新時(shí)的隊(duì)列合并
function mergeQueues(baseQueue, interleavedQueue) {
if (baseQueue === null) {
return interleavedQueue
}
if (interleavedQueue === null) {
return baseQueue
}
// 環(huán)形鏈表的合并:O(1) 完成
// baseQueue: ... → last1 → first1 → ...
// interleavedQueue: ... → last2 → first2 → ...
const first1 = baseQueue.next
const last1 = baseQueue
const first2 = interleavedQueue.next
const last2 = interleavedQueue
// 將兩個(gè)環(huán)連接成一個(gè)環(huán)
last1.next = first2
last2.next = first1
return interleavedQueue // 返回新的尾部
}
3. 批量更新與狀態(tài)計(jì)算
批量更新機(jī)制
// React 18 的自動(dòng)批處理
function batchUpdate() {
// 所有更新被收集到環(huán)形鏈表
setCount(1) // update1
setCount(2) // update2
setCount(3) // update3
setName('John') // 另一個(gè) Hook 的更新
// 環(huán)形鏈表結(jié)構(gòu):
// pending → update3 → update2 → update1 → (回到 update3)
// ↑____________________|
// 一次渲染處理所有更新
// 遍歷環(huán)形鏈表只需 O(n) 時(shí)間
}
// 處理環(huán)形鏈表的代碼
function processUpdateQueue(workInProgress, queue) {
let pending = queue.pending
if (pending !== null) {
// 關(guān)鍵:解除環(huán)形,變成單向鏈表方便處理
const first = pending.next
let last = pending
let newState = currentState
// 斷開環(huán)形
last.next = null
// 現(xiàn)在變成了單向鏈表,可以安全遍歷
let update = first
while (update !== null) {
newState = applyUpdate(newState, update.action)
update = update.next
}
return newState
}
}
4. 與單向鏈表的對(duì)比
// 性能對(duì)比測(cè)試
function benchmark() {
const updates = Array(1000).fill().map((_, i) => ({ action: i }))
// 環(huán)形鏈表插入
console.time('Circular Linked List')
let circularQueue = null
for (let update of updates) {
if (circularQueue === null) {
update.next = update
circularQueue = update
} else {
update.next = circularQueue.next
circularQueue.next = update
circularQueue = update
}
}
console.timeEnd('Circular Linked List') // ~0.1ms
// 單向鏈表插入
console.time('Singly Linked List')
let linearHead = null
let linearTail = null
for (let update of updates) {
if (linearHead === null) {
linearHead = update
linearTail = update
} else {
linearTail.next = update
linearTail = update
}
}
console.timeEnd('Singly Linked List') // ~0.15ms(略慢)
// 但環(huán)形鏈表在特定操作上優(yōu)勢(shì)明顯
// 比如:獲取第一個(gè)和最后一個(gè)元素都是 O(1)
}
5. 實(shí)際應(yīng)用場(chǎng)景
場(chǎng)景一:優(yōu)先級(jí)提升
// React 中的優(yōu)先級(jí)提升機(jī)制
function promoteUpdatePriority(queue, targetPriority) {
const pending = queue.pending
if (pending === null) return
// 環(huán)形鏈表可以輕松調(diào)整順序
let update = pending.next
let highestPriorityUpdate = null
do {
if (update.priority > targetPriority) {
// 找到高優(yōu)先級(jí)更新,提升它
highestPriorityUpdate = update
break
}
update = update.next
} while (update !== pending.next)
if (highestPriorityUpdate) {
// 將高優(yōu)先級(jí)更新移到環(huán)的頭部
// 這樣渲染時(shí)會(huì)優(yōu)先處理
queue.pending = highestPriorityUpdate
}
}
場(chǎng)景二:狀態(tài)回滾
// 時(shí)間切片中的狀態(tài)回滾
function rollbackUpdates(queue, rollbackPoint) {
const pending = queue.pending
if (pending === null) return
// 找到回滾點(diǎn)
let update = pending.next
let found = false
do {
if (update === rollbackPoint) {
found = true
break
}
update = update.next
} while (update !== pending.next)
if (found) {
// 截?cái)喹h(huán)形鏈表,丟棄回滾點(diǎn)之后的更新
queue.pending = rollbackPoint
rollbackPoint.next = rollbackPoint // 重新形成環(huán)
}
}
6. 內(nèi)存和 GC 優(yōu)勢(shì)
// 環(huán)形鏈表的垃圾回收優(yōu)勢(shì)
function cleanupQueue(queue) {
// 斷開環(huán)形引用,幫助 GC
const pending = queue.pending
if (pending !== null) {
// 打破循環(huán)引用
const first = pending.next
pending.next = null // 斷開環(huán)
// 現(xiàn)在可以安全地清理
let update = first
while (update !== null) {
const next = update.next
update.next = null // 幫助 GC
update = next
}
}
queue.pending = null
}
// 單向鏈表需要更多遍歷才能完全清理
總結(jié)
React Hooks 采用環(huán)形鏈表的核心原因:
- 性能優(yōu)化:O(1) 的頭部和尾部訪問,O(1) 的合并操作
- 并發(fā)安全:便于 fork 和合并更新隊(duì)列,支持優(yōu)先級(jí)調(diào)度
- 靈活性:可以從任意節(jié)點(diǎn)開始遍歷,方便實(shí)現(xiàn)各種調(diào)度策略
- 內(nèi)存效率:無需維護(hù)額外的頭尾指針,單個(gè)指針就能定位整個(gè)隊(duì)列
- 批量更新:天然支持環(huán)形遍歷,適合處理批量狀態(tài)更新
這種設(shè)計(jì)是 React 團(tuán)隊(duì)在性能和功能之間做出的最優(yōu)權(quán)衡,既滿足了并發(fā)渲染的需求,又保持了良好的性能特性。
到此這篇關(guān)于淺談React Hook采用環(huán)形鏈表的原因的文章就介紹到這了,更多相關(guān)React Hook環(huán)形鏈表內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react-routerV6版本和V5版本的詳細(xì)對(duì)比
React-Router5是React-Router6的前一個(gè)版本,它已經(jīng)被React-Router6取代,React-Router 6是一次較大的重大更新,本文就來介紹一下react-routerV6版本和V5版本的詳細(xì)對(duì)比,感興趣的可以了解一下2023-12-12
在React項(xiàng)目中實(shí)現(xiàn)一個(gè)簡(jiǎn)單的錨點(diǎn)目錄定位
錨點(diǎn)目錄定位功能在長(zhǎng)頁(yè)面和文檔類網(wǎng)站中非常常見,它可以讓用戶快速定位到頁(yè)面中的某個(gè)章節(jié),本文講給大家介紹一下React項(xiàng)目中如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的錨點(diǎn)目錄定位,文中有詳細(xì)的實(shí)現(xiàn)代碼,需要的朋友可以參考下2023-09-09
react跳轉(zhuǎn)后路由變了頁(yè)面沒刷新的解決
這篇文章主要介紹了react跳轉(zhuǎn)后路由變了頁(yè)面沒刷新的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
React中swiper的配置(reactjs-swiper)
本文詳述了在React項(xiàng)目中使用reactjs-swiper組件的步驟與技巧,包括安裝、配置、解決swiperOptions無效問題及組件掛載,下面就來詳細(xì)的介紹一下2026-06-06
瀏覽器中視頻播放器實(shí)現(xiàn)的基本思路與代碼
這篇文章主要給大家介紹了關(guān)于瀏覽器中視頻播放器實(shí)現(xiàn)的基本思路與代碼,并且詳細(xì)總結(jié)了瀏覽器中的音視頻知識(shí),對(duì)大家的理解和學(xué)習(xí)非常有幫助,需要的朋友可以參考下2021-08-08
Remix路由模塊輸出對(duì)象loader函數(shù)詳解
這篇文章主要為大家介紹了Remix路由模塊輸出對(duì)象loader函數(shù)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>2023-04-04

