事件原理(旧版)
React 事件系统可分为三个部分:
- 第一个部分是事件合成系统,初始化会注册不同的事件插件。
- 第二个就是在一次渲染过程中,对事件标签中事件的收集,向 container 注册事件。
- 第三个就是一次用户交互,事件触发,到事件执行一系列过程。
事件合成
- React 的事件不是绑定在元素上的,而是统一绑定在顶部容器上,在 v17 之前是绑定在 document 上的,在 v17 改成了 app 容器上。这样更利于一个 html 下存在多个应用(微前端)。
- 绑定事件并不是一次性绑定所有事件,比如发现了 onClick 事件,就会绑定 click 事件,比如发现 onChange 事件,会绑定
[blur,change ,focus ,keydown,keyup]多个事件。 - React 事件合成的概念:React 应用中,元素绑定的事件并不是原生事件,而是React 合成的事件
事件插件
registrationNameModules 记录了 React 事件(比如 onBlur )和与之对应的处理插件的映射,比如上述的 onClick ,就会用 SimpleEventPlugin 插件处理,onChange 就会用 ChangeEventPlugin 处理。应用于事件触发阶段,根据不同事件使用不同的插件。
registrationNameDependencies保存了 React 事件和原生事件对应关系,这就解释了为什么上述只写了一个 onChange ,会有很多原生事件绑定在 document 上。在事件绑定阶段,如果发现有 React 事件,比如 onChange ,就会找到对应的原生事件数组,逐一绑定。
事件绑定
diffProperties 函数在 diff props 如果发现是合成事件( onClick ) 就会调用 legacyListenToEvent 函数。注册事件监听器。
在注册的时候,通过 bind ,把参数绑定给 dispatchEvent 了。 React 事件触发,首先执行的就是 dispatchEvent 。
事件触发
-
第一步:批量更新
dispatchEvent 执行会传入真实的事件源 button 元素本身。通过元素可以找到 button 对应的 fiber ,fiber 和原生 DOM 之间是如何建立起联系的呢?React 在初始化真实 DOM 的时候,用一个随机的 key internalInstanceKey 指针指向了当前 DOM 对应的 fiber 对象,fiber 对象用 stateNode 指向了当前的 DOM 元素。
-
第二步:合成事件源
接下来会通过 onClick 找到对应的处理插件 SimpleEventPlugin ,合成新的事件源 e ,里面包含了 preventDefault 和 stopPropagation 等方法。 -
第三步:形成事件执行队列
在第一步通过原生 DOM 获取到对应的 fiber ,接着会从这个 fiber 向上遍历,遇到元素类型 fiber ,就会收集事件,用一个数组收集事件:- 如果遇到捕获阶段事件 onClickCapture ,就会 unshift 放在数组前面。以此模拟事件捕获阶段。
- 如果遇到冒泡阶段事件 onClick ,就会 push 到数组后面,模拟事件冒泡阶段。
- 一直收集到最顶端 app ,形成执行队列,在接下来阶段,依次执行队列里面的函数。
关于阻止冒泡
原生事件: e.preventDefault() 和 return false 可以用来阻止事件默认行为,由于在 React 中给元素的事件并不是真正的事件处理函数。所以导致 return false 方法在 React 应用中完全失去了作用。
React事件:在React应用中,可以用 e.preventDefault() 阻止事件默认行为,这个方法并非是原生事件的 preventDefault ,由于 React 事件源 e 也是独立组建的,所以 preventDefault 也是单独处理的。
// 真正的React阻止冒泡
// 是让执行队列在冒泡执行阶段停止遍历
// legacy-events/EventBatching.js
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... */
}
}
}
事件原理(新版(v18))
事件绑定——事件初始化
在 React 新版的事件系统中,在 createRoot 会一次性向外层容器上注册完全部事件。
事件触发
当发生一次点击事件,React 会根据事件源对应的 fiber 对象,根据 return指针向上遍历,收集所有相同的事件,比如是 onClick,那就收集父级元素的所有 onClick 事件,比如是 onClickCapture,那就收集父级的所有 onClickCapture。
得到了 dispatchQueue 之后,就需要 processDispatchQueue 执行事件了,这个函数的内部会经历两次遍历:
第一次遍历 dispatchQueue,通常情况下,只有一个事件类型,所有 dispatchQueue 中只有一个元素。
接下来会遍历每一个元素的 listener
如果一个事件中执行了 e.stopPropagation ,那么事件源中就能感知得到,接下来就可以通过 event.isPropagationStopped 来判断是否阻止冒泡,如果组织,那么就会退出,这样就模拟了事件流的执行过程,以及阻止事件冒泡。
新旧对比
新旧事件原理的区别
附赠
transition 原理