react移除监听事件无效
问题描述
首先先来复现一下代码,背景是这样的,需要监听一下scroll事件,但是监听函数需要当前组件的this环境,所以监听函数上需要bind(this), 但是这样操作后发现无法removeEventListener这个监听,提示: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
handleScroll(){
this.setStaete() //举例不一定用到setState
}
componentWillMount(){
window.addEventListener('scroll', this.handleScroll.bind(this));
}
/** 组件销毁后必要的清理*/
componentWillUnmount(){
window.removeEventListener('scroll', this.handleScroll.bind(this))
}
当我这样写了以后发现销毁组件的时候无法销毁。其实原因就在于添加了bind(this),如果没有他就会很方便的移除,一旦加上了bind(this),就失败了。
addEventListener、removeEventListener与事件处理程序
首先先来了解一下这两个函数的使用。addEventListener()和removeEventListener()是“DOM2级事件”中定义的两个方法,分别用于添加和删除事件处理程序。所有的DOM节点中都包含这两个方法,并且它们都接受3个参数:要处理的事件名、要为事件添加的处理程序的函数和一个表示事件处理阶段的布尔值。
这里重点是添加处理的函数,addEventListener()和removeEventListener()添加的处理函数必须是同一个函数,什么叫同一个函数呢,就是说这两个函数时相等的,指向同一个地址。我们都知道匿名函数是无法移除的,原因就在于此,直接添加的匿名函数时无法实现另一个匿名函数和此匿名函数相等。
window.addEventListener('scroll', function(e){
console.log(e)
});
window.removeEventListener('scroll', function(e){
console.log(e)
});
如上代码所示,虽然两个匿名函数写的一样,但是在程序上来说,这两个匿名函数是不相同的,所以匿名函数无法移除,这就是根源所在。
那么我们再来看一下为何加了bind(this)之后也无法移除呢。那先来看看bind的作用。
const test = {
name:'oecom',
getName:function(){
console.log(this.name)
}
}
test.getName()//输出oecom
然后我们对代码进行稍加改动
const test = {
name:'oecom',
getName:function(){
console.log(this.name)
}
}
const func = test.getName;
func();//这里直接输出了undefined
const func1 = test.getName.bind(test);
func1();//这里直接输出oecom
很明显,bind是为了指明this的指向,解决this丢失的问题。那么为何会加上bind之后无法移除呢,我们来看一下下面的两个代码
let func1 = test.getName.bind(test);
let func2 = test.getName.bind(test);
let func3 = test.getName;
let func4 = test.getName;
console.log(func1==func2)
console.log(func3==func4)
我想看到了上图的输出结果,大家应该明白了为何加上bind之后会无法移除监听事件了,其根源就在于每次加上bind之后返回的函数并不是指向同一个函数
解决方案
既然明白了原因所在,那么我们来说一下如何解决。具体思路有两个,其中一个是在constructor中提前声明好:
constructor(){
super();
this.scrollEvent = this.handleScroll.bind(this)
}
handleScroll(){
this.setStaete() //举例不一定用到setState
}
componentWillMount(){
window.addEventListener('scroll', this.scrollEvent);
}
/** 组件销毁后必要的清理*/
componentWillUnmount(){
window.removeEventListener('scroll', this.scrollEvent)
}
另一种方式则是使用箭头函数了:
handleScroll = ()=>{
this.setStaete() //举例不一定用到setState
}
componentWillMount(){
window.addEventListener('scroll', this.handleScroll);
}
/** 组件销毁后必要的清理*/
componentWillUnmount(){
window.removeEventListener('scroll', this.handleScroll)
}
好的解决办法
好的解决办法