react17是一个过渡版本,最大的改动莫过于针对_事件机制的重构_。虽然现在react18版本已经发布了,但对事件机制这部分几乎是没有改动的,因此这里依然可以对17版本的这部分源码作一次解读。
摘自_react_官网的独白:
这里把个人觉得较重大的改动框出来了:
1.将事件委托给根节点而不是document;
2.让所有的Capture事件与浏览器捕获阶段保持一致;
3.移除事件池;
恕我直言,你看到的不一定是真实的
1.元素上的事件并不是绑定在本身;2.event 并不是元素本身的事件对象;3.整个事件流(捕获、冒泡)过程都是 react 模拟的;接下来先整个面试题开开胃吧,在 react16.x 版本中,如下代码执行,弹窗组件表现如何?
state={visible:false
}
componentDidMount(){document.addEventListener('click',()=>{this.setState({visible:false})})
}
handleClick = ()=>{this.setState({visible:true})
}
render(){return(<>{this.state.visible && }>)
}
先卖个关子,看完后面内容就自然知道了😁。
// packages/react-dom/src/client/ReactDOMRoot.js
function createRootImpl( container: Container, // 项目根节点tag: RootTag,options: void | RootOptions, ) {if (enableEagerRootListeners) {const rootContainerElement =container.nodeType === COMMENT_NODE ? container.parentNode : container;// 注意此函数 监听所有支持的事件listenToAllSupportedEvents(rootContainerElement);}return root;
}
// packages/react-dom/src/events/DOMPluginEventSystem.js
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {if (enableEagerRootListeners) {// allNativeEvents 这个变量是所有原生事件的集合allNativeEvents.forEach(domEventName => {if (!nonDelegatedEvents.has(domEventName)) {listenToNativeEvent(domEventName,false,((rootContainerElement: any): Element),null,);}listenToNativeEvent(domEventName,true,((rootContainerElement: any): Element),null,);});}
}
这里有一个疑问 allNativeEvents 这个变量是怎么赋值的?实际上 packages/react-dom/src/events/DOMPluginEventSystem.js 在这个文件的顶部
// packages/react-dom/src/events/DOMPluginEventSystem.js
import * as BeforeInputEventPlugin from './plugins/BeforeInputEventPlugin';
import * as ChangeEventPlugin from './plugins/ChangeEventPlugin';
import * as EnterLeaveEventPlugin from './plugins/EnterLeaveEventPlugin';
import * as SelectEventPlugin from './plugins/SelectEventPlugin';
import * as SimpleEventPlugin from './plugins/SimpleEventPlugin';
SimpleEventPlugin.registerEvents();
EnterLeaveEventPlugin.registerEvents();
ChangeEventPlugin.registerEvents();
SelectEventPlugin.registerEvents();
BeforeInputEventPlugin.registerEvents();
在引入插件的同时调用了插件对应的事件注册方法,关于插件的内容在后续讲解针对事件源event的处理时再来讨论。
// packages/react-dom/src/events/EventRegistry.js
export const allNativeEvents: Set = new Set();
export function registerDirectEvent( registrationName: string,dependencies: Array, ) {for (let i = 0; i < dependencies.length; i++) {allNativeEvents.add(dependencies[i]);}
}
在搞清楚 allNativeEvents 的来源后我们继续往下
// packages/react-dom/src/events/DOMPluginEventSystem.js
function addTrappedEventListener(targetContainer: EventTarget,domEventName: DOMEventName,eventSystemFlags: EventSystemFlags,isCapturePhaseListener: boolean,isDeferredListenerForLegacyFBSupport?: boolean,
) {
// 特别注意这个地方 对事件的回调函数作了一次包装 稍后在事件触发阶段再来详细聊聊let listener = createEventListenerWrapperWithPriority(targetContainer,domEventName,eventSystemFlags,);let unsubscribeListener;if (isCapturePhaseListener) {unsubscribeListener = addEventCaptureListener(targetContainer,domEventName,listener,);} else {unsu