React 事件机制基于V17之后版本
所有的事件机制都是基于 React v17 版本介绍
重点在于事件委托,以及合成事件机制。
- 模拟实现捕获和冒泡 配合代码一起理解 前往github查看代码。
- 合成事件机制;
React 使用插件系统处理不同事件类型: 插件名称 处理的事件类型 SimpleEventPlugin click, submit, focus 等基础事件 ChangeEventPlugin input, textarea 变更事件 SelectEventPlugin select 选择事件 BeforeInputEventPlugin beforeinput 事件
例如onChange事件:
源码位置:packages/react-dom-bindings/src/events/plugins/ChangeEventPlugin.js
React 中onChange事件本质上是多个原生事件合成;多个原生事件都会触发onChange事件
function registerEvents() {
registerTwoPhaseEvent("onChange", [
"change",
"click", // 用于 checkbox/radio
"focusin",
"focusout",
"input",
"keydown",
"keyup",
"selectionchange",
]);
}
// 判断事件类型 & 触发元素
function shouldUseClickEvent(elem: any) {
// Use the `click` event to detect changes to checkbox and radio inputs.
// This approach works across all browsers, whereas `change` does not fire
// until `blur` in IE8.
const nodeName = elem.nodeName;
return (
nodeName &&
nodeName.toLowerCase() === "input" &&
(elem.type === "checkbox" || elem.type === "radio")
);
}
function getTargetInstForClickEvent(
domEventName: DOMEventName,
targetInst: null | Fiber
) {
if (domEventName === "click") {
return getInstIfValueChanged(targetInst);
}
}
function getTargetInstForInputOrChangeEvent(
domEventName: DOMEventName,
targetInst: null | Fiber
) {
if (domEventName === "input" || domEventName === "change") {
return getInstIfValueChanged(targetInst);
}
}
// 合成事件核心代码
function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: null | EventTarget,
) {
const targetNode = targetInst ? getNodeFromInstance(targetInst) : window;
let getTargetInstFunc, handleEventFunc;
if (shouldUseChangeEvent(targetNode)) {
getTargetInstFunc = getTargetInstForChangeEvent;
} else if (isTextInputElement(((targetNode: any): HTMLElement))) {
if (isInputEventSupported) {
getTargetInstFunc = getTargetInstForInputOrChangeEvent;
} else {
getTargetInstFunc = getTargetInstForInputEventPolyfill;
handleEventFunc = handleEventsForInputEventPolyfill;
}
} else if (shouldUseClickEvent(targetNode)) {
getTargetInstFunc = getTargetInstForClickEvent;
} else if (
targetInst &&
isCustomElement(targetInst.elementType, targetInst.memoizedProps)
) {
getTargetInstFunc = getTargetInstForChangeEvent;
}
if (getTargetInstFunc) {
const inst = getTargetInstFunc(domEventName, targetInst);
if (inst) {
createAndAccumulateChangeEvent(
dispatchQueue,
inst,
nativeEvent,
nativeEventTarget,
);
return;
}
}
if (handleEventFunc) {
handleEventFunc(domEventName, targetNode, targetInst);
}
// When blurring, set the value attribute for number inputs
if (domEventName === 'focusout' && targetInst) {
// These props aren't necessarily the most current but we warn for changing
// between controlled and uncontrolled, so it doesn't matter and the previous
// code was also broken for changes.
const props = targetInst.memoizedProps;
handleControlledInputBlur(((targetNode: any): HTMLInputElement), props);
}
}
function createAndAccumulateChangeEvent(
dispatchQueue: DispatchQueue,
inst: null | Fiber,
nativeEvent: AnyNativeEvent,
target: null | EventTarget,
) {
// 标记这个事件循环需要状态恢复
enqueueStateRestore(((target: any): Node));
// 收集两阶段的事件监听器
const listeners = accumulateTwoPhaseListeners(inst, 'onChange');
if (listeners.length > 0) {
// 创建合成事件
const event: ReactSyntheticEvent = new SyntheticEvent(
'onChange',
'change',
null,
nativeEvent,
target,
);
// 将事件和监听器添加到调度队列
dispatchQueue.push({event, listeners});
}
}
/**
对 checkbox 和 radio 使用 click 事件而不是 change 事件,以确保跨浏览器兼容性
使用合成事件系统来统一处理不同的事件类型
*/
看懂这个图就明白的 React 整个事件机制了
React v16 事件机制
React16 使用比较少,通常开发通用组件库时需要注意 16 的事件委托是在 document 上的。
-
React16 中事件是委托到全局的 document 节点的,若外部代码监听 document 的点击事件,
e.stopPropagation()无法阻止其执行;多个 React 版本共存时,事件系统相互覆盖。 -
事件处理程序仅在冒泡阶段触发,即使使用 onClickCapture 也是在冒泡阶段模拟捕获行为。
-
React16 中使用事件池,17 版本只取消事件池。