1 React16事件委托
1.1 document
在React17之前的版本上,在虚拟dom上绑定的事件,都会被放入到一个事件池中
然后React全局只要给document绑定一此事件,在这个事件中监听事件冒泡事件
React再去事件池中找到对应的dom元素与对应的事件将其触发

1.2 验证
这里有一个例子,在React注册之前在document上绑定一个事件(保证自己的事件比React的document事件先触发),然后在真实的dom上绑定一个事件和一个React合成事件。
等待页面渲染完成之后,点击按钮验证
- 准备一个
React组件
export default function () {
const buttonRef = useRef() as React.MutableRefObject<HTMLButtonElement>;
useEffect(() => {
buttonRef.current.addEventListener('click', function (e) {
console.log('真实的button的dom事件')
})
}, [])
const handleClick = useCallback(() => {
console.log('绑定在虚拟dom上的button事件')
}, [])
return <button ref={buttonRef} onClick={handleClick}>按钮</button>
}
然后在React依赖插入之前在document上绑定一个事件

之后点击按钮,发现控制台打印如下
真实的button的dom事件
document上的事件
绑定在虚拟dom上的button事件
这就能验证React把所有事件全部挂载到document上这个机制了
甚至,可以在真实事件中阻止事件冒泡,然后就会发现,document事件和React的事件全部都没有触发

点击按钮后,控制台打印如下
真实的button的dom事件
1.3 如何实现
当事件冒泡到document上之后,在事件触发的回调Event中,有一个path参数
里面详细的描述了冒泡顺序

1.4 函数触发参数
可以看下React中事件绑定回调 和 真实dom绑定回调的对象区别
button.addEventListener('click', function (e) {
console.log(e)
})
<button
id="button"
onClick={(e)=>{
console.log(e)
}}
>
按钮
</button>
可以看到,两者的回调是不同的

在 TypeScript 中,这两者的类型也是不同的
// 真实dom
button.addEventListener('click', function (e: MouseEvent) {})
// React合成事件
function(e:React.MouseEvent<HTMLButtonElement>){}
如果想在合成事件参数中获取原生的MouseEvent,可以访问这个参数
e.nativeEvent
1.5 persist
React为了把事件绑定性能做到了极致,做了一件事,当绑定的事件结束之后
合成事件回调对象 就会被立马销毁。下面的例子可以证明此事
const handleClick = useCallback((e) => {
console.log(e)
setTimeout(() => console.log(e))
}, [])

可以在事件执行中调用 e.presist 来保存状态
const handleClick = useCallback((e) => {
e.presist();
console.log(e)
setTimeout(() => console.log(e))
}, [])

2 React中的this
2.1 this消失
说完合成事件之后,再看一下为什么在 class 组件中为什么事件都要进行绑定 this
首先,在js中,this指向其调用者,如下
const obj = {
name: 'obj',
func() {
console.log(this)
}
}
obj.func() // {name: "obj", func: ƒ}
如果把func提取出来执行,那么func在模块化或严格模式中打印的就是undefined
const func = obj.func;
func(); // undefined
那这个操作就很熟悉了,组件代码如下
class App extends Component {
handleClick() {
console.log('handleClick')
}
render() {
return (
<button onClick={this.handleClick}>按钮</button>
);
}
}
this.handleClick 中的 handleClick 被提取出来,放在一个事件池中 [......]
之后被触发,这个时候,this 早已经不是指向那个组件了
2.2 绑定this指向
在以前,this绑定一共又三种方案,并且三种方案都有利弊
- 虚拟dom直接
bind绑定
<button onClick={this.handleClick.bind(this)}>按钮</button>
缺陷: 每当组件更新,函数就会bind一次,影响性能
- 构造函数绑定
constructor(){
this.handleClick = this.handleClick.bind(this)
}
缺陷: 每个函数都要bind一次,导致代码冗余臃肿
- 直接绑定一共匿名函数
<button onClick={()=>this.handleClick()}>按钮</button>
缺陷: 上面两个缺陷集合体
2.3 箭头函数
箭头函数的this指向永远指向其函数所在的位置的this指向,如下
const obj = {
name: 'obj',
func() {
const exec = () => console.log(this);
exec();
}
}
obj.func(); // {name: "obj", func: ƒ}}
所以,在组件中可以这么写
class App extends Component {
handleClick = () => {
console.log('handleClick')
}
render() {
return (
<button onClick={this.handleClick}>按钮</button>
);
}
}
3 React 17事件委托
先将项目中的React版本升级到17版本
$ yarn add react@17.0.1 react-dom@17.0.1
还是举刚才 React 把 事件绑定在 document 上的例子验证,一摸一样,只是把 React 版本升级到17
React16的执行顺序如下
真实的button的dom事件
document上的事件
绑定在虚拟dom上的button事件
那么React17 的执行顺序变成了这样
真实的button的dom事件
绑定在虚拟dom上的button事件
document上的事件
这是因为 React17 支持在页面中共存多个 React版本 ,如果把所有的事件全部绑定在一共document上,便会出现问题
所以,在17版本上,事件委托不放在 document 上,而是放在执行的根节点上,如 #root
ReactDOM.render(
<HelloWorld />,
document.getElementById('root') as HTMLElement
);

常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论