react 源码解析 18.react 事件零碎
视频解说(高效学习):进入学习
往期文章:
1. 开篇介绍和面试题
2.react 的设计理念
3.react 源码架构
4. 源码目录构造和调试
5.jsx& 外围 api
6.legacy 和 concurrent 模式入口函数
7.Fiber 架构
8.render 阶段
9.diff 算法
10.commit 阶段
11. 生命周期
12. 状态更新流程
13.hooks 源码
14. 手写 hooks
15.scheduler&Lane
16.concurrent 模式
17.context
18 事件零碎
19. 手写迷你版 react
20. 总结 & 第一章的面试题解答
21.demo
从一个 bug 说起
上面这个 demo_13 在 react17 和 react16 中有什么不同吗?代码也很简略,模仿一个 modal 框,点击显示呈现,点击其余中央,相当于点击了 mask,modal 隐没,因为 react 事件都是委托到下层,所以须要在 handleClick 阻止冒泡,这样点击显示的时候不会触发 document 上的事件回调,导致 modal 无奈显示。然而在 react16 上发现这样做还是不行,须要调用 e.nativeEvent.stopImmediatePropagation() 能力实现,而 react17 上没什么影响
究其原因就是 react16 和 17 在委托事件的容器上做出了扭转,react16 的事件会冒泡的 document 上,而 17 则会冒泡到 root 容器上,也就是 ReactDom.render 的第二个参数
export default class Demo13 extends React.Component {state = { show: false};
componentDidMount() {document.addEventListener("click", () => {this.setState({ show: false});
});
}
handleClick = (e) => {e.stopPropagation();//react17 中失效
// e.nativeEvent.stopImmediatePropagation(); //react16 中失效 stopImmediatePropagation 也阻止本级监听函数执行
this.setState({show: true});
};
render() {
return (
<div>
<button onClick={this.handleClick}> 显示 </button>
{this.state.show && <div onClick={(e) => e.nativeEvent.stopImmediatePropagation()}>modal</div>}
</div>
);
}
}
大家也能够看下 demo_11、demo_12 在 react16、17 触发程序有何差别,同时 demo 我的项目中的 event.html 也模仿了 react16、17 的事件代理机制
事件零碎架构图
咱们以 SimpleEvent 为例看事件注册、绑定和触发的过程,看视频的调试过程
事件注册
-
DOMPluginEventSystem.js 会调用 SimpleEventPlugin 插件的 registerEvents 办法注册事件
//DOMPluginEventSystem.js SimpleEventPlugin.registerEvents();
-
registerSimpleEvents
function registerSimpleEvents() {registerSimplePluginEventsAndSetTheirPriorities(discreteEventPairsForSimpleEventPlugin, DiscreteEvent); //... } function registerSimplePluginEventsAndSetTheirPriorities(eventTypes, priority) {for (var i = 0; i < eventTypes.length; i += 2) {var topEvent = eventTypes[i]; var event = eventTypes[i + 1]; var capitalizedEvent = event[0].toUpperCase() + event.slice(1); var reactName = 'on' + capitalizedEvent; eventPriorities.set(topEvent, priority); topLevelEventsToReactNames.set(topEvent, reactName); registerTwoPhaseEvent(reactName, [topEvent]);// 注册捕捉和冒泡两个阶段的事件 } }
-
registerTwoPhaseEvent
function registerTwoPhaseEvent(registrationName, dependencies) {registerDirectEvent(registrationName, dependencies); registerDirectEvent(registrationName + 'Capture', dependencies); }
-
registerDirectEvent
function registerDirectEvent(registrationName, dependencies) { //... for (var i = 0; i < dependencies.length; i++) {allNativeEvents.add(dependencies[i]);// 生成 allNativeEvents 对象 } }
事件绑定
-
listenToAllSupportedEvents
// 由函数 createRootImpl 调用,也就是在创立根节点之后执行 function listenToAllSupportedEvents(rootContainerElement) {allNativeEvents.forEach(function (domEventName) {if (!nonDelegatedEvents.has(domEventName)) {listenToNativeEvent(domEventName, false, rootContainerElement, null); } listenToNativeEvent(domEventName, true, rootContainerElement, null); }); } }
-
listenToNativeEvent
function listenToNativeEvent(domEventName, isCapturePhaseListener, rootContainerElement, targetElement) { //... if (!listenerSet.has(listenerSetKey)) {if (isCapturePhaseListener) {eventSystemFlags |= IS_CAPTURE_PHASE;} addTrappedEventListener(target, domEventName, eventSystemFlags, isCapturePhaseListener); listenerSet.add(listenerSetKey); } }
-
addTrappedEventListener
function addTrappedEventListener(targetContainer, domEventName, eventSystemFlags, isCapturePhaseListener, isDeferredListenerForLegacyFBSupport) { // 创立具备优先级的监听函数 var listener = createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags); //... targetContainer = targetContainer; var unsubscribeListener; 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); } } }
-
createEventListenerWrapperWithPriority
function createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags) {var eventPriority = getEventPriorityForPluginSystem(domEventName); var listenerWrapper; switch (eventPriority) { case DiscreteEvent: listenerWrapper = dispatchDiscreteEvent; break; case UserBlockingEvent: listenerWrapper = dispatchUserBlockingUpdate; break; case ContinuousEvent: default: listenerWrapper = dispatchEvent; break; } // 绑定 dispatchDiscreteEvent return listenerWrapper.bind(null, domEventName, eventSystemFlags, targetContainer); }
事件触发
-
dispatchDiscreteEvent(dispatchEvent)
function dispatchDiscreteEvent(domEventName, eventSystemFlags, container, nativeEvent) { {flushDiscreteUpdatesIfNeeded(nativeEvent.timeStamp); } discreteUpdates(dispatchEvent, domEventName, eventSystemFlags, container, nativeEvent); }
-
unstable_runWithPriority
function unstable_runWithPriority(priorityLevel, eventHandler) {//eventHandler 就是 dispatchEvent 函数 switch (priorityLevel) { case ImmediatePriority: case UserBlockingPriority: case NormalPriority: case LowPriority: case IdlePriority: break; default: priorityLevel = NormalPriority; } var previousPriorityLevel = currentPriorityLevel; currentPriorityLevel = priorityLevel; try {return eventHandler(); } finally {currentPriorityLevel = previousPriorityLevel;} }
-
batchedEventUpdates
function batchedEventUpdates(fn, a, b) {if (isBatchingEventUpdates) {return fn(a, b); } isBatchingEventUpdates = true; try {return batchedEventUpdatesImpl(fn, a, b); //fn 参数即://function () {// return dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, //ancestorInst); //} function () {return dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, ancestorInst); } } finally { isBatchingEventUpdates = false; finishEventHandler();} }
-
dispatchEventsForPlugins
function dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) {var nativeEventTarget = getEventTarget(nativeEvent); var dispatchQueue = []; //extractEvent 生成 SyntheticEvent extractEvents(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags); //processDispatchQueue 执行造成事件队列 processDispatchQueue(dispatchQueue, eventSystemFlags); }