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

React處理高頻的實(shí)時(shí)數(shù)據(jù)的解決方案

 更新時(shí)間:2025年11月19日 08:24:09   作者:mCell  
本文介紹了在開發(fā)一個(gè)實(shí)時(shí)日志查看器時(shí)遇到的React錯(cuò)誤Maximum update depth exceeded(超過最大更新深度),并分析了問題的根源,解決了這個(gè)問題,需要的朋友可以參考下

最近,我遇到了一個(gè)很有意思的 React 問題。

我需要開發(fā)一個(gè)實(shí)時(shí)的日志查看器,功能上需要實(shí)時(shí)展示服務(wù)運(yùn)行的日志。因?yàn)檫@個(gè)項(xiàng)目是內(nèi)部的,我這里大概抽象一下:

后端使用 SSE(Server-Sent Events) 技術(shù),源源不斷地把日志推送給前端。

當(dāng)日志一條一條、不緊不慢地過來時(shí),一切正常。

但是,當(dāng)我預(yù)覽一個(gè)已經(jīng)完成的任務(wù)日志時(shí),網(wǎng)頁卡頓了一下。瀏覽器控制臺(tái)顯示了一個(gè) React 開發(fā)者很熟悉的錯(cuò)誤:

Uncaught Error: Maximum update depth exceeded... (錯(cuò)誤:超過最大更新深度)

這個(gè)錯(cuò)誤通常意味著,存在什么組件陷入了無限循環(huán)。比如,組件的渲染函數(shù)里直接調(diào)用了 setState,導(dǎo)致“渲染 → 更新狀態(tài) → 觸發(fā)渲染 → ...”的死循環(huán)。

比如這樣:

export default function Demo() {
  const [count, setCount] = useState(0)
  setCount(count + 1)
  return <h1>Count: {count}</h1>
}

但我的代碼并沒有這樣的邏輯,該使用 useEffect 的地方都使用了。我只是在 SSE 的事件回調(diào)里更新狀態(tài)。

// 示意代碼
const source = new EventSource("/api/logs")
source.addEventListener("log", (event) => {
  // 每來一條日志,就調(diào)用 set 函數(shù)
  appendLog(event.data)
})

那么,問題出在哪里呢?

問題的根源:高頻更新

起初我以為是哪里的更新邏輯不對(duì),讓 claude 排查很久都沒找到具體問題。在給現(xiàn)有函數(shù)增加了不少緩存,比如useMemo,useCallback,甚至 React.memo 都使用上了,仍舊沒有解決這個(gè)報(bào)錯(cuò)。

代碼沒有問題,那么問題就應(yīng)該出現(xiàn)在一些極端場景導(dǎo)致的高頻渲染。比如網(wǎng)絡(luò)?我才打開控制臺(tái)的網(wǎng)絡(luò)部分,看到幾乎在很短時(shí)間內(nèi),上百條的 log 被推送過來!

到這里問題就和清晰了:當(dāng)服務(wù)器在短時(shí)間內(nèi)(比如 1 秒內(nèi))推送上百條日志時(shí),每一個(gè) log 都觸發(fā)了 React 進(jìn)行重新渲染,這里觸發(fā)了 React 的某些機(jī)制,React 對(duì)這種行為發(fā)出了報(bào)錯(cuò)。

React 內(nèi)部有一個(gè)“嵌套更新計(jì)數(shù)器”,用來防止無限循環(huán)。

簡單說,如果在一次渲染(Render)的過程中,又因?yàn)槟承┰蛴|發(fā)了新的狀態(tài)更新,這就叫“嵌套更新”。當(dāng)這個(gè)次數(shù)短時(shí)間內(nèi)超過一個(gè)閾值(通常是 50 次),React 就會(huì)認(rèn)為你“可能”寫了一個(gè) Bug,于是主動(dòng)拋出錯(cuò)誤,終止程序。

我們的問題就出在這里。SSE 的事件回調(diào)來得太快了。

當(dāng)服務(wù)器在 1 秒內(nèi)推送 150 條日志時(shí),瀏覽器的事件循環(huán)會(huì)瘋狂執(zhí)行回調(diào):

  1. SSE 事件 1 抵達(dá) → appendLog() → 觸發(fā) React 更新(第 1 次)
  2. React 還沒來得及渲染,SSE 事件 2 抵達(dá) → appendLog() → 觸發(fā) React 更新(第 2 次)
  3. ...
  4. SSE 事件 50 抵達(dá) → appendLog() → 觸發(fā) React 更新(第 50 次)
  5. SSE 事件 51 抵達(dá) → appendLog() → 觸發(fā) React 更新(第 51 次)

在 React 看來,這 51 次更新幾乎是“同時(shí)”發(fā)生的,它無法分辨這是“51 條獨(dú)立日志”還是“一個(gè)死循環(huán)”。為了保護(hù)自己,它選擇了報(bào)錯(cuò)。

問題的本質(zhì)是:數(shù)據(jù)接收的頻率(高頻)和 React 狀態(tài)更新的頻率(低頻)不匹配。

我們不能每收到一條數(shù)據(jù),就立刻更新一次狀態(tài)。

后續(xù)我了解到 React 18 版本對(duì)高頻渲染的問題進(jìn)行了優(yōu)化,但它目前僅適用于 React 事件處理函數(shù)內(nèi)的同步更新。對(duì)于 SSE 回調(diào)、fetch 回調(diào)、setInterval 等異步事件源觸發(fā)的更新,仍需手動(dòng)實(shí)現(xiàn)批處理。

解決方案:批處理(Batching)

既然不能一條一條地更新,那很自然就想到,能不能把日志“攢一下”,再一次性提交給 React?

這就是“批處理”(Batching)思想。

我們不再是“來一條,更新一次”,而是“來 N 條,更新一次”。

實(shí)現(xiàn)這個(gè)功能的關(guān)鍵,是需要一個(gè)“緩沖區(qū)”(Buffer)和一個(gè)“定時(shí)器”(Timer)。

  1. 緩沖區(qū):需要一個(gè)地方暫存日志,但這個(gè)地方本身不能是 React 的 state(否則又觸發(fā)渲染了)。useRef 是最合適的人選。
  2. 定時(shí)器:需要一個(gè)機(jī)制,在“攢”日志的間隙,把它們統(tǒng)一提交。setTimeout(..., 0) 是這里的法寶。

代碼實(shí)現(xiàn)

我們來改造一下 log 事件的處理。

首先,在組件里定義緩沖區(qū)和定時(shí)器:

export default function LogPage() {
  // 1. 從 store 獲取批量更新的方法
  const appendLogs = useLogStore((state) => state.appendLogs)

  // 2. 批處理緩沖區(qū)(使用 ref 不會(huì)觸發(fā)渲染)
  const batchBufferRef = useRef([])

  // 3. 定時(shí)器引用(保證只有一個(gè)定時(shí)器在運(yùn)行)
  const batchTimerRef = useRef(null)

  // ...
}

其次,實(shí)現(xiàn)一個(gè)“提交緩沖區(qū)”的函數(shù) flushBatch

// 4. 批量提交函數(shù)
const flushBatch = useCallback(() => {
  // 如果緩沖區(qū)有數(shù)據(jù)
  if (batchBufferRef.current.length > 0) {
    // 一次性提交給 store
    appendLogs(batchBufferRef.current)
    // 清空緩沖區(qū)
    batchBufferRef.current = []
  }
  // 重置定時(shí)器引用
  batchTimerRef.current = null
}, [appendLogs]) // 依賴 appendLogs

最后,修改 SSE 的事件處理函數(shù) handleLogEvent

// 5. 新的 SSE 事件處理函數(shù)
const handleLogEvent = useCallback(
  (event) => {
    const entry = {
      /* ...解析日志... */
    }

    // 重點(diǎn):不再直接調(diào)用 appendLog
    // 而是將日志加入緩沖區(qū)
    batchBufferRef.current.push(entry)

    // 如果還沒有計(jì)劃批處理,則在下一個(gè)事件循環(huán)中執(zhí)行
    if (batchTimerRef.current === null) {
      batchTimerRef.current = window.setTimeout(flushBatch, 0)
    }
  },
  [flushBatch] // 依賴 flushBatch
)

為什么是setTimeout(..., 0)?

你可能會(huì)問,為什么是 setTimeout(..., 0)?

這是一個(gè)很巧妙的技巧。它并不是真的“延遲 0 毫秒”,而是告訴瀏覽器:“請(qǐng)?jiān)诋?dāng)前這一輪事件循環(huán)(Event Loop)的同步代碼都執(zhí)行完之后,再執(zhí)行這個(gè) flushBatch 函數(shù)。”

當(dāng) 150 條日志在短時(shí)間內(nèi)涌入時(shí),會(huì)發(fā)生什么?

  1. 事件 1 抵達(dá) → push 到緩沖區(qū) → setTimeout 注冊(cè)一個(gè) flushBatch 回調(diào)。
  2. 事件 2 抵達(dá) → push 到緩沖區(qū) → 檢查定時(shí)器,發(fā)現(xiàn)已有,跳過。
  3. 事件 3 抵達(dá) → push 到緩沖區(qū) → 跳過。
  4. ...
  5. 事件 150 抵達(dá) → push 到緩沖區(qū) → 跳過。
  6. (當(dāng)前宏任務(wù)結(jié)束,所有同步代碼執(zhí)行完畢)
  7. 瀏覽器從任務(wù)隊(duì)列中取出 flushBatch 回調(diào),執(zhí)行。
  8. flushBatch 函數(shù)將 150 條日志一次性提交給 React。

于是,150 次 setState 調(diào)用,被神奇地合并成了 1 次。應(yīng)用流暢如初。

到此這篇關(guān)于React處理高頻的實(shí)時(shí)數(shù)據(jù)的解決方案的文章就介紹到這了,更多相關(guān)React處理高頻實(shí)時(shí)數(shù)據(jù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • React使用useSearchParams同步URL和查詢參數(shù)的方法

    React使用useSearchParams同步URL和查詢參數(shù)的方法

    在開發(fā)React應(yīng)用時(shí),我們經(jīng)常遇到一種場景:用戶在搜索框輸入關(guān)鍵詞,篩選出一個(gè)列表,然后希望把這個(gè)結(jié)果分享給同事,在React Router v6中,useSearchParams這個(gè)Hook就是專門用來處理這個(gè)問題的,本文將介紹如何使用它來實(shí)現(xiàn) URL 與應(yīng)用狀態(tài)的同步,需要的朋友可以參考下
    2025-12-12
  • React?高階組件與Render?Props優(yōu)缺點(diǎn)詳解

    React?高階組件與Render?Props優(yōu)缺點(diǎn)詳解

    這篇文章主要weidajai?介紹了React?高階組件與Render?Props優(yōu)缺點(diǎn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • 在React頁面重新加載時(shí)保留數(shù)據(jù)的實(shí)現(xiàn)方法總結(jié)

    在React頁面重新加載時(shí)保留數(shù)據(jù)的實(shí)現(xiàn)方法總結(jié)

    在React頁面重新加載時(shí)保留數(shù)據(jù),可以通過多種方法來實(shí)現(xiàn),常見的方法包括使用瀏覽器的本地存儲(chǔ)(Local Storage 或 Session Storage)、URL參數(shù)、以及服務(wù)器端存儲(chǔ)等,本文給大家總結(jié)了一些具體實(shí)現(xiàn)方法,需要的朋友可以參考下
    2024-06-06
  • 詳解如何優(yōu)雅地在React項(xiàng)目中使用Redux

    詳解如何優(yōu)雅地在React項(xiàng)目中使用Redux

    這篇文章主要介紹了詳解如何優(yōu)雅地在React項(xiàng)目中使用Redux,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-12-12
  • ReactJS入門實(shí)例教程詳解

    ReactJS入門實(shí)例教程詳解

    React.render?是?React?的最基本方法,用于將模板轉(zhuǎn)為?HTML?語言,并插入指定的?DOM?節(jié)點(diǎn),這篇文章主要介紹了ReactJS入門實(shí)例教程,需要的朋友可以參考下
    2022-06-06
  • React組件三大核心屬性State?props?Refs介紹

    React組件三大核心屬性State?props?Refs介紹

    組件實(shí)例的三大核心屬性是:State、Props、Refs。類組件中這三大屬性都存在。函數(shù)式組件中訪問不到?this,也就不存在組件實(shí)例這種說法,但由于它的特殊性(函數(shù)可以接收參數(shù)),所以存在Props這種屬性
    2023-02-02
  • react?源碼中位運(yùn)算符的使用詳解

    react?源碼中位運(yùn)算符的使用詳解

    這篇文章主要為大家詳細(xì)介紹了react?位運(yùn)算符,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • React用法之高階組件的用法詳解

    React用法之高階組件的用法詳解

    高階組件也就是我們常說的HOC,是React中用于復(fù)用組件邏輯的一種高級(jí)技巧。這篇文章主要通過一些示例帶大家學(xué)習(xí)一下高階組件的使用,希望對(duì)大家有所幫助
    2023-04-04
  • React 18版本配置rem 和 vw的詳細(xì)步驟

    React 18版本配置rem 和 vw的詳細(xì)步驟

    這篇文章主要介紹了React 18版本配置rem 和 vw的詳細(xì)步驟,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2024-01-01
  • React 全自動(dòng)數(shù)據(jù)表格組件——BodeGrid的實(shí)現(xiàn)思路

    React 全自動(dòng)數(shù)據(jù)表格組件——BodeGrid的實(shí)現(xiàn)思路

    表格是在后臺(tái)管理系統(tǒng)中用的最頻繁的組件之一,相關(guān)的功能有數(shù)據(jù)的新增和編輯、查詢、排序、分頁、自定義顯示以及一些操作按鈕。這篇文章主要介紹了React 全自動(dòng)數(shù)據(jù)表格組件——BodeGrid ,需要的朋友可以參考下
    2019-06-06

最新評(píng)論

汶上县| 陆丰市| 商城县| 资源县| 赤城县| 惠州市| 中牟县| 什邡市| 唐河县| 古田县| 鹿泉市| 于田县| 孟津县| 乌鲁木齐市| 赤水市| 石棉县| 开阳县| 江津市| 邯郸县| 乃东县| 乐业县| 日土县| 乌拉特前旗| 德令哈市| 五台县| 农安县| 南郑县| 吐鲁番市| 张掖市| 镇沅| 瓮安县| 固镇县| 浮山县| 定陶县| 泗水县| 牡丹江市| 阿坝县| 滨州市| 建始县| 南昌市| 棋牌|