最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • React 事件机制-v16.13.1

    正文概述 掘金(字节跳动ADFE团队)   2021-03-16   687

    概览

    所谓事件机制,无非就是注册事件与分发事件两个步骤,程序员通过代码来注册事件到react对应的节点上,用户与浏览器发生交互,触发浏览器的原生事件,原生事件被react捕获,从原生事件中拿到原生节点,然后根据原生节点拿到react设计的fiber节点,然后从fiber节点中取出相应的回调来执行它,简单流程就是如此,所以我们这次讨论react事件机制,就是如何注册,以及如何分发

    React 事件机制-v16.13.1

    取出事件

    react初始化过程,简单来讲就是我们的jsx被babel转成一串json,我们将这串json叫做react元素的树,react再将这串json转成fiber树(fiber树主要用来做diff),生成fiber树的过程其实就是,我们对react元素的树进行一个深度优先的遍历,遍历的过程中,对于每个节点,我们所绑定的事件其实是作为一个属性绑定在上面的,遍历完成后,我们需要生成一颗真实DOM的树,生成的时候,每个真实DOM节点都会和fiber节点关联起来(通过一个key值internalInstanceKey

    React 事件机制-v16.13.1

    注册事件

    在我们生成真实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进行事件的分发。

    React 事件机制-v16.13.1

    分发事件

    分发事件,简单来讲就是事件被触发后,从内存取出对应的回调来执行,当用户与浏览器产生交互时,以下事情会先后执行。

    React 事件机制-v16.13.1

    拿到原生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内部也是如此。

    React 事件机制-v16.13.1

    细节就不再过多赘述了,主要做的事是

    1. 对原生事件的封装,基本上是把原生事件的一些属性在代码内部自己定义了一遍。
    2. 对某些原生事件(change,select,beforeInput等)的升级和改造,这类事件注册时会附带注册一些依赖项,例如,给input注册了onchange事件,那么"blur", "change", "click", "focus", "input", "keydown", "keyup", "selectionchange"这些事件全都会被注册,原生只注册一个onchange的话,需要在失去焦点的时候才能触发这个事件,所以这个原生事件的缺陷react也帮我们弥补了。
    3. 不同浏览器事件兼容的处理
    1  const nativeEventTarget = getEventTarget(nativeEvent);
    2  const dispatchQueue: DispatchQueue = [];
    3  // 合成事件
    4  extractEvents(dispatchQueue);
    5  // 触发回调
    6  processDispatchQueue(dispatchQueue, eventSystemFlags);
    

    合成过程

    1. 根据事件类型选择插件进行合成(react将所有事件归纳进了六种插件,事件的合成由插件进行)代码地址:extractEvents
    2. 根据事件的类型,实例化不同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  );
    

    累积所有实例和侦听器

    1. 根据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  }
    
    1. 根据捕获或冒泡阶段给事件队列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
    

    React 事件机制-v16.13.1

    作者向恢进


    起源地下载网 » React 事件机制-v16.13.1

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元