概览
所谓事件机制,无非就是注册事件与分发事件两个步骤,程序员通过代码来注册事件到react对应的节点上,用户与浏览器发生交互,触发浏览器的原生事件,原生事件被react捕获,从原生事件中拿到原生节点,然后根据原生节点拿到react设计的fiber节点,然后从fiber节点中取出相应的回调来执行它,简单流程就是如此,所以我们这次讨论react事件机制,就是如何注册,以及如何分发。
取出事件
react初始化过程,简单来讲就是我们的jsx被babel转成一串json,我们将这串json叫做react元素的树,react再将这串json转成fiber树(fiber树主要用来做diff),生成fiber树的过程其实就是,我们对react元素的树进行一个深度优先的遍历,遍历的过程中,对于每个节点,我们所绑定的事件其实是作为一个属性绑定在上面的,遍历完成后,我们需要生成一颗真实DOM的树,生成的时候,每个真实DOM节点都会和fiber节点关联起来(通过一个key值internalInstanceKey)
注册事件
在我们生成真实DOM树的时候,需要将fiber树的一些属性(比如样式)映射到真实DOM上,当发现这个节点绑定了事件的时候,我们直接将这个事件类型绑定到根节点或者document上。
1 function addEventBubbleListener(target, eventType, listener) {
2 console.log(target, eventType, listener.name);
3 target.addEventListener(eventType, listener, false);
4 return listener;
5 }
react监听的是根节点的事件,所以listener是一个能够处理所有节点的响应事件的函数,最终浏览器中注册的事件应该是下面这样,每个事件类型,都在根节点上注册了一个相应的listener进行事件的分发。
分发事件
分发事件,简单来讲就是事件被触发后,从内存取出对应的回调来执行,当用户与浏览器产生交互时,以下事情会先后执行。
拿到原生event
触发原生事件,拿到原生事件的event。
1 document.addEventListener(type, dispatchEvent)
2 function dispatchEvent(event) {
3 console.log(event) // 从这里拿到event
4 }
拿到fiber节点
根据原生事件的event可以拿到原生DOM节点,继而拿到其fiber节点。(在生成真实dom树的时候,dom节点就已经通internalInstanceKey和fiber节点关联起来了)
1 const nativeEventTarget = getEventTarget(nativeEvent);
2 const targetInst = getClosestInstanceFromNode(nativeEventTarget);
3
4 function getClosestInstanceFromNode(targetNode: Node) {
5 let targetInst = (targetNode: any)[internalInstanceKey];
6 if (targetInst) {
7 // Don't return HostRoot or SuspenseComponent here
8 return targetInst;
9 }
10 }
合成react事件
根据原生事件开始合成react事件,在此之前,先大概介绍以下react合成事件这个概念。
核心概念
浏览器的事件,大多数直接派生自Event,另外有7类属于UI事件,派生自UIEvent,UIEvent派生自Event。react内部也是如此。
细节就不再过多赘述了,主要做的事是
- 对原生事件的封装,基本上是把原生事件的一些属性在代码内部自己定义了一遍。
- 对某些原生事件(change,select,beforeInput等)的升级和改造,这类事件注册时会附带注册一些依赖项,例如,给input注册了onchange事件,那么"blur", "change", "click", "focus", "input", "keydown", "keyup", "selectionchange"这些事件全都会被注册,原生只注册一个onchange的话,需要在失去焦点的时候才能触发这个事件,所以这个原生事件的缺陷react也帮我们弥补了。
- 不同浏览器事件兼容的处理。
1 const nativeEventTarget = getEventTarget(nativeEvent);
2 const dispatchQueue: DispatchQueue = [];
3 // 合成事件
4 extractEvents(dispatchQueue);
5 // 触发回调
6 processDispatchQueue(dispatchQueue, eventSystemFlags);
合成过程
- 根据事件类型选择插件进行合成(react将所有事件归纳进了六种插件,事件的合成由插件进行)代码地址:extractEvents
- 根据事件的类型,实例化不同React事件的构造函数进行合成,代码地址: SimpleEventPlugin.extractEvents
1 let EventConstructor;
2 switch (topLevelType) {
3 case DOMTopLevelEventTypes.TOP_KEY_DOWN:
4 case DOMTopLevelEventTypes.TOP_KEY_UP:
5 EventConstructor = SyntheticKeyboardEvent;
6 break;
7 // other case ...
8 }
9 const event = new EventConstructor(
10 reactName,
11 null,
12 nativeEvent,
13 nativeEventTarget,
14 );
累积所有实例和侦听器
- 根据fiber节点找到对应事件的回调函数
1 function getListener() {
2 const stateNode = inst.stateNode;
3 if (stateNode === null) {
4 // Work in progress (ex: onload events in incremental mode).
5 return null;
6 }
7 const props = getFiberCurrentPropsFromNode(stateNode);
8 if (props === null) {
9 // Work in progress.
10 return null;
11 }
12 const listener = props[registrationName];
13 return listener;
14 }
- 根据捕获或冒泡阶段给事件队列listeners添加回调函数
- 先将捕获事件的回调放入listeners里
- 如果是冒泡和捕获阶段都需要触发的事件,则放到listener的头部(unshift)
- 如果不是,捕获阶段的事件放到listener的尾部(pop)
1 const listeners: Array<DispatchListener> = [];
2 // 需要注意一下下面这两变量都是字符串,不是布尔值
3 const bubbled = event._reactName;
4 const captured = bubbled !== null ? bubbled + 'Capture' : null;
5
6 // 是否需要模拟冒泡和捕获两个阶段,capturePhaseEvents是代码里定义的枚举值,如focus,blur
7 const shouldEmulateTwoPhase = capturePhaseEvents.has(
8 ((targetType: any): DOMTopLevelEventType),
9 );
10
11 if (bubbled !== null) {
12 // 拿到回调
13 const bubbleListener = getListener(instance, bubbled);
14 const entry = createDispatchListener(instance, bubbleListener, currentTarget);
15
16 // push进队列
17 if (shouldEmulateTwoPhase) {
18 // 特定的事件触发时机放到前面
19 listeners.unshift(entry);
20 } else {
21 // 其他的事件追加在后面
22 listeners.push(entry);
23 }
24 }
25 }
3. 节点一直向上遍历,对于每个节点重复执行1和2
1 let instance = targetFiber;
2 while (instance !== null) {
3 // do step 1
4 // do step 2
5 instance = instance.return;
6 }
触发回调
现在listeners里包含了所有的侦听器,循环执行,将其分发出去。
1 if (listeners.length !== 0) {
2 dispatchQueue.push(createDispatchEntry(event, listeners));
3 }
4
5 function processDispatchQueue(
6 dispatchQueue: DispatchQueue,
7 eventSystemFlags: EventSystemFlags,
8 ): void {
9 const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
10 for (let i = 0; i < dispatchQueue.length; i++) {
11 const {event, listeners} = dispatchQueue[i];
12 // 把listners 循环一遍依次执行
13 processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
14 // Modern event system doesn't use pooling.
15 }
16 }
另外有一点是当其中有某一个回调执行了stopPropagation后,后续的代码就不会执行了。
1
作者:向恢进
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!