React原理 事件系统

创建事件集合

React会在初始化的时候生成事件系统所需要的事件对象集合。

  • 把系统事件按照优先级分为三大类,每种事件对应着一个优先级
事件类别 变量名称 说明 优先级 举例
独立事件 discreteEventPairsForSimpleEventPlugin DiscreteEvent=0 例如: click
用户阻塞事件 userBlockingPairsForSimpleEventPlugin
因为事件持触发,所以如果某段逻辑执行之间过长,会影响用户的交互
UserBlockingEvent=1 例如:drag
连续事件 continuousPairsForSimpleEventPlugin 需要系统一直监听是否触发的事件 ContinuousEvent=2 例如: animationEnd
  • 通过循环每种事件对应的数组,将事件保存在一下几种事件对象中
变量名称 变量对象 说明
allNativeEvents Set集合
保存所有原生事件的名称 例如 0:"cancel"
eventPriorities Map集
保存事件名称和事件优先级对应关系 例如 click=>0
topLevelEventsToReactNames Map集
保存原始事件名称和 React事件的对应关系 例如 "cancel" => "onCancel"
registrationNameDependencies Object
保存React事件和原生事件的对应关系 例如 onClick:(1) ['click'] 每个React事件对应一个数组用于保存合成事件对应关系
possibleRegistrationNames Object
保存小写的React事件名称和正确的驼峰命名事件的对应关系,用于校验用户输入 例如 onclick:onClick
  • 注册合成事件

合成事件: 某些React事件会对应多个原生事件 例如:

1
'onChange' => ['change', 'click', 'focusin', 'focusout', 'input', 'keydown', 'keyup', 'selectionchange']

合成事件会维护在registrationNameDependencies变量中,例如 onChange事件最终会变为 ``

创建事件对象

事件对象用于不用的事件类型,当React事件触发时,将会传入对应的事件对象,而不是原生的事件对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function createSyntheticEvent(Interface) {
function SyntheticBaseEvent(reactName, reactEventType, targetInst, nativeEvent, nativeEventTarget) {
this._reactName = reactName;
this._targetInst = targetInst;
this.type = reactEventType;
this.nativeEvent = nativeEvent;
this.target = nativeEventTarget;
this.currentTarget = null;
_assign(SyntheticBaseEvent.prototype, {
preventDefault: function () {},
stopPropagation: function () {this.isPropagationStopped = functionThatReturnsTrue;},
persist: function () {},
isPersistent: functionThatReturnsTrue
});

return SyntheticBaseEvent;
}
var UIEventInterface = _assign({}, EventInterface, {
view: 0,
detail: 0
});

var SyntheticUIEvent = createSyntheticEvent(UIEventInterface);

事件绑定

React 创建 FiberRoot 根节点阶段, 会循环所有的原声事件,将事件绑定在 root 元素上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function listenToAllSupportedEvents(rootContainerElement) {
{
var listeningMarker = '_reactListening' + Math.random().toString(36).slice(2);
rootContainerElement[listeningMarker] = true;
allNativeEvents.forEach(function (domEventName) {
// 没有事件委托的事件,也就是不能冒泡到document的事件
if (!nonDelegatedEvents.has(domEventName)) {
listenToNativeEvent(domEventName, false, rootContainerElement, null);
}

listenToNativeEvent(domEventName, true, rootContainerElement, null);
});
}
}

根据事件的优先级不同,事件类型不同,绑定的事件处理函数也会不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}

return listenerWrapper.bind(null, domEventName, eventSystemFlags, targetContainer);
}
  • 对于独立事件,如果更新时存在副作用,会在下一个事件前立即执行
1
2
3
4
function flushDiscreteUpdates() {
flushPendingDiscreteUpdates();
flushPassiveEffects();
}

对于用户阻塞事件,通过原生事件的targetsrcElement 获取到触发事件的元素,再通过定义在原生DOM上的属性 internalInstanceKey 找到DOM对应的Fiber节点

1
var targetInst = targetNode[internalInstanceKey];

通过 batchedEventUpdates 标记批处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function batchedEventUpdates(fn, a, b) {
if (isBatchingEventUpdates) {
// If we are currently inside another batch, we need to wait until it
// fully completes before restoring state.
return fn(a, b);
}

isBatchingEventUpdates = true;

try {
return batchedEventUpdatesImpl(fn, a, b);
} finally {
isBatchingEventUpdates = false;
finishEventHandler();
}
}

为不同的事件类型选择不同的事件对象,通过FiberNode上的props属性获取到事件,加入事件队列,
用队列来模拟冒泡事件和捕获事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
switch (domEventName) {
case 'click':
// Firefox creates a click event on right mouse clicks. This removes the
// unwanted click events.
if (nativeEvent.button === 2) {
return;
}

case 'auxclick':
case 'dblclick':
case 'mousedown':
case 'mousemove':
case 'mouseup': // TODO: Disabled elements should not respond to mouse events

/* falls through */

case 'mouseout':
case 'mouseover':
case 'contextmenu':
SyntheticEventCtor = SyntheticMouseEvent;
break;
}
while (instance !== null) {
var _instance2 = instance,
stateNode = _instance2.stateNode,
tag = _instance2.tag; // Handle listeners that are on HostComponents (i.e. <div>)

if (reactEventName !== null) {
var props = getFiberCurrentPropsFromNode(stateNode);
var listener = props[registrationName];

if (captureListener != null) {
listeners.unshift(createDispatchListener(instance, captureListener, currentTarget));
}

var bubbleListener = getListener(instance, reactName);

if (bubbleListener != null) {
listeners.push(createDispatchListener(instance, bubbleListener, currentTarget));
}
}
instance = instance.return;
}


dispatchQueue.push({
event: _event,
listeners: _listeners
});

循环事件队列,如果执行过stopPropagation 直接跳出循环,方法定义在上面的事件对象中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function processDispatchQueueItemsInOrder(event, dispatchListeners, inCapturePhase) {gfdg
var previousInstance;

for (var i = dispatchListeners.length - 1; i >= 0; i--) {
var _dispatchListeners$i = dispatchListeners[i],
instance = _dispatchListeners$i.instance,
currentTarget = _dispatchListeners$i.currentTarget,
listener = _dispatchListeners$i.listener;

if (instance !== previousInstance && event.isPropagationStopped()) {
return;
}

executeDispatch(event, listener, currentTarget);
previousInstance = instance;
}
}

FAQ

  • React 为什么有自己的事件系统?

为了抹平浏览器间差异,封装了事件对象,模拟了事件冒泡和捕获。

  • 什么是事件合成?

一个React可能是一个事件,也可能是多个原生事件。而且事件对象也是封装过的对象。

  • 如何实现的批量更新?

通过 isBatchingEventUpdates 标记批处理开始, 一次用户阻塞事件中的所有相同类型事件,都会合并成一次更新

  • 事件系统如何模拟冒泡和捕获阶段?

通过队列,冒泡事件插入在队列尾部,捕获事件插入队列的头部

  • 如何通过 dom 元素找到与之匹配的fiber?

通过原生事件对象获取触发事件的DOM元素,通过DOM元素上的内置属性,获取fiber节点

  • 阻止默认行为 ?

原生事件: e.preventDefault() 和 return false 可以用来阻止事件默认行为,由于在 React 中给元素的事件并不是真正的事件处理函数。所以导致 return false 方法在 React 应用中完全失去了作用。

React事件 在React应用中,可以用 e.preventDefault() 阻止事件默认行为,这个方法并非是原生事件的 preventDefault ,由于 React 事件源 e 也是独立组建的,所以 preventDefault 也是单独处理的。

  • 事件是绑定在真实的dom上吗?如何不是绑定在哪里?

绑定在root节点上

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2025 SunZhiqi

此时无声胜有声!

支付宝
微信