React合成事件原理解析

目录
  • 事件介绍
    • 什么是事件?
    • 举个栗子
    • 代码实现
  • React合成事件基础知识
    • 什么是合成事件?
    • 在React中事件的写法和原生事件写法的区别?
    • 为什么会有合成事件?
    • 合成事件机制简述
  • React合成事件实现原理
    • 事件注册
    • 事件触发-事件监听器做了什么
    • React中模拟冒泡和捕获
  • 总结

事件介绍

什么是事件?

事件是在编程时系统内发生的动作或者发生的事情,而开发者可以某种方式对事件做出回应,而这里有几个先决条件

  • 事件对象
  • 给事件对象注册事件,当事件被触发后需要做什么
  • 事件触发

举个栗子

在机场等待检票,听到广播后排队检票

  • 事件对象,也就是检票事件
  • 事件注册/监听,也就是我们在机场等待的时候
  • 事件触发,广播响起开始检票
  • 事件触发之后的回调,我们开始排队...

代码实现

// 检票事件
var event = new Event('check-ticket');
// 事件注册,并编写回调
elem.addEventListener('check-ticket', function (e) {
  // 开始排队
  LiningUp()
});
// 事件触发,广播开始检票
elem.dispatchEvent(event);

React合成事件基础知识

什么是合成事件?

不是浏览器本身触发的事件,自己创建和触发的事件。

在React中事件的写法和原生事件写法的区别?

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
// HTML中的写法
<button onclick="handleClick()">
  Activate Lasers
</button>
// React中的写法
<button onClick={handleClick}>
  Activate Lasers
</button>

为什么会有合成事件?

  • 对事件进行归类,可以在事件产生的任务上包含不同的优先级
  • 提供合成事件对象,抹平浏览器的兼容性差异

合成事件机制简述

提供了一种“顶层注册,事件收集,统一触发”的事件机制

  • “顶层注册”,其实是在root元素上绑定一个统一的事件处理函数
  • “事件收集”, 事件触发时(实际上是root上的事件处理函数被执行),构造合成事件对象,按照冒泡或捕获的路径去组件中收集真正的事件处理函数
  • “统一触发”,在收集过程之后,对收集的事件逐一执行,并共享同一个合成事件对象

React合成事件实现原理

事件注册

时机:在fiber节点进入render阶段的complete阶段时,名为onClick的props会被识别为事件进行处理

function setInitialDOMProperties(
  tag: string,
  domElement: Element,
  rootContainerElement: Element | Document,
  nextProps: Object,
  isCustomComponentTag: boolean,
): void {
  for (const propKey in nextProps) {
    if (!nextProps.hasOwnProperty(propKey)) {
      ...
    } else if (registrationNameDependencies.hasOwnProperty(propKey)) {
        // 如果propKey属于事件类型,则进行事件绑定
        ensureListeningTo(rootContainerElement, propKey, domElement);
      }
    }
  }
}
// registrationNameDependencies为合成事件名和真实事件的映射,格式如下:
{
  onChange: ['change', 'click', 'focusin', 'focusout', 'input', 'keydown', 'keyup', 'selectionchange']
  onChangeCapture: ['change', 'click', 'focusin', 'focusout', 'input', 'keydown', 'keyup', 'selectionchange']
  onClick: ['click']
}

调用ensureListeningTo进行事件绑定

然后调用DOMPluginEventSystem.js中的addTrappedEventListener进行事件注册,而监听器listener是React通过createEventListenerWrapperWithPriority创建出优先级不同的时间监听包装器

总的来说,会有三种事件监听包装器:

  • dispatchDiscreteEvent: 处理离散事件
  • dispatchUserBlockingUpdate:处理用户阻塞事件
  • dispatchEvent:处理连续事件

事件触发-事件监听器做了什么

负责以不同的优先级权重来触发真正的事件流程

createEventListenerWrapperWithPriority中,根据eventPriority事件优先级不同调用事件dispatchDiscreteEventdispatchUserBlockingUpdatedispatchUserBlockingUpdatedispatchEvent进行事件绑定

export function createEventListenerWrapperWithPriority(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
): Function {
  const eventPriority = getEventPriorityForPluginSystem(domEventName);
  let 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,
  );
}

在每一个事件绑定函数中,会调用dispatchEventsForPlugins函数

  • 合成事件,放入dispatchQueue中

    而dispatchQueue由两部分组成

    • 合成事件对象
    • 多个listeners(可以共用一个合成事件对象)
  • 执行事件执行路径下的事件,从dispatchQueue中取出事件对象events和具体执行路径listeners,然后遍历执行
function dispatchEventsForPlugins(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  nativeEvent: AnyNativeEvent,
  targetInst: null | Fiber,
  targetContainer: EventTarget,
): void {
  const nativeEventTarget = getEventTarget(nativeEvent);
  const dispatchQueue: DispatchQueue = [];
  // 通过不同事件类型插件进行事件对象合成,将合成事件放入事件队列中:dispatchQueue中
  extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer,
  );
  // 执行dispatchQueue中具体事件执行路径下的事件
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}
// 通过extractEvents合成的dispatchQueue的格式如下
[
  {
    // event是合成事件对象
    event,
    // 具体的事件执行路径,currentTarget、listener等信息
    listeners: [
      {
        currentTarget: div.counter,
        instance: FiberNode {tag: 5, key: null, elementType: 'div', type: 'div', stateNode: div.counter, …},
        listener: e => {…}
      },
      {
        currentTarget: div.counter-parent,
        instance: FiberNode {tag: 5, key: null, elementType: 'div', type: 'div', stateNode: div.counter, …},
        listener: e => {…}
      }
    ]
  }
]

React中模拟冒泡和捕获

原理:收集的事件放在dispatchQueue数组中,而冒泡和捕获的区别在于执行时机和顺序,那么我们只需要对数组按照不同顺序循环执行即可

function processDispatchQueueItemsInOrder(
  event: ReactSyntheticEvent,
  dispatchListeners: Array<DispatchListener>,
  inCapturePhase: boolean,
): void {
  let previousInstance;
​
  if (inCapturePhase) {
    // 事件捕获倒序循环
    for (let i = dispatchListeners.length - 1; i >= 0; i--) {
      const {instance, currentTarget, listener} = dispatchListeners[i];
      if (instance !== previousInstance && event.isPropagationStopped()) {
        return;
      }
      // 执行事件,传入event对象,和currentTarget
      executeDispatch(event, listener, currentTarget);
      previousInstance = instance;
    }
  } else {
    // 事件冒泡正序循环
    for (let i = 0; i < dispatchListeners.length; i++) {
      const {instance, currentTarget, listener} = dispatchListeners[i];
      // 如果事件对象阻止了冒泡,则return掉循环过程
      if (instance !== previousInstance && event.isPropagationStopped()) {
        return;
      }
      executeDispatch(event, listener, currentTarget);
      previousInstance = instance;
    }
  }
}

总结

以上就是最近学习React源码之--合成事件的知识整理啦!

首先会在fiber节点进入render阶段的complete阶段时,将事件监听绑定在root上。然后调用ensureListeningTo进行事件绑定,生成事件合成对象收集事件触发真正的事件

(0)

相关推荐

  • react合成事件与原生事件的相关理解

    1. 原生事件 原生事件就是js的原生事件,如通过document.addEventListener来设置的监听事件. 在react中即使有自己的一套事件机制(见下面合成事件),但有时候的业务场景我们仍然需要使用原生事件.比如我们封装一个Modal弹窗组件,需要在点击非弹窗区域时关掉弹窗,此时我们只能针对document进行原生点击事件监听. 由于原生事件需要绑定在真实DOM上,所以一般是在componentDidMount阶段或者组件/元素的ref的函数执行阶段进行绑定操作,并且注意要在com

  • React合成事件详解

    react合成事件指的是react用js模拟了一个Dom事件流.(fiber树模拟Dom树结构) 合成事件的事件流在fiber树中发生捕获和冒泡. 从点击输入框开始 当你点击input输入框,react在根节点(注1)监听到focus事件(注2)(注3). 如何从原生事件找到对应的虚拟Dom? 此时,react得到的信息只有原生事件对象(nativeEvent).react通过nativeEvent对应的Dom(eventTarget),沿着Dom树向上找到距离该eventTarget最近的被r

  • React事件处理的机制及原理

    React中的事件处理 在React元素中绑定事件有两点需要注意: (1)在React中,事件命名采用驼峰命名方式,而不是DOM元素中的小写字母命名方式.例如onclick要写成onClick,onchange要写成onChange等. (2)处理事件的响应函数要以对象的形式赋值给事件属性,而不是DOM中的字符串形式.例如在DOM中绑定一个点击事件应该写成: <button onclick="clickButton()"> Click </button> 而在R

  • React合成事件原理解析

    目录 事件介绍 什么是事件? 举个栗子 代码实现 React合成事件基础知识 什么是合成事件? 在React中事件的写法和原生事件写法的区别? 为什么会有合成事件? 合成事件机制简述 React合成事件实现原理 事件注册 事件触发-事件监听器做了什么 React中模拟冒泡和捕获 总结 事件介绍 什么是事件? 事件是在编程时系统内发生的动作或者发生的事情,而开发者可以某种方式对事件做出回应,而这里有几个先决条件 事件对象 给事件对象注册事件,当事件被触发后需要做什么 事件触发 举个栗子 在机场等待

  • react源码合成事件深入解析

    目录 引言 导火线 事件委托 合成事件特点 React 事件系统 事件注册 enqueuePutListener() listenTo() trapCapturedEvent 与 trapBubbledEvent 事件存储 事件分发 事件执行 构造合成事件 批处理 引言 温馨提示: 下边是对React合成事件的源码阅读,全文有点长,但是!如果你真的想知道这不为人知的背后内幕,那一定要耐心看下去! 最近在做一个功能,然后不小心踩到了 React 合成事件 的坑,好奇心的驱使,去看了 React 官

  • react  Suspense工作原理解析

    目录 Suspense 基本应用 Suspense 原理 基本流程 源码解读 - primary 组件 源码解读 - 异常捕获 源码解读 - 添加 promise 回调 源码解读-Suspense 首次渲染 primary 组件加载完成前的渲染 primary 组件加载完成时的渲染 利用 Suspense 自己实现数据加载 Suspense 基本应用 Suspense 目前在 react 中一般配合 lazy 使用,当有一些组件需要动态加载(例如各种插件)时可以利用 lazy 方法来完成.其中

  • react hooks实现原理解析

    目录 react hooks 实现 Hooks 解决了什么问题 Hooks API 类型 首先接触到的是 State hooks 其次接触到的是 Effect hooks 最后接触到的是 custom hooks Hooks 实现方式 问题一:useState dispatch 函数如何与其使用的 Function Component 进行绑定 react hooks 实现 Hooks 解决了什么问题 在 React 的设计哲学中,简单的来说可以用下面这条公式来表示: UI = f(data)

  • 微信小程序 冒泡事件原理解析

    在微信小程序的事件分为冒泡事件和非冒泡事件: 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递. 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递. WXML的冒泡事件列表: 类型 触发条件 最低版本 touchstart 手指触摸动作开始   touchmove 手指触摸后移动   touchcancel 手指触摸动作被打断,如来电提醒,弹窗   touchend 手指触摸动作结束   tap 手指触摸后马上离开   longpress 手指触摸后,超过350ms再离开

  • React setState是异步还是同步原理解析

    目录 setState异步更新 那么为什么setState设计为异步呢? 如何获取异步的结果 setState一定是异步的吗? setState异步更新 开发中当组件中的状态发生了变化,页面并不会重新渲染.我们必须要通过setState来告知React数据已经发生了变化,重新渲染页面. 先来看下面的例子: constructor() { super(); this.state = { message: "Hello World", }; } changeText() { this.se

  • React实现合成事件的源码分析

    目录 事件绑定 事件触发 结尾 今天尝试学习 React 事件的源码实现. React 版本为 18.2.0 React 中的事件,是对原生事件的封装,叫做合成事件.抽象出一层合成事件,是为了做兼容,抹平不同浏览器之间的差异. 下面会从两个方面进行源码的解读: 事件绑定 事件触发 事件绑定 首先是 React 项目过程启动时,调用 listenToAllSupportedEvents 方法,做合成事件的绑定. // 对应方法 `ReactDOM.createRoot() function cre

  • 深入分析React源码中的合成事件

    目录 热身准备 明确几个概念 事件系统角色划分 事件注册 registerSimpleEvents registerEvents$2 registerEvents$1 registerEvents$3 registerEvents 事件监听 事件派发 合成事件 捕获和冒泡 总结 热身准备 明确几个概念 在React@17.0.3版本中: 所有事件都是委托在id = root的DOM元素中(网上很多说是在document中,17版本不是了): 在应用中所有节点的事件监听其实都是在id = root

随机推荐