javascript ·

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)

react移除监听事件无效
我想看到了上图的输出结果,大家应该明白了为何加上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)
    }
````
另一种方式则是使用箭头函数了:
```javascript
handleScroll = ()=>{
    this.setStaete()  //举例不一定用到setState
    }
 componentWillMount(){
        window.addEventListener('scroll', this.handleScroll);
    }
    /** 组件销毁后必要的清理*/
    componentWillUnmount(){
        window.removeEventListener('scroll', this.handleScroll)
    }

参与评论