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

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

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

先說結(jié)論,React 選擇 MessageChannel 完成事件調(diào)度,是因?yàn)樗?/strong>

  • 屬于宏任務(wù)(不會(huì)餓死瀏覽器:JavaScript 一直占著主線程,導(dǎo)致瀏覽器一直沒有機(jī)會(huì)去做它必須做的事(渲染、響應(yīng)輸入、布局、繪制))
  • 延遲極低(接近微任務(wù),但不會(huì)阻塞渲染)
  • 相較于 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)只有是:在不阻塞瀏覽器的前提下,盡可能多地推進(jìn) Fiber 渲染進(jìn)度。

所以 Scheduler 需要滿足:

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

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

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

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

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

二、React Scheduler 源碼(React 19)

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

核心邏輯(簡(jiǎn)化):

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ù) && 沒超時(shí)) {
  執(zhí)行 Fiber work
}

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

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

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

Promise.resolve().then(workLoop)

問題 1:會(huì)阻塞渲染

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

意味著:

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

這完全就是 Fiber 的“時(shí)間切片”的對(duì)立做法。

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

  • 微任務(wù)一旦開始
  • 瀏覽器必須清空
  • React 無法“讓出主線程”,更沒法實(shí)現(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 在嵌套層級(jí)超過 5 層,timeout(延時(shí))如果小于 4ms,那么則會(huì)設(shè)置為 4ms,這個(gè)時(shí)差是 React 無法接受的。

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

五、為什么不用 requestAnimationFrame(rAF)

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

一幀 ≈ 16.6ms

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

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

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

六、還得是 MessageChannel ~

MessageChannel 是什么?

const channel = new MessageChannel();
// 兩個(gè)頻道端口,這兩個(gè)端口可以相互通信
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ī)避掉上述一系列缺點(diǎn):

MessageChannel + shouldYield => 時(shí)間切片。

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

shouldYield()

判斷依據(jù):

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

如果該讓出:

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

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

七、彩蛋來咯

1、requestAnimationFrame

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

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

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

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

rAF 回調(diào)永遠(yuǎn)不會(huì)擠占渲染時(shí)機(jī),只會(huì)“對(duì)齊”渲染節(jié)奏

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

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

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

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

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

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

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

setTimeout(step, 16)

發(fā)生了什么?

Task A (20ms)  超過 16ms

setTimeout 回調(diào)排隊(duì)

Task B (又 20ms)

Task C ...

后果是:

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

如果改為 rAF:

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

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

2、用時(shí)間軸演示卡頓

卡頓:場(chǎng)景一

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

Task 200ms?Task 200ms?Task 200ms

結(jié)果:

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

rAF 無解

類型二:?jiǎn)螏紶柍瑫r(shí)(可恢復(fù)卡頓)

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

結(jié)果:

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

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

卡頓:場(chǎng)景二

假設(shè)場(chǎng)景

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

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

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

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

第 2 幀(開始積壓)

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

第 N 幀(雪崩)

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

表現(xiàn)為:

  • JS 一直在跑
  • 瀏覽器幾乎沒有 Render 機(jī)會(huì)
  • 頁(yè)面看起來 卡住不動(dòng)
  • CPU 占滿

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

不管:

  • 主線程忙不忙
  • 能不能渲染
  • 用戶是不是在滾動(dòng) / 點(diǎn)擊

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

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

這意味著:錯(cuò)過的幀會(huì)變成多余的 JS 工作量

3、用戶體感 vs setTimeout

setTimeout(雪崩)

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

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

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

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

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

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

相關(guān)文章

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

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

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

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

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

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

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

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

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

    React實(shí)現(xiàn)雙滑塊交叉滑動(dòng)

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

    React Query + REST API 最佳實(shí)踐

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

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

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

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

    我們?cè)谧銮岸吮韱翁峤粫r(shí),經(jīng)常會(huì)遇到要對(duì)表單中的數(shù)據(jù)進(jìn)行校驗(yàn)的問題。這篇文章主要介紹了js中什么是自定義react數(shù)據(jù)驗(yàn)證組件,需要的朋友可以參考下
    2018-10-10
  • antd form表單如何處理自定義組件

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

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

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

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

最新評(píng)論

榆社县| 天水市| 威远县| 肥城市| 咸丰县| 永平县| 中西区| 敦煌市| 灵丘县| 任丘市| 鄄城县| 和田市| 麻江县| 普格县| 广宁县| 皋兰县| 弋阳县| 方山县| 盐城市| 北辰区| 深泽县| 加查县| 竹北市| 九龙县| 无棣县| 通化市| 遵义县| 兖州市| 东山县| 东莞市| 阳曲县| 井冈山市| 赤城县| 北海市| 江门市| 镇安县| 恭城| 读书| 贡山| 云浮市| 乌兰浩特市|