【保姆级】react17 事件机制源码解析

本文详细解析了React17中的事件机制变化,包括事件绑定在根节点、事件触发过程,以及如何模拟事件捕获和冒泡。通过源码分析,阐述了React事件与原生事件的区别,解释了事件对象的合成过程和兼容性处理,同时解答了相关面试问题。

写在前面

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(<><button onClick={this.handleClick}>点击打开dialog</button>{this.state.visible && <Dialog/>}</>)
} 

先卖个关子,看完后面内容就自然知道了😁。

正式开始吧

事件绑定
// 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<DOMEventName> = new Set();
export function registerDirectEvent( registrationName: string,dependencies: Array<DOMEventName>, ) {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(tar
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值