React (2)——事件机制

137 阅读2分钟

React 事件机制基于V17之后版本

所有的事件机制都是基于 React v17 版本介绍

重点在于事件委托,以及合成事件机制。

  1. 模拟实现捕获和冒泡 配合代码一起理解 前往github查看代码
  2. 合成事件机制;

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 整个事件机制了

image.png

React v16 事件机制

React16 使用比较少,通常开发通用组件库时需要注意 16 的事件委托是在 document 上的。

  1. React16 中事件是委托到全局的 document 节点的,若外部代码监听 document 的点击事件,e.stopPropagation() 无法阻止其执行;多个 React 版本共存时,事件系统相互覆盖。

  2. 事件处理程序仅在冒泡阶段触发,即使使用 onClickCapture 也是在冒泡阶段模拟捕获行为。

  3. React16 中使用事件池,17 版本只取消事件池。