React 事件系統(tǒng)addTrappedEventListener的使用
React 合成事件系統(tǒng)最底層 DOM 監(jiān)聽器創(chuàng)建與綁定核心函數(shù)
- 生成 React 封裝的高優(yōu)先級(jí)事件回調(diào)
- 自動(dòng)區(qū)分 touch/wheel 事件開啟 passive 優(yōu)化
- 根據(jù)捕獲 / 冒泡 + 是否 passive 分流調(diào)用對(duì)應(yīng)底層綁定 API
- 生成解綁句柄,供一次性監(jiān)聽自動(dòng)銷毀
完整調(diào)用鏈路全覽
──────────────────────────────────────────────────────────────────────
createRoot(container)
──────────────────────────────────────────────────────────────────────
│
▼
listenToAllSupportedEvents(container)
│
├── (container._reactListening = true)
│
├── allNativeEvents.forEach('click'):
│ ├── 'click' ? nonDelegatedEvents
│ │ └── listenToNativeEvent('click', false, container)
│ │ └── addTrappedEventListener(container, 'click', 0, false)
│ │ ├── createEventListenerWrapperWithPriority(container, 'click', 0)
│ │ │ └── getEventPriority('click') → DiscreteEventPriority
│ │ │ └── dispatchDiscreteEvent.bind(null, 'click', 0, container)
│ │ ├── passiveBrowserEventsSupported? Yes, but click ∈ [touchstart,touchmove,wheel]? No
│ │ │ └── isPassiveListener = undefined
│ │ ├── isCapturePhaseListener? false → addEventBubbleListener
│ │ │ └── container.addEventListener('click', wrapper, false)
│ │ └── return listener (unsubscribe)
│ │
│ └── listenToNativeEvent('click', true, container)
│ └── addTrappedEventListener(container, 'click', IS_CAPTURE_PHASE, true)
│ ├── dispatchDiscreteEvent.bind(null, 'click', IS_CAPTURE_PHASE, container)
│ ├── isCapturePhaseListener? true → addEventCaptureListener
│ │ └── container.addEventListener('click', wrapper, true)
│ └── return listener
│
├── allNativeEvents.forEach('touchstart'):
│ ├── listenToNativeEvent('touchstart', false, container)
│ │ └── addTrappedEventListener(container, 'touchstart', 0, false)
│ │ ├── isPassiveListener = true (domEventName === 'touchstart')
│ │ ├── isCapturePhaseListener? false, isPassiveListener? true
│ │ │ └── addEventBubbleListenerWithPassiveFlag(container, 'touchstart', wrapper, true)
│ │ │ └── container.addEventListener('touchstart', wrapper, { passive: true })
│ │ └── return listener
│ │
│ └── listenToNativeEvent('touchstart', true, container)
│ └── addTrappedEventListener(container, 'touchstart', IS_CAPTURE_PHASE, true)
│ ├── isCapturePhaseListener? true, isPassiveListener? true
│ │ └── addEventCaptureListenerWithPassiveFlag(container, 'touchstart', wrapper, true)
│ │ └── container.addEventListener('touchstart', wrapper, { capture: true, passive: true })
│ └── return listener
│
└── ownerDocument = document
└── listenToNativeEvent('selectionchange', false, document)
└── addTrappedEventListener(document, 'selectionchange', 0, false)
├── getEventPriority('selectionchange') → DiscreteEventPriority
├── isPassiveListener? false (non-passive)
└── document.addEventListener('selectionchange', wrapper, false)
──────────────────────────────────────────────────────────────────────
mount(<img onLoad={fn} />)
──────────────────────────────────────────────────────────────────────
│
└── ReactDOMComponent.finalizeInitialChildren
└── listenToNonDelegatedEvent('load', imgElement)
├── listenerSet = imgElement.__reactEvents || new Set()
├── listenerSetKey = "load__bubble"
├── Set has "load__bubble"? No
│
└── addTrappedEventListener(imgElement, 'load', IS_NON_DELEGATED, false)
├── createEventListenerWrapperWithPriority(imgElement, 'load', IS_NON_DELEGATED)
│ └── getEventPriority('load') → DefaultEventPriority
│ └── dispatchEvent.bind(null, 'load', IS_NON_DELEGATED, imgElement)
├── isPassiveListener = undefined (load ? [touchstart,touchmove,wheel])
└── imgElement.addEventListener('load', wrapper, false)
└── listenerSet.add("load__bubble")源碼解析
步驟 ①:創(chuàng)建優(yōu)先級(jí)包裝器
生成非原生、帶 React 調(diào)度能力的攔截回調(diào):原生 DOM 觸發(fā)事件時(shí),不會(huì)直接執(zhí)行組件 onClick,而是先走這個(gè) wrapper。把原生 DOM 事件與 ``React 內(nèi)部渲染調(diào)度、合成事件機(jī)制解耦,所有底層監(jiān)聽統(tǒng)一使用標(biāo)準(zhǔn)化包裝函數(shù),上層不用關(guān)心調(diào)度細(xì)節(jié)
let listener = createEventListenerWrapperWithPriority( targetContainer, domEventName, eventSystemFlags, );
步驟 ②:被動(dòng)事件優(yōu)化
瀏覽器支持 passive 配置時(shí),滾動(dòng) / 觸摸類事件強(qiáng)制設(shè)為 passive: true。自動(dòng)做滾動(dòng)性能優(yōu)化,開發(fā)者無需手動(dòng)配置,抹平瀏覽器滾動(dòng)卡頓缺陷
let isPassiveListener = undefined;
if (passiveBrowserEventsSupported) {
if (
domEventName === 'touchstart' ||
domEventName === 'touchmove' ||
domEventName === 'wheel'
) {
isPassiveListener = true;
}
}
// 特性檢測(cè):瀏覽器是否支持 { passive: true } 選項(xiàng)
if (canUseDOM) {
try {
const options = {};
Object.defineProperty(options, 'passive', {
get: function () {
passiveBrowserEventsSupported = true; // getter 觸發(fā) → 瀏覽器支持
},
});
window.addEventListener('test', options, options);
window.removeEventListener('test', options, options);
} catch (e) {
passiveBrowserEventsSupported = false;
}
}
檢測(cè)原理
addEventListener 第三個(gè)參數(shù)可以傳入 { passive: true }。如果瀏覽器支持,options.passive 的 getter 會(huì)被觸發(fā),passiveBrowserEventsSupported 設(shè)為 true。如果瀏覽器不支持此選項(xiàng)(如 IE ≤ 11),catch 塊設(shè)為 false
設(shè)計(jì)意義
Chrome 56+ 將 touchstart/touchmove/wheel 的 document 級(jí)監(jiān)聽器默認(rèn)設(shè)為 { passive: true }(因?yàn)殚_發(fā)者幾乎不會(huì)在 touch 事件上調(diào)用 preventDefault 但必須等待 JS 執(zhí)行判斷是否調(diào)用)。React 19 不把事件綁到 document 而綁到 rootContainer,手動(dòng)模擬這個(gè) passive 行為以獲得相同性能收益
步驟 ③:選擇 addEventListener 重載
根據(jù)捕獲 / 冒泡 + 是否 passive 分流到 4 個(gè)底層綁定工具,統(tǒng)一管理 DOM addEventListener 參數(shù)差異,保存解綁函數(shù),支撐一次性監(jiān)聽自動(dòng)移除,為后續(xù)全局卸載根容器事件提供基礎(chǔ)能力
if (isCapturePhaseListener) {
// 捕獲階段
if (isPassiveListener !== undefined) {
unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
targetContainer, domEventName, listener, isPassiveListener,
);
} else {
unsubscribeListener = addEventCaptureListener(
targetContainer, domEventName, listener,
);
}
} else {
// 冒泡階段
if (isPassiveListener !== undefined) {
unsubscribeListener = addEventBubbleListenerWithPassiveFlag(
targetContainer, domEventName, listener, isPassiveListener,
);
} else {
unsubscribeListener = addEventBubbleListener(
targetContainer, domEventName, listener,
);
}
}
export function addEventBubbleListener(
target,
eventType,
listener,
) {
target.addEventListener(eventType, listener, false);
return listener;
}
export function addEventCaptureListener(
target,
eventType,
listener,
) {
target.addEventListener(eventType, listener, true);
return listener;
}
export function addEventCaptureListenerWithPassiveFlag(
target,
eventType,
listener,
passive,
) {
target.addEventListener(eventType, listener, {
capture: true,
passive,
});
return listener;
}
export function addEventBubbleListenerWithPassiveFlag(
target,
eventType,
listener,
passive,
) {
target.addEventListener(eventType, listener, {
passive,
});
return listener;
}
四種組合
- 捕獲 + passive(touch/wheel)
- 捕獲 + 無 passive(普通事件如 clickCapture)
- 冒泡 + passive(touch/wheel 常規(guī)監(jiān)聽)
- 冒泡 + 無 passive(常規(guī) click/input 等)
| isCapturePhaseListener | isPassiveListener | 調(diào)用的函數(shù) | DOM 參數(shù) |
|---|---|---|---|
| true | true | addEventCaptureListenerWithPassiveFlag | { capture: true, passive: true } |
| true | undefined | addEventCaptureListener | true |
| false | true | addEventBubbleListenerWithPassiveFlag | { passive: true } |
| false | undefined | addEventBubbleListener | false |
輔助函數(shù)
創(chuàng)建優(yōu)先級(jí)事件監(jiān)聽器:createEventListenerWrapperWithPriority
export function createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags,
) {
const eventPriority = getEventPriority(domEventName);
let listenerWrapper;
switch (eventPriority) {
case DiscreteEventPriority:
listenerWrapper = dispatchDiscreteEvent;
break;
case ContinuousEventPriority:
listenerWrapper = dispatchContinuousEvent;
break;
case DefaultEventPriority:
default:
listenerWrapper = dispatchEvent;
break;
}
return listenerWrapper.bind(
null,
domEventName,
eventSystemFlags,
targetContainer,
);
}
作用
- 調(diào)用 getEventPriority 根據(jù)事件名稱區(qū)分事件優(yōu)先級(jí),React 并發(fā)渲染調(diào)度的核心依據(jù)
- DiscreteEventPriority:離散高優(yōu)事件(click、keydown、input、focus 等,用戶即時(shí)交互,需要同步響應(yīng),可打斷渲染)
- ContinuousEventPriority:連續(xù)高頻事件(mousemove、scroll、touchmove 等,持續(xù)觸發(fā),低打斷權(quán)重)
- DefaultEventPriority:默認(rèn)普通優(yōu)先級(jí)(其余事件)
- 按事件類型分流調(diào)度邏輯,實(shí)現(xiàn) React 并發(fā)渲染的優(yōu)先級(jí)機(jī)制,不同交互行為擁有不同渲染搶占能力
- 離散高優(yōu)事件:使用 dispatchDiscreteEvent。標(biāo)記離散更新、同步刷新、允許中斷后臺(tái)渲染,保證點(diǎn)擊、輸入即時(shí)響應(yīng)
- 連續(xù)高頻事件:使用 dispatchContinuousEvent。走連續(xù)調(diào)度,合并多次更新,避免高頻觸發(fā)造成頁(yè)面卡頓
- 默認(rèn) / 其他事件:兜底 dispatchEvent,標(biāo)準(zhǔn)合成事件流程
- bind 預(yù)綁定固定參數(shù),返回最終監(jiān)聽回調(diào):把靜態(tài)不變的配置(事件名、捕獲標(biāo)記、根容器)提前固化,不用每次事件觸發(fā)再傳遞 / 查找,減少運(yùn)行時(shí)計(jì)算開銷;同時(shí)統(tǒng)一分發(fā)函數(shù)入?yún)⒁?guī)范,下層分發(fā)邏輯無需額外從 DOM 讀取信息
- 預(yù)固化三個(gè)前置參數(shù):domEventName、eventSystemFlags、targetContainer
- 當(dāng)瀏覽器觸發(fā)事件執(zhí)行回調(diào)時(shí),原生 Event 對(duì)象會(huì)作為第四個(gè)參數(shù)傳入分發(fā)函數(shù)
事件優(yōu)先級(jí)映射(getEventPriority)
| 優(yōu)先級(jí) | 代表事件 | 包裝器 |
|---|---|---|
| DiscreteEventPriority | click, keydown, focusin, input, mousedown, touchstart, submit, toggle, beforetoggle, cancel, close, paste, copy, cut, contextmenu, auxclick, dblclick, dragend, dragstart, drop, invalid, keypress, keyup, mouseup, pause, play, pointercancel, pointerdown, pointerup, ratechange, reset, seeked, touchcancel, touchend, volumechange, change, selectionchange, beforeblur, afterblur, blur, focus, hashchange, popstate, select, selectstart, beforeinput, fullscreenchange, fullscreenerror | dispatchDiscreteEvent |
| ContinuousEventPriority | mousemove, mouseout, mouseover, pointermove, pointerout, pointerover, drag, dragenter, dragexit, dragleave, dragover, resize, scroll, touchmove, wheel, mouseenter, mouseleave, pointerenter, pointerleave | dispatchContinuousEvent |
| DefaultEventPriority | load, error, animationend, transitionend, message(特定調(diào)度優(yōu)先級(jí))等不匹配上述分類的事件 | dispatchEvent |
離散事件的調(diào)度包裝器:dispatchDiscreteEvent
- 臨時(shí)提升渲染優(yōu)先級(jí),保障交互即時(shí)性:針對(duì) click/input/keydown 等離散用戶交互,把事件內(nèi)所有狀態(tài)更新強(qiáng)制設(shè)為最高優(yōu)先級(jí),能夠打斷后臺(tái)正在執(zhí)行的低優(yōu)先級(jí)渲染,解決點(diǎn)擊延遲、輸入卡頓問題,是 React 并發(fā)渲染交互流暢性的核心保障
- 隔離 startTransition 低優(yōu)上下文:強(qiáng)制清空全局 Transition 標(biāo)記,確保離散交互不受 startTransition 降級(jí)邏輯影響,緊急操作永遠(yuǎn)優(yōu)先渲染
- 上下文壓棧 + 自動(dòng)恢復(fù),無全局污染:采用先緩存現(xiàn)場(chǎng) → 修改全局環(huán)境 → 執(zhí)行事件邏輯 → 無論成敗強(qiáng)制還原的范式,不會(huì)篡改外部代碼的優(yōu)先級(jí)、Transition 環(huán)境,多個(gè)事件嵌套、并發(fā)執(zhí)行時(shí)上下文互不干擾
function dispatchDiscreteEvent(
domEventName,
eventSystemFlags,
container,
nativeEvent,
) {
const prevTransition = ReactSharedInternals.T;
ReactSharedInternals.T = null;
const previousPriority = getCurrentUpdatePriority();
try {
setCurrentUpdatePriority(DiscreteEventPriority);
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
} finally {
setCurrentUpdatePriority(previousPriority);
ReactSharedInternals.T = prevTransition;
}
}
設(shè)計(jì)意義
即使當(dāng)前代碼處在 startTransition 內(nèi)部,點(diǎn)擊 / 輸入這類緊急交互也要走高優(yōu)渲染,不能被降級(jí)延遲,事件執(zhí)行過程中產(chǎn)生的所有 setState 更新,都會(huì)標(biāo)記為高優(yōu),可以中斷正在進(jìn)行的后臺(tái)渲染,保證交互即時(shí)響應(yīng)
連續(xù)事件的調(diào)度包裝器:dispatchContinuousEvent
- 設(shè)置連續(xù)事件專屬調(diào)度優(yōu)先級(jí),平衡流暢性與性能針對(duì)滾動(dòng)、鼠標(biāo)移動(dòng)、拖拽、滾輪等每秒多次觸發(fā)的高頻事件,使用中等優(yōu)先級(jí):
- 不會(huì)像離散點(diǎn)擊那樣強(qiáng)行搶占渲染;
- 多個(gè)連續(xù)更新會(huì)被調(diào)度器合并,減少重復(fù)渲染開銷;
- 當(dāng)同時(shí)發(fā)生點(diǎn)擊輸入時(shí),離散事件可以打斷滾動(dòng)渲染,保證用戶主動(dòng)交互優(yōu)先
- 剝離 startTransition 低優(yōu)上下文:清空全局 Transition 標(biāo)記,滾動(dòng)相關(guān)更新不會(huì)被當(dāng)作延遲渲染任務(wù),避免滾動(dòng)滯后、畫面掉幀
- 安全隔離全局調(diào)度環(huán)境,無副作用污染:采用緩存現(xiàn)場(chǎng) → 臨時(shí)覆蓋 → 最終強(qiáng)制還原的棧式上下文模式,單次事件執(zhí)行僅局部生效,函數(shù)退出后全局狀態(tài)完全復(fù)原,異常場(chǎng)景也不會(huì)破壞全局調(diào)度規(guī)則
function dispatchContinuousEvent(
domEventName,
eventSystemFlags,
container,
nativeEvent,
) {
const prevTransition = ReactSharedInternals.T;
ReactSharedInternals.T = null;
const previousPriority = getCurrentUpdatePriority();
try {
setCurrentUpdatePriority(ContinuousEventPriority);
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
} finally {
setCurrentUpdatePriority(previousPriority);
ReactSharedInternals.T = prevTransition;
}
}
設(shè)計(jì)意義
滾動(dòng)、鼠標(biāo)移動(dòng)這類持續(xù)交互,不降級(jí)為 transition 更新,使用專屬連續(xù)優(yōu)先級(jí)(優(yōu)先級(jí)低于點(diǎn)擊、輸入等離散事件),后臺(tái)渲染任務(wù)遇到離散交互會(huì)被直接搶占中斷,連續(xù)事件之間會(huì)合并調(diào)度,減少重復(fù)渲染,防止高頻事件卡死頁(yè)面。高頻滾動(dòng)事件不搶占主線程,避免短時(shí)間大量渲染阻塞頁(yè)面
事件分發(fā)中樞控制器:dispatchEvent
針對(duì) SSR 水合阻塞場(chǎng)景做分流處理
- 無未水合 DOM:直接執(zhí)行完整事件流程
- 高頻連續(xù)事件遇阻塞:暫存隊(duì)列、丟棄本次觸發(fā)
- 捕獲階段離散交互事件遇阻塞:同步強(qiáng)制水合組件,再執(zhí)行事件
- 其余普通阻塞場(chǎng)景:直接執(zhí)行事件(不等待水合)
export function dispatchEvent(
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
) {
// 標(biāo)記 React 合成事件系統(tǒng)是否啟用;關(guān)閉則直接跳過所有事件處理
if (!_enabled) {
return;
}
// 查找當(dāng)前觸發(fā)事件的 DOM 節(jié)點(diǎn)鏈上,還未完成水合(hydrate)的 DOM 元素;返回對(duì)應(yīng) DOM,無阻塞節(jié)點(diǎn)返回 null
let blockedOn = findInstanceBlockingEvent(nativeEvent);
if (blockedOn === null) {
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
return_targetInst, // 事件目標(biāo) DOM 對(duì)應(yīng)的 Fiber 實(shí)例
targetContainer,
);
// 清空當(dāng)前連續(xù)事件緩存隊(duì)列,丟棄過期事件
clearIfContinuousEvent(domEventName, nativeEvent);
return;
}
if (
// 連續(xù)事件(scroll/mousemove/touchmove)入隊(duì)
queueIfContinuousEvent(
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
)
) {
nativeEvent.stopPropagation();
return;
}
clearIfContinuousEvent(domEventName, nativeEvent);
if (
eventSystemFlags & IS_CAPTURE_PHASE &&
// 判斷該離散事件是否必須等待水合完成才能執(zhí)行(click、input、keydown 等用戶強(qiáng)交互)
isDiscreteEventThatRequiresHydration(domEventName)
) {
while (blockedOn !== null) {
const fiber = getInstanceFromNode(blockedOn);
if (fiber !== null) {
attemptSynchronousHydration(fiber);
}
const nextBlockedOn = findInstanceBlockingEvent(nativeEvent);
if (nextBlockedOn === null) {
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
return_targetInst,
targetContainer,
);
}
if (nextBlockedOn === blockedOn) {
break;
}
blockedOn = nextBlockedOn;
}
if (blockedOn !== null) {
nativeEvent.stopPropagation();
}
return;
}
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
null,
targetContainer,
);
}
作用
- 事件系統(tǒng)總開關(guān)攔截:全局關(guān)閉合成事件系統(tǒng)時(shí),直接終止事件分發(fā),不執(zhí)行任何后續(xù)邏輯
- 調(diào)用 findInstanceBlockingEvent 查找是否存在未水合阻塞 DOM:從原生事件觸發(fā)目標(biāo)向上遍歷 DOM 樹,查找對(duì)應(yīng) Fiber 未完成水合的 DOM 節(jié)點(diǎn)
- 無任何未水合阻塞節(jié)點(diǎn)(正常流程):直接執(zhí)行事件,傳入目標(biāo) Fiber,清空連續(xù)事件隊(duì)列
- 調(diào)用核心事件執(zhí)行函數(shù) dispatchEventForPluginEventSystem,傳入目標(biāo) DOM 對(duì)應(yīng)的 Fiber 實(shí)例 return_targetInst,完整執(zhí)行合成事件、遍歷 Fiber、執(zhí)行用戶回調(diào)
- clearIfContinuousEvent:清空該事件類型的連續(xù)事件緩存隊(duì)列,直接退出
- 存在阻塞節(jié)點(diǎn),優(yōu)先處理高頻連續(xù)事件:事件入隊(duì)延遲執(zhí)行,停止冒泡,放棄本次分發(fā),不阻塞主線程
- queueIfContinuousEvent 判斷當(dāng)前是否為 scroll/mousemove/touchmove 等連續(xù)高頻事件
- 是連續(xù)事件:將本次事件存入延遲隊(duì)列,等待水合完成后統(tǒng)一批量執(zhí)行;調(diào)用 nativeEvent.stopPropagation 停止原生事件冒泡,不繼續(xù)向下分發(fā),直接退出
- 非連續(xù)事件:執(zhí)行 clearIfContinuousEvent 清空歷史緩存
- queueIfContinuousEvent 判斷當(dāng)前是否為 scroll/mousemove/touchmove 等連續(xù)高頻事件
- 捕獲階段 + 存在阻塞需要強(qiáng)制水合的離散交互事件(click/input 等):同步強(qiáng)制水合所有阻塞組件,水合完成后執(zhí)行事件,若水合失敗則停止冒泡,保證即時(shí)響應(yīng)
- 根據(jù)阻塞 DOM 拿到對(duì)應(yīng) Fiber
- attemptSynchronousHydration:同步阻塞主線程,強(qiáng)制完成該組件水合
- 重新查找阻塞節(jié)點(diǎn) nextBlockedOn
- 如果水合后無阻塞節(jié)點(diǎn),直接執(zhí)行完整事件分發(fā)
- 如果前后阻塞節(jié)點(diǎn)不變,代表無法完成水合,跳出循環(huán)
- 更新 blockedOn,循環(huán)處理下一個(gè)阻塞 DOM
- 循環(huán)結(jié)束后,若仍存在未水合節(jié)點(diǎn),停止原生事件冒泡,直接退出
- 兜底分支:普通冒泡離散事件,存在阻塞但無需同步水合:不阻塞主線程,直接執(zhí)行事件,動(dòng)態(tài)查找 Fiber 兼容半水合狀態(tài)
設(shè)計(jì)意義
SSR/SSG頁(yè)面:HTML先渲染到頁(yè)面,JS加載完成后才會(huì)執(zhí)行水合;此時(shí)點(diǎn)擊DOM,對(duì)應(yīng)組件還未掛載Fiber,無法讀取事件回調(diào),需要特殊兼容- 丟棄過期高頻過渡事件:懸浮、焦點(diǎn)、拖拽類事件觸發(fā)頻率極高,水合階段積壓大量事件會(huì)造成水合完成后主線程卡頓;執(zhí)行有效事件后清空緩存,減少后續(xù)批量執(zhí)行開銷,提升
SSR頁(yè)面交互流暢度 - 高頻滾動(dòng)類事件不需要同步阻塞等待水合,入隊(duì)延遲執(zhí)行,避免主線程卡頓;同時(shí)停止冒泡,防止原生事件繼續(xù)擴(kuò)散
- 捕獲階段的用戶點(diǎn)擊、輸入是強(qiáng)即時(shí)交互,不能延遲;因此強(qiáng)制同步水合組件,保證事件回調(diào)可以正常執(zhí)行,解決
SSR頁(yè)面點(diǎn)擊失效問題 - 普通冒泡離散事件不阻塞主線程同步水合,直接嘗試執(zhí)行事件;框架內(nèi)部動(dòng)態(tài)兼容半水合狀態(tài),平衡交互響應(yīng)速度與主線程阻塞開銷
查找未水合阻塞 DOM:findInstanceBlockingEvent
- 專門識(shí)別三類會(huì)導(dǎo)致事件失效的特殊容器:脫水根、Suspense、Activity
- 判定是否存在水合阻塞容器,存在則返回阻塞 DOM;
- 緩存事件目標(biāo)對(duì)應(yīng)的有效 Fiber 到全局變量,供事件插件系統(tǒng)復(fù)用,避免重復(fù)查找 DOM-Fiber 映射
- 當(dāng)事件觸發(fā)節(jié)點(diǎn)自身未掛載,僅上層祖先掛載時(shí),清空 Fiber 實(shí)例,避免事件系統(tǒng)讀取未就緒組件的回調(diào),防止報(bào)錯(cuò)
export function findInstanceBlockingEvent(
nativeEvent,
) {
const nativeEventTarget = getEventTarget(nativeEvent);
return findInstanceBlockingTarget(nativeEventTarget);
}
function getEventTarget(nativeEvent) {
let target = nativeEvent.target || nativeEvent.srcElement || window;
if (target.correspondingUseElement) {
target = target.correspondingUseElement;
}
return target.nodeType === TEXT_NODE ? target.parentNode : target;
}
export let return_targetInst = null;
export function findInstanceBlockingTarget(
targetNode,
) {
return_targetInst = null;
// 從 DOM 向上找最近綁定 Fiber 的實(shí)例
let targetInst = getClosestInstanceFromNode(targetNode);
if (targetInst !== null) {
// 從當(dāng)前 Fiber 向上找最近已掛載的 Fiber
const nearestMounted = getNearestMountedFiber(targetInst);
if (nearestMounted === null) {
targetInst = null;
} else {
const tag = nearestMounted.tag;
// Suspense 組件,子樹延遲加載,內(nèi)部 DOM 已渲染但未激活
if (tag === SuspenseComponent) {
const instance = getSuspenseInstanceFromFiber(nearestMounted);
if (instance !== null) {
return instance;
}
targetInst = null;
// 離屏緩存組件(React 內(nèi)部),DOM 存在但組件未激活
} else if (tag === ActivityComponent) {
const instance = getActivityInstanceFromFiber(nearestMounted);
if (instance !== null) {
return instance;
}
targetInst = null;
// 應(yīng)用根 Fiber,對(duì)應(yīng) #root 容器
} else if (tag === HostRoot) {
const root = nearestMounted.stateNode;
if (isRootDehydrated(root)) {
return getContainerFromFiber(nearestMounted);
}
targetInst = null;
} else if (nearestMounted !== targetInst) {
targetInst = null;
}
}
}
return_targetInst = targetInst;
return null;
}
作用
- 獲取事件真實(shí)觸發(fā) DOM:統(tǒng)一抹平瀏覽器事件 target 相關(guān) API 差異,正確取出事件真正觸發(fā)的 DOM 節(jié)點(diǎn),拿到事件源頭 DOM,作為向上遍歷查找阻塞節(jié)點(diǎn)的起點(diǎn)
- 轉(zhuǎn)發(fā)給核心查找函數(shù) findInstanceBlockingTarget
- 調(diào)用 getClosestInstanceFromNode 獲取當(dāng)前 DOM 綁定的最近 Fiber:從傳入的 DOM 節(jié)點(diǎn)向上遍歷,拿到綁定在 DOM 上的 Fiber 實(shí)例
- 存在 Fiber 實(shí)例,調(diào)用 getNearestMountedFiber,從當(dāng)前 Fiber 向上遍歷 Fiber 樹,找到第一個(gè)已完成掛載的祖先 Fiber
- 無任何已掛載祖先 Fiber:整條組件樹都未掛載,不存在可響應(yīng)事件的 Fiber,清空 targetInst,后續(xù)直接返回 null
- 最近掛載節(jié)點(diǎn)是 Suspense
- getSuspenseInstanceFromFiber 獲取 Suspense 對(duì)應(yīng)的 DOM 容器
- 容器存在 → 直接返回該 DOM,代表被 Suspense 阻塞水合
- 無容器則清空 targetInst,視為無阻塞
- 最近掛載節(jié)點(diǎn)是 Activity 離屏組件:DOM 保留在頁(yè)面但組件處于凍結(jié) / 未激活狀態(tài),事件無法正常響應(yīng),找到對(duì)應(yīng) DOM 容器直接返回,標(biāo)記為阻塞節(jié)點(diǎn)
- 最近掛載節(jié)點(diǎn)是應(yīng)用根 HostRoot,判斷根容器是否處于脫水未水合狀態(tài)(SSR 剛加載完 HTML,JS 還沒完成水合)
- 脫水狀態(tài):返回根容器 DOM,代表全局水合阻塞
- 已完成水合:清空 targetInst
- 最近掛載 Fiber 不是當(dāng)前事件節(jié)點(diǎn)自身:當(dāng)前觸發(fā)事件的 Fiber 自身未掛載,僅上層祖先掛載;組件未就緒,不能作為有效事件目標(biāo),清空 targetInst
- 把有效 Fiber 存入全局變量供上層使用:走完所有阻塞判斷后,若 targetInst 不為 null,代表當(dāng)前 DOM 對(duì)應(yīng)的組件已正常掛載、無阻塞;存入全局變量,后續(xù)插件事件系統(tǒng)可以直接拿到事件目標(biāo) Fiber,快速收集事件處理器
- 函數(shù)統(tǒng)一返回 null:只有上面 Suspense/Activity/ 脫水根三個(gè)分支會(huì)提前 return DOM 阻塞節(jié)點(diǎn);其余所有正常、無阻塞場(chǎng)景統(tǒng)一返回 null
清理緩存的過渡類事件:clearIfContinuousEvent
清理 SSR 半水合階段積壓的過渡類事件緩存,最終只執(zhí)行最新一次交互事件,中間滑動(dòng)產(chǎn)生的過渡事件全部作廢。當(dāng)頁(yè)面處于半水合狀態(tài)時(shí),queueIfContinuousEvent 會(huì)把頻繁觸發(fā)的懸浮、焦點(diǎn)、拖拽、指針事件存入全局隊(duì)列延遲執(zhí)行;一旦當(dāng)前事件可以正常分發(fā)(組件已水合、無阻塞),調(diào)用本函數(shù)清空對(duì)應(yīng)緩存,避免過期事件后續(xù)補(bǔ)發(fā)造成狀態(tài)錯(cuò)亂
- 本次焦點(diǎn)事件正常分發(fā)執(zhí)行,清空之前緩存的焦點(diǎn)隊(duì)列,丟棄未執(zhí)行的舊焦點(diǎn)事件
- 清空拖拽事件緩存,過期拖拽事件不再補(bǔ)發(fā)
- 清空鼠標(biāo)懸浮緩存
- 只刪除當(dāng)前觸點(diǎn)對(duì)應(yīng)的緩存事件,不影響其他觸點(diǎn)隊(duì)列(指針設(shè)備支持多觸點(diǎn),用
pointerId區(qū)分不同手指 / 觸控筆) - 按觸點(diǎn)
ID清除對(duì)應(yīng)指針捕獲緩存事件
export function clearIfContinuousEvent(
domEventName,
nativeEvent,
) {
switch (domEventName) {
case 'focusin':
case 'focusout':
// 緩存 focusin /focusout 焦點(diǎn)過渡事件
queuedFocus = null;
break;
case 'dragenter':
case 'dragleave':
// 緩存 dragenter /dragleave 拖拽進(jìn)出事件
queuedDrag = null;
break;
case 'mouseover':
case 'mouseout':
// 緩存 mouseover /mouseout 鼠標(biāo)懸浮進(jìn)出事件
queuedMouse = null;
break;
case 'pointerover':
case 'pointerout': {
// 緩存 pointerover/pointerout 指針懸浮事件
const pointerId = nativeEvent.pointerId;
queuedPointers.delete(pointerId);
break;
}
case 'gotpointercapture':
case 'lostpointercapture': {
// 緩存 gotpointercapture/lostpointercapture 指針捕獲事件
const pointerId = nativeEvent.pointerId;
queuedPointerCaptures.delete(pointerId);
break;
}
}
}
緩存高頻過渡類事件:queueIfContinuousEvent
focusin / dragenter / mouseover / pointerover / gotpointercapture 均為高頻、成對(duì)切換、瞬時(shí)狀態(tài)過渡事件,短時(shí)間會(huì)大量連續(xù)觸發(fā);SSR 半水合階段如果立刻執(zhí)行會(huì)阻塞主線程,且大量中間事件無業(yè)務(wù)價(jià)值,只需要保留最新一次回放
- 匹配到可緩存的連續(xù)過渡事件:存入緩存并返回 true;上層執(zhí)行 nativeEvent.stopPropagation(),終止本次事件分發(fā)
- 不匹配任何 case(click/input/scroll/wheel 等):返回 false,上層繼續(xù)走正常阻塞事件分發(fā)邏輯
export function queueIfContinuousEvent(
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
) {
switch (domEventName) {
case 'focusin': {
const focusEvent = nativeEvent;
queuedFocus = accumulateOrCreateContinuousQueuedReplayableEvent(
queuedFocus,
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
focusEvent,
);
return true;
}
case 'dragenter': {
const dragEvent = nativeEvent;
queuedDrag = accumulateOrCreateContinuousQueuedReplayableEvent(
queuedDrag,
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
dragEvent,
);
return true;
}
case 'mouseover': {
const mouseEvent = nativeEvent;
queuedMouse = accumulateOrCreateContinuousQueuedReplayableEvent(
queuedMouse,
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
mouseEvent,
);
return true;
}
case 'pointerover': {
const pointerEvent = nativeEvent;
const pointerId = pointerEvent.pointerId;
queuedPointers.set(
pointerId,
accumulateOrCreateContinuousQueuedReplayableEvent(
queuedPointers.get(pointerId) || null,
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
pointerEvent,
),
);
return true;
}
case 'gotpointercapture': {
const pointerEvent = nativeEvent;
const pointerId = pointerEvent.pointerId;
queuedPointerCaptures.set(
pointerId,
accumulateOrCreateContinuousQueuedReplayableEvent(
queuedPointerCaptures.get(pointerId) || null,
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
pointerEvent,
),
);
return true;
}
}
return false;
}
連續(xù)過渡事件緩存工具:accumulateOrCreateContinuousQueuedReplayableEvent
- 無舊緩存 / 原生事件對(duì)象不同:調(diào)用 createQueuedReplayableEvent 新建一份可回放事件快照,統(tǒng)一封裝回放所需全部上下文,后續(xù)水合完成后遍歷緩存、執(zhí)行事件分發(fā)時(shí),僅依賴 QueuedReplayableEvent 即可完整復(fù)現(xiàn)當(dāng)時(shí)事件環(huán)境,無需額外保存臨時(shí)變量,并對(duì)阻塞 Fiber 提升水合優(yōu)先級(jí),調(diào)度器會(huì)優(yōu)先執(zhí)行這個(gè)組件的水合邏輯,盡快解除阻塞,減少事件持續(xù)緩存積壓,避免大量過渡事件長(zhǎng)期駐留內(nèi)存
- 保存阻塞節(jié)點(diǎn)、事件名稱、初始 flags
- targetContainers 數(shù)組存入當(dāng)前根容器
- 綁定原生事件對(duì)象作為唯一標(biāo)識(shí)
- 舊緩存會(huì)被本次新快照覆蓋,舊快照失去引用,等待 GC 回收,只保留最新一次交互狀態(tài)
- 從阻塞 DOM 取出對(duì)應(yīng) Fiber
- 標(biāo)記該 Fiber 為高優(yōu)連續(xù)水合任務(wù)
- 返回新建快照,覆蓋全局緩存
- 已經(jīng)存在同一份原生事件緩存:合并標(biāo)記、收集多根容器,復(fù)用舊快照(高頻過渡事件短時(shí)間大量觸發(fā),如果每次都新建快照會(huì)頻繁 GC),不重復(fù)創(chuàng)建,減少內(nèi)存對(duì)象創(chuàng)建,降低 GC 開銷
- 把本次監(jiān)聽的捕獲 / 非委托標(biāo)記合并到舊快照,回放時(shí)同時(shí)攜帶兩套標(biāo)記
- 快照內(nèi) targetContainers 是數(shù)組,存儲(chǔ)所有觸發(fā)該事件的根容器;如果當(dāng)前根容器不在數(shù)組中,追加進(jìn)去;保證回放時(shí)能在所有關(guān)聯(lián) root 分發(fā)事件
- 返回原有合并后的快照
function accumulateOrCreateContinuousQueuedReplayableEvent(
existingQueuedEvent,
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
) {
if (
existingQueuedEvent === null ||
existingQueuedEvent.nativeEvent !== nativeEvent
) {
const queuedEvent = createQueuedReplayableEvent(
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
);
if (blockedOn !== null) {
const fiber = getInstanceFromNode(blockedOn);
if (fiber !== null) {
// 給被阻塞的 Fiber 打上連續(xù)事件水合優(yōu)先級(jí),調(diào)度器優(yōu)先處理該組件水合,盡快解除阻塞
attemptContinuousHydration(fiber);
}
}
return queuedEvent;
}
existingQueuedEvent.eventSystemFlags |= eventSystemFlags;
const targetContainers = existingQueuedEvent.targetContainers;
if (
targetContainer !== null &&
targetContainers.indexOf(targetContainer) === -1
) {
targetContainers.push(targetContainer);
}
return existingQueuedEvent;
}
function createQueuedReplayableEvent(
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
) {
return {
blockedOn,
domEventName,
eventSystemFlags,
nativeEvent,
targetContainers: [targetContainer],
};
}
事件邊界校正器 + 批量更新入口:dispatchEventForPluginEventSystem
- 對(duì)常規(guī)委托事件,修正事件向上遍歷的頂層邊界 ancestorInst,保證事件冒泡僅在當(dāng)前 React 根容器內(nèi)部傳播,不跨 root、不跨 Portal 污染其他應(yīng)用
- 單根應(yīng)用普通 DOM:向上遍歷 Fiber 命中當(dāng)前 HostRoot,break 循環(huán),ancestorInst 停在根 Fiber,事件冒泡范圍整個(gè) root 內(nèi)部
- Portal 內(nèi)部觸發(fā)事件
- Portal 容器不屬于當(dāng)前 root,內(nèi)層循環(huán)向上查找父 Fiber
- 若 Portal 掛載到外部 DOM,直接 return,事件不會(huì)冒泡到 Portal 外部組件
- 若 Portal 容器 DOM 能找到內(nèi)部托管組件,重置起點(diǎn)重新查找根邊界
- 多 Root 并行應(yīng)用:每個(gè) root 獨(dú)立綁定事件,邊界校正會(huì)限制冒泡僅在當(dāng)前 root 內(nèi),A 根事件不會(huì)觸發(fā) B 根組件回調(diào),隔離多應(yīng)用
- 把插件事件執(zhí)行邏輯包裹進(jìn)批量更新,統(tǒng)一觸發(fā)批量渲染
export function dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer,
) {
let ancestorInst = targetInst;
if (
// 當(dāng)前 DOM 是 React 托管組件,不是第三方外部 DOM
(eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 &&
// 當(dāng)前事件支持冒泡委托(click/input 等常規(guī)事件,不是 scroll/focus 這類不冒泡事件)
(eventSystemFlags & IS_NON_DELEGATED) === 0
) {
const targetContainerNode = targetContainer;
if (targetInst !== null) {
let node = targetInst;
mainLoop: while (true) {
if (node === null) {
return;
}
const nodeTag = node.tag;
if (nodeTag === HostRoot || nodeTag === HostPortal) {
let container = node.stateNode.containerInfo;
// 判斷 DOM 容器是否為當(dāng)前事件綁定的根容器
if (isMatchingRootContainer(container, targetContainerNode)) {
break;
}
if (nodeTag === HostPortal) {
let grandNode = node.return;
while (grandNode !== null) {
const grandTag = grandNode.tag;
if (grandTag === HostRoot || grandTag === HostPortal) {
const grandContainer = grandNode.stateNode.containerInfo;
if (
isMatchingRootContainer(grandContainer, targetContainerNode)
) {
return;
}
}
grandNode = grandNode.return;
}
}
while (container !== null) {
const parentNode = getClosestInstanceFromNode(container);
if (parentNode === null) {
return;
}
const parentTag = parentNode.tag;
if (
parentTag === HostComponent ||
parentTag === HostText ||
parentTag === HostHoistable ||
parentTag === HostSingleton
) {
node = ancestorInst = parentNode;
continue mainLoop;
}
container = container.parentNode;
}
}
node = node.return;
}
}
}
// 批量更新,內(nèi)部回調(diào)所有 setState 會(huì)合并批量渲染,減少重渲染
batchedUpdates(() =>
// 事件插件體系真正執(zhí)行邏輯,生成合成事件、遍歷 Fiber 執(zhí)行 onXxx 回調(diào)
dispatchEventsForPlugins(
domEventName,
eventSystemFlags,
nativeEvent,
ancestorInst,
targetContainer,
),
);
}
作用
- 初始化事件向上冒泡遍歷 Fiber 樹的終止邊界節(jié)點(diǎn) ancestorInst
- 僅對(duì)托管 + 可委托事件做邊界裁剪:當(dāng)前 DOM 是 React 托管組件,不是第三方外部 DOM 且當(dāng)前事件支持冒泡委托(click/input 等常規(guī)事件,不是 scroll/focus 這類不冒泡事件)
- 僅目標(biāo)存在 Fiber 才執(zhí)行邊界遍歷
- 向上遍歷 Fiber 樹,尋找當(dāng)前根容器邊界:從當(dāng)前 Fiber 持續(xù)向上 node.return 遍歷父 Fiber
- 遍歷到 HostRoot / HostPortal(根 / 傳送門)
- 取出 Root/Portal 對(duì)應(yīng)的真實(shí) DOM 容器
- 如果容器等于當(dāng)前事件綁定的根容器 → 命中邊界,跳出循環(huán),當(dāng)前 node 作為冒泡終點(diǎn)
- 不匹配則進(jìn)入 Portal 特殊處理邏輯
- Portal 特殊處理:跨 Portal 不能冒泡到外部根,Portal 節(jié)點(diǎn)渲染到外部 DOM,邏輯上父級(jí)不在當(dāng)前 root 內(nèi)部,隔離傳送門事件作用域
- 向上查找 Portal 的父 Fiber 鏈
- 若找到當(dāng)前根容器,說明 Portal 渲染范圍在外部,直接 return 終止整個(gè)函數(shù)
- 向上回溯 DOM 容器,重新定位容器內(nèi) Fiber(Portal 跨 DOM 場(chǎng)景修復(fù)):Portal 渲染到外部 DOM,F(xiàn)iber 樹和 DOM 樹層級(jí)不一致
- 從 Portal 容器 DOM 向上遍歷原生 DOM
- 找到第一個(gè) React 托管 DOM 組件 Fiber
- 重置遍歷起點(diǎn) node 和冒泡邊界 ancestorInst,重啟主循環(huán)重新查找根邊界
- 找不到托管 Fiber 直接 return,終止事件分發(fā)
- 未命中 Root/Portal,繼續(xù)向上遍歷父 Fiber
- 邊界修正完成,進(jìn)入批量執(zhí)行完整合成事件流程
設(shè)計(jì)意義
- 多根、Portal 事件隔離,修復(fù)原生 DOM 冒泡穿透問題:瀏覽器原生 DOM 冒泡不受 React 根容器、Portal 邊界限制,會(huì)跨 DOM 樹傳播;本函數(shù)通過校正 ancestorInst 限定事件遍歷上限,實(shí)現(xiàn)多 #root 應(yīng)用事件互相隔離和 Portal 內(nèi)部事件不冒泡到外部父組件。處理 Portal 渲染到外部 DOM 導(dǎo)致 Fiber、DOM 層級(jí)不匹配的問題,抹平 DOM 樹與 Fiber 樹層級(jí)不一致的行為差異
- 統(tǒng)一批量更新包裝:所有用戶事件回調(diào)統(tǒng)一包裹在 batchedUpdates,自動(dòng)合并多次狀態(tài)更新,避免高頻事件重復(fù)渲染,保障頁(yè)面性能
事件收集與回調(diào)執(zhí)行:dispatchEventsForPlugins
- 調(diào)用插件系統(tǒng),從 Fiber 樹提取所有捕獲 / 冒泡事件處理器,存入調(diào)度隊(duì)列
- 按階段順序執(zhí)行隊(duì)列里的所有事件回調(diào)
function dispatchEventsForPlugins(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer,
) {
const nativeEventTarget = getEventTarget(nativeEvent);
// 事件調(diào)度隊(duì)列,數(shù)組結(jié)構(gòu),存儲(chǔ)每條事件處理記錄(包含階段、回調(diào)、Fiber 實(shí)例、合成事件配置等)
const dispatchQueue = [];
extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
// 按捕獲先、冒泡后的順序遍歷隊(duì)列,創(chuàng)建 SyntheticEvent 合成事件、執(zhí)行用戶回調(diào)、處理事件池、阻止冒泡 / 默認(rèn)行為
processDispatchQueue(dispatchQueue, eventSystemFlags);
}
作用
- 調(diào)用 getEventTarget 獲取標(biāo)準(zhǔn)化原生事件目標(biāo) DOM
- 初始化空事件調(diào)度隊(duì)列 dispatchQueue:存放所有待執(zhí)行的事件處理器。每條隊(duì)列項(xiàng)會(huì)記錄事件階段(捕獲 / 冒泡)、對(duì)應(yīng) Fiber、事件回調(diào)、插件配置等信息
- 調(diào)用 extractEvents 插件體系提取所有事件處理器,填充隊(duì)列,執(zhí)行完畢后 dispatchQueue 已有序存放全部待執(zhí)行回調(diào)
- 遍歷 React 內(nèi)置所有事件插件(點(diǎn)擊插件、表單插件、焦點(diǎn)插件、觸摸插件等)
- 從 targetInst 向上遍歷 Fiber 樹,直到上層限定的邊界 ancestorInst
- 區(qū)分捕獲階段回調(diào) (onXxxCapture) 、冒泡階段回調(diào) (onXxx) ,分別推入隊(duì)列
- 插件負(fù)責(zé)映射原生事件與 React 合成事件、處理特殊事件(如 onChange、onSelect)
- 調(diào)用 processDispatchQueue 按階段順序執(zhí)行隊(duì)列內(nèi)所有事件
- 區(qū)分捕獲 / 冒泡順序:先執(zhí)行全部捕獲回調(diào),再執(zhí)行全部冒泡回調(diào),模擬 DOM 原生事件傳播模型
- 復(fù)用事件池,創(chuàng)建 SyntheticEvent 合成事件對(duì)象,掛載原生事件、stopPropagation/preventDefault 封裝方法
- 依次執(zhí)行用戶寫的事件回調(diào)函數(shù)
- 監(jiān)聽回調(diào)中調(diào)用 e.stopPropagation(),截?cái)嗪罄m(xù)同階段事件執(zhí)行
- 回調(diào)執(zhí)行完畢回收合成事件對(duì)象,放回事件池復(fù)用,減少 GC 開銷
設(shè)計(jì)思想
- 優(yōu)先級(jí)包裝器策略:createEventListenerWrapperWithPriority 根據(jù)事件類型選擇 dispatchDiscreteEvent/dispatchContinuousEvent/dispatchEvent,在原生事件觸發(fā)時(shí)同步設(shè)置 currentUpdatePriority,決定后續(xù) setState 的 lane
- 被動(dòng)事件仿 document 行為:Chrome 對(duì) document 上的 touchstart/touchmove/wheel 強(qiáng)制 { passive: true },React 19 在 rootContainer 上手動(dòng)模擬(非被動(dòng)時(shí)瀏覽器阻塞滾動(dòng)等待 JS)
- 偏函數(shù)閉包緩存:bind(null, domEventName, eventSystemFlags, targetContainer) 創(chuàng)建一次性閉包,原生 addEventListener 的 listener 引用在整個(gè) root 生命周期中不變,無需重新綁定
到此這篇關(guān)于React 事件系統(tǒng)addTrappedEventListener的使用的文章就介紹到這了,更多相關(guān)React addTrappedEventListener內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
create-react-app修改為多頁(yè)面支持的方法
本篇文章主要介紹了create-react-app修改為多頁(yè)面支持的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05
詳解開發(fā)react應(yīng)用最好用的腳手架 create-react-app
本篇文章主要介紹了詳解開發(fā)react應(yīng)用最好用的腳手架 create-react-app,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-04-04
在React項(xiàng)目中使用TypeScript詳情
這篇文章主要介紹了在React項(xiàng)目中使用TypeScript詳情,文章通過圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09
React使用xlsx和js-export-excel實(shí)現(xiàn)前端導(dǎo)出
這篇文章主要為大家詳細(xì)介紹了React如何分別使用xlsx和js-export-excel實(shí)現(xiàn)前端導(dǎo)出功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2024-02-02
react antd-mobile ActionSheet+tag實(shí)現(xiàn)多選方式
這篇文章主要介紹了react antd-mobile ActionSheet+tag實(shí)現(xiàn)多選方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10
React Native自定義Android的SSL證書鏈校驗(yàn)
這篇文章主要為大家介紹了React Native自定義Android的SSL證書鏈校驗(yàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
React Form組件的實(shí)現(xiàn)封裝雜談
這篇文章主要介紹了React Form組件的實(shí)現(xiàn)封裝雜談,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05
關(guān)于react ant 組件 Select下拉框 值回顯的問題
這篇文章主要介紹了關(guān)于react ant 組件 Select下拉框 值回顯的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08

