react 事件机制
# 01. react中的事件处理
在语法上:
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
- 使用 JSX 语法时需要传入一个函数作为事件处理函数,而不是一个字符串。
传统:
<button onclick="activateLasers()">
Activate Lasers
</button>
React 中
<button onClick={activateLasers}>
Activate Lasers
</button>
- 阻止默认事件执行:不能return false 来阻止默认行为,必须通过
preventDefault
来阻止
传统:
<form onsubmit="console.log('You clicked submit.'); return false">
<button type="submit">Submit</button>
</form>
react 中
function Form() {
function handleSubmit(e) {
// e 是一个合成事件
e.preventDefault();
console.log('You clicked submit.');
}
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
提示
通过 在回调中使用箭头函数绑定this,此语法问题在于每次渲染组件时都会创建不同的回调函数。如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染
<button onClick={() => this.handleClick()}>Click me
<button onClick={this.deleteRow.bind(this, id)}>Delete Row
# 02. 合成事件的实现机制
在react底层,主要对合成事件做了两件事:事件委派 和 自动绑定
# 事件委派
react的事件代理机制,不会把事件处理函数直接绑定到真实的节点上,而是把所有的事件绑定到结构的最外层,使用一个统一的事件监听器。 当组件挂载或者卸载时,只是在这个统一的事件监听器上插入或者删除一些对象;当事件发生时,首先被这个统一的事件监听器处理,然后再映射里找到真正的事件处理函数并调用。 这样做简化了事件梳理和回收机制。
# 自动绑定
在react组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this
为当前组件。而且React还会对这种引用进行缓存,以达到CPU和内存的最优化。
在使用ES6 classes 或者纯函数的时候,这种绑定就不复存在了。需要我们手动绑定this
- bind方法
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
del(){
console.log('del')
}
render() {
return (
<div className="home">
<span onClick={this.del.bind(this)}>Test</span>
</div>
);
}
}
提示
如果方法只绑定,不传参,stage0
草案提供给了 - 双冒号语法。 其作用与this.del.bind(this)
一致。babel已经实现了该提案。
<span onClick={::this.del}>Test</span>
- 构造器内声明 在组件的构造函数内完成了this的绑定,这种绑定方法的好处在于仅需要进行一次绑定,而不需要每次调用事件监听器时去执行绑定操作。
constructor(props) {
super(props);
this.del = this.del.bind(this)
}
- 箭头函数 箭头函数不仅仅是函数的语法糖,它还自动绑定了定义此函数作用域的this, 因此我们不需要对它进行bind操作。 写法一:
del = () => {
console.log('del')
}
render() {
return (
<div className="home">
<span onClick={this.del}>11111</span>
</div>
);
}
写法二:
del(){
}
render() {
return (
<div className="home">
<span onClick={() => this.del()}>11111</span>
</div>
);
}
# 03. 合成事件与原生事件混用
应该尽量避免在React中混用合成事件和原生DOM事件。另外,用reactEvent.nativeEvent.stopPropagation() 来阻止冒泡时不行的。阻止React事件冒泡的行为只能用于React合成系统中,且没办法阻止原生事件的冒泡。反之,在原生事件中的阻止冒泡行为,可以阻止React合成事件的传播。
React的合成事件系统只是原生DOM事件系统的一个子集。它仅仅实现了DOM Level3的事件接口,并且统一了浏览器的兼容性问题。 有些事件React没有实现,或者说受限制,没有办法去实现。比如window的resize事件。
对于无法使用React的合成事件的场景,我们需要使用原生事件来完成。
# 04. 合成事件/js原生事件对比
主要从四个方面对比
- 事件传播与阻止事件传播
浏览器原生DOM事件的传播可以分为3个阶段:
事件捕获
,目标对象本身的事件处理程序调用
,事件冒泡
。
- 事件捕获会优先调用结构树最外层的元素上绑定的事件监听器
- 然后依次向内调用,一直调用到目标元素上的事件监听器为止。(可以在e.addEventListener()的第三个参数设置为true时,为元素e注册捕获事件处理程序,并且在事件传播的第一个阶段调用)
- 事件冒泡与事件捕获相反,从目标元素向外传播,由内向外直到最外层。
阻止原生事件传播需要使用e.stopPropagation(),不过对于该方法的浏览器,需要使用.cancalBubble = true来阻止。在react 合成事件中,只需要使用stopProgapation()
- 事件类型 React 合成事件的事件类型是js原生事件类型的一个子集。
- 事件绑定方式 原生绑定方式有多种,而React的合成事件的绑定方式简单
- 事件对象 原生DOM事件对象在W3C标准和IE标准下存在差异。在低版本的IE浏览器中,使用window.event 来获取事件对象。而在React合成事件系统中,不存在这种兼容性问题,在事件处理函数中可以得到一恶搞合成事件对象。
# 05. react 事件机制特点
由于fiber机制的特点,生成一个fiber节点时,它对应的dom节点有可能还未挂载,onClick这样的事件处理函数作为fiber节点的prop,不能直接被绑定到真实的DOM节点上。所以React提供了一种“顶层注册,事件收集,统一触发”的事件机制。利用fiber树的层级关系来生成事件执行路径,进而模拟事件捕获和冒泡
- 顶层注册: 在root元素上绑定一个统一的事件处理函数
- 事件收集:事件触发时(实际上是root上的事件处理函数被执行),构造合成事件对象,按照冒泡或捕获的路径去组件中收集真正的事件处理函数
- 统一触发: 在收集过程之后,对所收集的事件逐一执行,并共享同一个合成事件对象。
# 03. react事件注册机制
- 事件注册 - 组件挂载阶段,根据组件内的声明的事件类型-onclick,onchange 等,给 root(17之前是添加到document) 上添加事件 -addEventListener,将事件绑定到root元素上
事件监听器listener:是执行 createEventListenerWrapperWithPriority
的调用结果,根据优先级和事件名的映射关系返回不同的事件。
- 事件存储 - 就是把 react 组件内的所有事件统一的存放到一个对象里,缓存起来,为了在触发事件的时候可以查找到对应的方法去执行。
# 04. react事件合成机制
合成事件对象: 经过React合成的SyntheticEvent对象
- 对原生事件的封装
- 对某些原生事件的升级和改造
- 不同浏览器事件兼容的处理
- 通过将事件 normalize 以让他们在不同浏览器中拥有一致的属性。
如果一个节点上同时绑定了合成和原生事件,那么禁止冒泡后执行关系是怎样的?
因为合成事件的触发是基于浏览器的事件机制来实现的,通过冒泡机制冒泡到最顶层元素,然后再由 dispatchEvent统一去处理。
- 原生事件阻止冒泡肯定会阻止合成事件的触发。
- 合成事件的阻止冒泡不会影响原生事件。 浏览器事件的执行需要经过三个阶段,捕获阶段-目标元素阶段-冒泡阶段。
节点上的原生事件的执行是在目标阶段,合成事件的执行是在冒泡阶段,所以原生事件会先合成事件执行,然后再往父节点冒泡。
事件执行路径:当事件对象合成完毕,将事件收集到事件执行路径上,从触发事件的元素开始,依据fiber树的层级结构向上查找,累加上级元素中所有相同类型的事件,最终形成一个具有所有相同类型事件的数组。React自己模拟了一套事件捕获与冒泡的机制。
总结: 由于fiber树的特点,一个组件如果含有事件的prop,那么将会在对应fiber节点的commit阶段绑定一个事件监听到root上,这个事件监听是持有优先级的,将它和优先级机制联系了起来,可以把合成事件机制当作一个协调者,负责去协调合成事件对象、收集事件、触发真正的事件处理函数这三个过程