原理篇:事件系统

React为什么要实现一套自己的事件系统?

A:为了兼容各浏览器对事件处理的差异性/

冒泡和捕获

  1. 冒泡:事件从最内层的元素开始,一直向上传播,直到document对象。
  2. 捕获:事件从最外层开始,直到最具体的元素。
  3. addEventListener(event, function,useCapture):useCapture=true,事件捕获阶段调用处理函数;useCapture=false,事件冒泡阶段调用处理函数;

react 中, 想要在捕获阶段执行可以将事件后面加上 Capture 后缀。

<div>
    <button onClick={handleClick} onClickCapture={ handleClickCapture }  >点击</button>
</div>
  1. 阻止冒泡:e.stopPropagation();
  2. 阻止默认行为

    原生事件: e.preventDefault() 和 return false ;

    react事件:e.preventDefault()

    在react中给元素绑定的事件并不是真正的事件处理函数,所以无法使用return false 阻止默认行为。

React如何模拟阻止事件冒泡

function runEventsInBatch(){
    const dispatchListeners = event._dispatchListeners;
    if (Array.isArray(dispatchListeners)) {
    for (let i = 0; i < dispatchListeners.length; i++) {
      if (event.isPropagationStopped()) { /* 判断是否已经阻止事件冒泡 */
        break;
      }    
      dispatchListeners[i](event) /* 执行真正的处理函数 及handleClick1... */
    }
  }
}

事件合成

  1. React的事件不是绑定在元素上,而是统一绑定在顶部的容器上
  2. 元素绑定的事件不是原生事件,而是react合成事件。比如onClick绑定的是click事件,onChange事件又blur ,change ,focus等多个事件组成。

绑定在document上的事件处理函数,是上面所写的handleClick吗?

A:绑定在 document 的事件,是由react中的dispatchEvent统一处理。只要是 React 事件触发,首先执行的就是 dispatchEvent 。

const listener = dispatchEvent.bind(null,'click',eventSystemFlags,document) 
/* TODO: 重要, 这里进行真正的事件绑定。*/
document.addEventListener('click',listener,false) 

事件绑定

给元素绑定的事件 onChange onClick ,最后去了哪里呢?

A: 会保存在对应 DOM 元素对应的 fiber 对象上。通过dispatchEvent 注册事件,dispatchEvent的执行会传入事件元素本身,通过元素可以找到对应的 fiber。
真实DOM和fiber是通过key和stateNode一一对应的。

const listener = dispatchEvent.bind(null,'click',eventSystemFlags,document) 
/* TODO: 重要, 这里进行真正的事件绑定。*/
document.addEventListener('click',listener,false) 

image

事件触发流程

下面代码触发顺序是怎样的?

export default function Index(){
    const handleClick1 = () => console.log(1)
    const handleClick2 = () => console.log(2)
    const handleClick3 = () => console.log(3)
    const handleClick4 = () => console.log(4)
    return <div onClick={ handleClick3 }  onClickCapture={ handleClick4 }  >
        <button onClick={ handleClick1 }  onClickCapture={ handleClick2 }  >点击</button>
    </div>
}
  1. dispatchEvent 执行会传入真实的事件源 button 元素本身,接下来就是批量更新。
export function batchedEventUpdates(fn,a){
    isBatchingEventUpdates = true; //打开批量更新开关
    try{
       fn(a)  // 事件在这里执行
    }finally{
        isBatchingEventUpdates = false //关闭批量更新开关
    }
}
  1. 通过 onClick 找到对应的处理插件 SimpleEventPlugin ,合成新的事件源 e 。
  2. 形成事件执行队列:

    3.1 如果遇到捕获阶段事件 onClickCapture ,就会 unshift 放在数组前面。以此模拟事件捕获阶段。

    3.2 如果遇到冒泡阶段事件 onClick ,就会 push 到数组后面,模拟事件冒泡阶段。

    3.3 一直收集到最顶端 app ,形成执行队列。
  3. 依次执行队列的函数。

如上点击一次按钮,4个事件执行顺序是这样的:

  • 第一次事件源查找在button上。
  • handleClick1冒泡事件push处理,handleClick2捕获事件unshift处理。形成结构 [ handleClick2 , handleClick1 ]。
  • 接着向上查找事件源,遇到div。
  • handleClick3 冒泡事件 push 处理,handleClick4 捕获事件 unshift 处理。[handleClick4, handleClick2 , handleClick1, handleClick3 ]
  • 依次执行数组里面的事件,所以打印 4 2 1 3。

面试题1:react中的事件是怎么绑定的?

A: react的事件都是合成事件,在17之前被统一绑定在document上,17之后被绑定在app容器上。

面试题2: react是如何确定绑定事件对应的元素?

A: 绑定的事件会保存在DOM对应的fiber上。通过dispatchEvent注册事件,dispatchEvent在执行的时候会传入事件元素本身,通过该元素可以找到对应的fiber。元素fiber和dom是一一对应的。

posted @ 2022-01-04 09:27  webLion200  阅读(124)  评论(0编辑  收藏  举报