最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺談React19事件調(diào)度的設(shè)計思路

 更新時間:2026年05月11日 09:16:53   作者:秀秀不只會前端  
本文主要介紹了React選擇MessageChannel作為事件調(diào)度機制的原因,essageChannel屬于宏任務(wù),延遲極低且不會阻塞渲染,能夠滿足React在不阻塞瀏覽器的前提下,盡可能多地推進Fiber渲染進度的需求

先說結(jié)論,React 選擇 MessageChannel 完成事件調(diào)度,是因為它:

  • 屬于宏任務(wù)(不會餓死瀏覽器:JavaScript 一直占著主線程,導(dǎo)致瀏覽器一直沒有機會去做它必須做的事(渲染、響應(yīng)輸入、布局、繪制))
  • 延遲極低(接近微任務(wù),但不會阻塞渲染)
  • 相較于 rAF 不綁定渲染幀
  • 可控、可中斷、可讓出主線程

一、React 調(diào)度和事件循環(huán)的密切聯(lián)系

1、React 在“調(diào)度”什么?

React 調(diào)度的不是「事件」, React 調(diào)度的是:Fiber 渲染任務(wù)(render work)

也就是我上篇文章說過的這些東西:

  • beginWork
  • completeWork
  • diff
  • 構(gòu)建 workInProgress Fiber 樹

React Scheduler 的目標(biāo)只有是:在不阻塞瀏覽器的前提下,盡可能多地推進 Fiber 渲染進度。

所以 Scheduler 需要滿足:

  • 能反復(fù)被調(diào)用
  • 每次執(zhí)行一小部分
  • 執(zhí)行完就“讓出主線程”

2、回憶瀏覽器事件循環(huán)

事件循環(huán)模型:

┌─────────────┐
│ 宏任務(wù)隊列(Task)     │  ← setTimeout / MessageChannel / rAF callback
└─────┬───────┘
          ↓
      執(zhí)行 JS
          ↓
┌─────────────┐
│ 微任務(wù)隊列(一次性清空) │  ← Promise.then / queueMicrotask
└─────┬───────┘
          ↓
      清空所有微任務(wù)
          ↓
      瀏覽器渲染(paint)

因此,為了滿足上述 Scheduler 的需求,我們只能選擇 Task(后續(xù)詳細說明為什么最終選擇了 MessageChannel)。

二、React Scheduler 源碼(React 19)

packages/scheduler/src/forks/SchedulerHostConfig.default.js

核心邏輯(簡化):

const channel = new MessageChannel();
const port = channel.port2;

channel.port1.onmessage = performWorkUntilDeadline;

function requestHostCallback() {
  port.postMessage(null); // 用 MessageChannel 來“自我喚醒”
}

Scheduler 執(zhí)行模型:

MessageChannel 回調(diào)觸發(fā)

performWorkUntilDeadline

while (還有任務(wù) && 沒超時) {
  執(zhí)行 Fiber work
}

時間不夠 → 再發(fā)一次 MessageChannel(MessageChannel 是“下一次調(diào)度 tick”的觸發(fā)器)

三、為什么不用微任務(wù)(Promise / queueMicrotask)

假如 React 用微任務(wù)會發(fā)生什么?

Promise.resolve().then(workLoop)

問題 1:會阻塞渲染

微任務(wù)會在 paint 之前全部執(zhí)行完

意味著:

React 繼續(xù) work
→ work 里又調(diào)度微任務(wù)
→ 瀏覽器:你先別畫
→ UI 卡死

這完全就是 Fiber 的“時間切片”的對立做法。

問題 2:微任務(wù)不可中斷

  • 微任務(wù)一旦開始
  • 瀏覽器必須清空
  • React 無法“讓出主線程”,更沒法實現(xiàn)并發(fā)渲染

四、為什么不用 setTimeout

setTimeout 的問題不是“慢”,而是“不穩(wěn)定”。

問題 1:最小延遲不可靠

  • HTML 標(biāo)準(zhǔn):?最小 4ms(?HTML Living Standard — Last Updated 31 January 2026

    If nesting level is greater than 5, and timeout is less than 4, then set timeout to 4.

    setTimeout 在嵌套層級超過 5 層,timeout(延時)如果小于 4ms,那么則會設(shè)置為 4ms,這個時差是 React 無法接受的。

  • 精度太粗(Scheduler:“當(dāng)前幀還能不能再干 2ms 的活?”)

五、為什么不用 requestAnimationFrame(rAF)

1、rAF 被綁定到“渲染幀”

一幀 ≈ 16.6ms

但 React 的目標(biāo)是:?只要主線程空一點,我就推進一點 Fiber;?而不是:“非要等下一幀”。

2、rAF 在后臺不執(zhí)行

瀏覽器會暫停 rAF(選擇性跳過渲染幀),React 更新直接“凍結(jié)”!

六、還得是 MessageChannel ~

MessageChannel 是什么?

const channel = new MessageChannel();
// 兩個頻道端口,這兩個端口可以相互通信
const port1 = channel.port1;
const port2 = channel.port2;
btn1.onclick = function(){
  // port2 給 port1 發(fā)消息
  port2.postMessage(content.value);
}
// port1 監(jiān)聽自己受到的消息
port1.onmessage = function(event){
  console.log(`port1 收到了來自 port2 的消息:${event.data}`);
}

MessageChannel 完美規(guī)避掉上述一系列缺點:

MessageChannel + shouldYield => 時間切片。

React 并不是“無腦跑”,而是每一小段都問一句:

shouldYield()

判斷依據(jù):

  • performance.now()
  • 幀預(yù)算
  • 用戶輸入是否 pending

如果該讓出:

requestHostCallback() // 再發(fā)一個 MessageChannel
return;

[宏任務(wù)] MessageChannel
  ↓
  React 執(zhí)行 Fiber work(2~5ms)
  ↓
  shouldYield = true
  ↓
  postMessage 再約一次
  ↓
[瀏覽器有機會 paint / 處理輸入]
  ↓
[下一次 MessageChannel]

七、彩蛋來咯

1、requestAnimationFrame

盲猜很多同學(xué)對于上面若干種不如 MessageChannel 的做法還不是很清楚,根本在于事件循環(huán)掌握的不好,我這里針對事件循環(huán)的**requestAnimationFrame**詳細講講(其他知識點可以翻看我之前寫的關(guān)于事件循環(huán)的文章,講解的非常清楚)。

事件循環(huán)里面的 requestAnimationFrame 僅僅是一個跟著渲染幀走的“小弟”,有渲染才有 rAF:

  • 它不能“縮短”上一個 16.66ms 中 Task 的執(zhí)行時間
  • 保證回調(diào)只會在“瀏覽器即將渲染下一幀之前”執(zhí)行

因此如果上一幀的 Task 太重導(dǎo)致錯過渲染窗口,瀏覽器會直接“丟幀”,而不是排隊執(zhí)行導(dǎo)致連鎖累積卡頓(setTimeout 的做法)

rAF 回調(diào)永遠不會擠占渲染時機,只會“對齊”渲染節(jié)奏

“丟幀”這個概念,對于數(shù)碼產(chǎn)品經(jīng)常關(guān)注的同學(xué)應(yīng)該會非常熟悉。我們拿游戲“原神”舉例子,幀率越高動畫越流暢,而如果某一幀事件 Task 執(zhí)行時間太長(超過 1 幀總時長),rAF 就不再執(zhí)行,這幀就被自動“丟掉了”。而一些手機廠商為了彌補這個問題,所以就出現(xiàn)了手動“插幀”的做法。

一般地,1s 對應(yīng)著 60 幀,而 1 幀就是 16.66ms。如果一個 Task 超過了 16.66ms,那么就占用了下一幀的時間,下一幀則不再 rAF/paint (出現(xiàn)丟幀)。但如果我們使用低幀率,假如使用 30 幀 1s,那么 1 幀就是 33.3ms,這樣雖然畫質(zhì)變差了,但是動畫流暢度確實更好了。

瀏覽器在一幀內(nèi)要做的事情(簡化):

JS Task(古老說法:宏任務(wù))
→ 微任務(wù)
→ rAF
→ 樣式計算
→ Layout
→ Paint
→ Composite
→ 屏幕顯示

?只要 JS Task 超過 ~16ms,瀏覽器就來不及渲染這一幀?,結(jié)果就是:

  • 這一幀直接沒畫出來(掉幀)
  • 用戶看到卡頓

假設(shè)這樣寫動畫:

setTimeout(step, 16)

發(fā)生了什么?

Task A (20ms)  超過 16ms

setTimeout 回調(diào)排隊

Task B (又 20ms)

Task C ...

后果是:

  • 定時器 只管時間,不管渲染(這是“時間驅(qū)動”,不是“渲染驅(qū)動”)
  • 回調(diào)會 持續(xù)排隊
  • 每一幀都被 JS Task 擠爆
  • 卡頓會 累積 + 放大

如果改為 rAF:

requestAnimationFrame(callback) // “當(dāng)瀏覽器準(zhǔn)備開始下一次渲染之前,調(diào)用我”
while (true) {
  1. 取一個 Task 執(zhí)行(macro task)
  2. 執(zhí)行所有 microtasks
  3. 【渲染檢查點】(當(dāng)前時間 - 上一幀渲染時間 < 16.66ms(60Hz))
     - requestAnimationFrame
     - style / layout / paint
}

當(dāng)然,如果 Task 一直執(zhí)行得太久,requestAnimationFrame 一直得不到執(zhí)行,本質(zhì)上仍然是卡頓,而且是「主線程被長期占用型卡頓」。所以 rAF 并不能拯救被 JS 完全占死的主線程。

2、用時間軸演示卡頓

卡頓:場景一

類型一:JS 把主線程徹底占死(致命卡頓)

Task 200ms?Task 200ms?Task 200ms

結(jié)果:

  • rAF
  • Render
  • 輸入響應(yīng)
  • 頁面假死

rAF 無解

類型二:單幀偶爾超時(可恢復(fù)卡頓)

Task 20ms(偶發(fā))?Task 5ms?Task 5ms

結(jié)果:

  • 掉 1 幀
  • 后續(xù)幀恢復(fù)
  • 動畫繼續(xù)

這是 rAF 的“主戰(zhàn)場”

卡頓:場景二

假設(shè)場景

  • 屏幕 60Hz(16.6ms / 幀)
  • 每個動畫 step 的 JS 執(zhí)行 18ms
  • 使用 setTimeout(step, 16)

第 1 幀(已經(jīng)開始出問題)

0ms Task: step 執(zhí)行(18ms)?18ms microtasks?18ms ? 超過 16.6ms,無法渲染?18ms setTimeout 已經(jīng)到期 → 下一個 step 已在 Task 隊列中

結(jié)果:沒渲染,但 JS 沒停

第 2 幀(開始積壓)

18ms Task: step 執(zhí)行(18ms)?36ms microtasks?36ms ? 又錯過渲染?36ms 下一個 step 繼續(xù)排隊

第 N 幀(雪崩)

Task → Task → Task → Task → Task ? 18ms 18ms 18ms 18ms 18ms

表現(xiàn)為:

  • JS 一直在跑
  • 瀏覽器幾乎沒有 Render 機會
  • 頁面看起來 卡住不動
  • CPU 占滿

setTimeout 只認(rèn):時間到了 → 執(zhí)行回調(diào)

不管:

  • 主線程忙不忙
  • 能不能渲染
  • 用戶是不是在滾動 / 點擊

當(dāng)一幀沒畫出來:

  • rAF:直接跳過
  • setTimeout:繼續(xù)補執(zhí)行(它會制造“補幀”)

這意味著:錯過的幀會變成多余的 JS 工作量

3、用戶體感 vs setTimeout

setTimeout(雪崩)

Task Task Task Task Task ?18ms 18ms 18ms 18ms

  • JS 連續(xù)霸占主線程
  • Render 幾乎進不去
  • 頁面“僵死”

requestAnimationFrame(穩(wěn)定但慢)

step →(等下一幀)→ step →(等下一幀)→ step

  • 每幀最多執(zhí)行一次
  • Render 之間有喘息
  • 頁面還能響應(yīng)輸入
  • 動畫只是 低 FPS(這是“慢”,不是“死”)

到此這篇關(guān)于淺談React19事件調(diào)度的設(shè)計思路的文章就介紹到這了,更多相關(guān)React19事件調(diào)度內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 在?React?中如何從狀態(tài)數(shù)組中刪除一個元素

    在?React?中如何從狀態(tài)數(shù)組中刪除一個元素

    這篇文章主要介紹了在?React?中從狀態(tài)數(shù)組中刪除一個元素,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-03-03
  • TypeScript在React項目中的實戰(zhàn)應(yīng)用指南及技巧

    TypeScript在React項目中的實戰(zhàn)應(yīng)用指南及技巧

    在React項目中集成TypeScript可以顯著提升開發(fā)體驗,通過類型檢查減少運行時錯誤,提高代碼可維護性,這篇文章主要介紹了TypeScript在React項目中實戰(zhàn)應(yīng)用指南及技巧的相關(guān)資料,需要的朋友可以參考下
    2026-03-03
  • React中使用setInterval函數(shù)的實例

    React中使用setInterval函數(shù)的實例

    這篇文章主要介紹了React中使用setInterval函數(shù)的實例,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • React-Native實現(xiàn)ListView組件之上拉刷新實例(iOS和Android通用)

    React-Native實現(xiàn)ListView組件之上拉刷新實例(iOS和Android通用)

    本篇文章主要介紹了React-Native實現(xiàn)ListView組件之上拉刷新實例(iOS和Android通用),具有一定的參考價值,有興趣的可以了解一下
    2017-07-07
  • React實現(xiàn)雙滑塊交叉滑動

    React實現(xiàn)雙滑塊交叉滑動

    這篇文章主要為大家詳細介紹了React實現(xiàn)雙滑塊交叉滑動,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • React Query + REST API 最佳實踐

    React Query + REST API 最佳實踐

    本文介紹了如何利用React Query構(gòu)建React應(yīng)用的REST API數(shù)據(jù)層,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2026-06-06
  • react函數(shù)組件useState異步,數(shù)據(jù)不能及時獲取到的問題

    react函數(shù)組件useState異步,數(shù)據(jù)不能及時獲取到的問題

    這篇文章主要介紹了react函數(shù)組件useState異步,數(shù)據(jù)不能及時獲取到的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • 淺析JS中什么是自定義react數(shù)據(jù)驗證組件

    淺析JS中什么是自定義react數(shù)據(jù)驗證組件

    我們在做前端表單提交時,經(jīng)常會遇到要對表單中的數(shù)據(jù)進行校驗的問題。這篇文章主要介紹了js中什么是自定義react數(shù)據(jù)驗證組件,需要的朋友可以參考下
    2018-10-10
  • antd form表單如何處理自定義組件

    antd form表單如何處理自定義組件

    這篇文章主要介紹了antd form表單如何處理自定義組件問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • react實現(xiàn)Modal彈窗效果

    react實現(xiàn)Modal彈窗效果

    這篇文章主要為大家詳細介紹了react實現(xiàn)Modal彈窗效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08

最新評論

临泉县| 张掖市| 铅山县| 霍州市| 鄢陵县| 策勒县| 镇赉县| 顺昌县| 黑龙江省| 紫金县| 临城县| 衡水市| 大同市| 磴口县| 景泰县| 贵南县| 刚察县| 武汉市| 乌拉特前旗| 湘阴县| 霍邱县| 曲周县| 理塘县| 宜城市| 西城区| 乐业县| 平乡县| 茌平县| 上虞市| 荔浦县| 台南县| 长乐市| 巴南区| 屏边| 嵊泗县| 华池县| 汾阳市| 平阴县| 兴安县| 岳普湖县| 宁夏|