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介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论