问题
问:React 为什么有自己的事件系统?
简答:
- 统一管理事件;
- 跨浏览器、跨平台兼容性好。
答:不同浏览器,对事件处理存在兼容性问题。若要实现兼容全浏览器事件系统,需抹平各个浏览器事件系统的差异。
v17 前,事件绑定在document
,之后绑定在应用对应容器container
上,将事件绑定在同一容器统一管理。
事件不是绑定在真实 DOM 上,所以 React 需模拟一套事件流:事件捕获 —> 事件源 —> 事件冒泡,同时重新事件源event
。
React 事件系统可分为三个阶段:
- 事件合成系统,初始化会注册不同的事件插件;
- 在一次渲染中,对事件标签上的事件进行收集,在
container
上注册事件; - 一次用户交互,事件触发,到事件执行一系列过程。
问:如何处理事件源对象?
问:什么是事件合成?
答:
事件合成概念:React 应用中,元素绑定的事件并不是原生事件,而是 React 的合成事件。如:onClick
是由click
合成,onChange
是blur\focus\change\keydown\keyup
合成。
- React事件不是绑定在元素上,而是绑定在顶部容器上,v17之前绑定在document上,v17之后绑定在app容器上,利于一个HTML下存在多个应用(微前端);
- 绑定事件不是一次性绑定上所以事件,是发现啥事件,绑定啥事件;
问:如何实现批量更新?
问:为什么不能用**return false**
来阻止事件的默认行为?
答:原生事件:e.preventDefault() / return false
来阻止事件默认行为。但在 React 中return false
无效。
React 事件:在 React 中事件源(e)已被重写,只能通过e.preventDefault()
来阻止事件默认行为,此方法并非原生事件。
问:React 如何模拟阻止事件冒泡?
答:(重点:事件队列执行)
在for
循环,执行事件队列时,若事件源event
中有event.stopPropagation()
则会跳出循环break
,队列后边的事件将不再执行,以此来模拟阻止事件冒泡。
问:事件系统如何模拟冒泡、捕获阶段?
答:如图:第三步:形成事件执行队列。(注意:使用while
循环进行收集,直到instance === null
)
- 第一步通过原生 DOM 找到对应 fiber 对象,根据此 fiber 向上遍历,遇到 元素数据类型 就收集,收集到数组中;
- 若遇到
onClickCapture
捕获事件,就会通过unshift
添加到数组前边,模拟捕获阶段; - 若遇到
onClick
冒泡事件,就会通过push
添加到数组后边,模拟冒泡阶段; - 一直收集到最顶层 App,形成执行队列,接下来就依次执行队列中的函数。
问:如何通过 DOM 元素找到与之对应的 fiber 对象?
答:如上图。
问:事件是绑定在真实 DOM 上吗?若不是,那绑定在哪里?(考察:事件绑定)
答:给元素绑定的事件,会保存在 DOM 元素对应的 fiber 对象的memorizedProps
属性上。
绑定在document
上的事件,是 React 统一的事件处理函数dispatchEvent
,React 通过这个函数去代理事件逻辑,包括 React 批量更新等逻辑。
React 事件触发,首先执行的就是dispatchEvent
,那dispatchEvent
如何知道是什么事件触发的呢?事件注册时,通过bind
已将参数绑定到dispatchEvent
上了。如下,绑定click
事件:
const listener = dispatchEvent.bind(null, 'click', eventSystemFlags, document)
// 真正实现绑定
document.addEventListener('click', listener, false)
问:v17 事件系统有哪些改变?
问:每此点击事件都需要重新收集每个节点的事件码?
答:是的。因为有些元素节点上绑定的事件是动态的。
知识点
React 事件系统中的“假”。
- 给元素绑定的事件,不是真正的事件处理函数;
- 在冒泡/捕获阶段绑定的事件,也不是在冒泡/捕获阶段执行的;
- 事件处理函数中拿到的事件源
event
,也不是真正的事件源对象。