商店的更改侦听器未在componentWillUnmount上移除? [英] Stores' change listeners not getting removed on componentWillUnmount?

查看:160
本文介绍了商店的更改侦听器未在componentWillUnmount上移除?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在reactjs-flux上编写了一个简单的应用程序,并且一切正常,除了收到来自reactjs的警告,告诉我在未安装的组件上调用setState.

我发现这是因为钩挂了组件的changelistener并未从componentWillUnmount的商店中删除.我知道这是因为,当我从Eventemitter打印侦听器列表时,我看到应该被销毁的侦听器仍然存在,并且当我多次装载/卸载同一组件时,列表会变得更大.

我从BaseStore粘贴代码:

 import Constants from '../core/Constants';
import {EventEmitter} from 'events';

class BaseStore extends EventEmitter {
  // Allow Controller-View to register itself with store
  addChangeListener(callback) {
    this.on(Constants.CHANGE_EVENT, callback);
  }

  removeChangeListener(callback) {
    this.removeListener(Constants.CHANGE_EVENT, callback);
  }

  // triggers change listener above, firing controller-view callback
  emitChange() {
    this.emit(Constants.CHANGE_EVENT);
  }
}

export default BaseStore;
 

我从遇到此错误的组件中粘贴相关代码(尽管它在所有组件中都发生了):

 @AuthenticatedComponent
class ProductsPage extends React.Component {
  static propTypes = {
    accessToken: PropTypes.string
  };

  constructor() {
    super();
    this._productBatch;
    this._productBatchesNum;
    this._activeProductBatch;
    this._productBlacklist;
    this._searchById;
    this._searchingById;
    this.state = this._getStateFromStore();
  }

  componentDidMount() {
    ProductsStore.addChangeListener(this._onChange.bind(this));
  }

  componentWillUnmount() {
    ProductsStore.removeChangeListener(this._onChange.bind(this));
  }

  _onChange() {
    this.setState(this._getStateFromStore());
  }
}
 

在这一点上,这真让我发疯.有什么想法吗?

谢谢!

解决方案

简短版本:expect(f.bind(this)).not.toBe(f.bind(this));

更长的解释:

问题的原因是EventEmitter.removeListener要求您传递以前在EventEmitter.addListener中注册的功能.如果您传递对任何其他函数的引用,则它是无提示操作.

在您的代码中,您正在将this._onChange.bind(this)传递给addListener. bind返回与此绑定的 new 函数.然后,您将放弃对该绑定函数的引用.然后,您尝试删除由绑定调用创建的另一个 new 函数,由于没有添加该函数,因此它是一个无操作的操作.

React.createClass自动绑定方法.在ES6中,您需要手动绑定您的构造函数:

 @AuthenticatedComponent
class ProductsPage extends React.Component {
  static propTypes = {
    accessToken: PropTypes.string
  };

  constructor() {
    super();
    this._productBatch;
    this._productBatchesNum;
    this._activeProductBatch;
    this._productBlacklist;
    this._searchById;
    this._searchingById;
    this.state = this._getStateFromStore();
    // Bind listeners (you can write an autoBind(this);
    this._onChange = this._onChange.bind(this);
  }

  componentDidMount() {
    // listener pre-bound into a fixed function reference. Add it
    ProductsStore.addChangeListener(this._onChange);
  }

  componentWillUnmount() {
    // Remove same function reference that was added
    ProductsStore.removeChangeListener(this._onChange);
  }

  _onChange() {
    this.setState(this._getStateFromStore());
  } 

有多种简化绑定的方法-您可以使用ES7 @autobind方法修饰器(例如npm上的autobind-decorator),或编写一个用autoBind(this);在构造函数中调用的autoBind函数.

在ES7中,您(希望)能够使用类属性来获得更方便的语法.如果您愿意作为第一阶段提案的一部分,可以在Babel中启用此功能 http://babeljs.io/docs/plugins/transform-class-properties/.然后,您只需将事件侦听器方法声明为类属性,而不是方法:

_onChange = () => {
    this.setState(this._getStateFromStore());
}

由于_onChange的初始化程序是在构造函数的上下文中调用的,因此箭头函数会将this自动绑定到类实例,因此您可以将this._onChange作为事件处理程序传递而无需手动绑定. /p>

I am coding a simple app on reactjs-flux and everything works fine except I am receiving a warning from reactjs telling me that I am calling setState on unmounted components.

I have figured out this is because changelisteners to which components are hooked are not being removed from the store on componentWillUnmount. I know it because when I print the list of listeners from Eventemitter I see the listener which was supposed to be destroyed still there, and the list grows larger as I mount/unmount the same component several times.

I paste code from my BaseStore:

import Constants from '../core/Constants';
import {EventEmitter} from 'events';

class BaseStore extends EventEmitter {
  // Allow Controller-View to register itself with store
  addChangeListener(callback) {
    this.on(Constants.CHANGE_EVENT, callback);
  }

  removeChangeListener(callback) {
    this.removeListener(Constants.CHANGE_EVENT, callback);
  }

  // triggers change listener above, firing controller-view callback
  emitChange() {
    this.emit(Constants.CHANGE_EVENT);
  }
}

export default BaseStore;

I paste the relevant code from a component experiencing this bug (it happens with all components, though):

@AuthenticatedComponent
class ProductsPage extends React.Component {
  static propTypes = {
    accessToken: PropTypes.string
  };

  constructor() {
    super();
    this._productBatch;
    this._productBatchesNum;
    this._activeProductBatch;
    this._productBlacklist;
    this._searchById;
    this._searchingById;
    this.state = this._getStateFromStore();
  }

  componentDidMount() {
    ProductsStore.addChangeListener(this._onChange.bind(this));
  }

  componentWillUnmount() {
    ProductsStore.removeChangeListener(this._onChange.bind(this));
  }

  _onChange() {
    this.setState(this._getStateFromStore());
  }
}

This is driving me pretty nuts at this point. Any ideas?

Thank you!

解决方案

Short version: expect(f.bind(this)).not.toBe(f.bind(this));

Longer explanation:

The cause of the issue is that EventEmitter.removeListener requires that you pass a function you have previously registered with EventEmitter.addListener. If you pass a reference to any other function, it is a silent no-op.

In your code, you are passing this._onChange.bind(this) to addListener. bind returns a new function that is bound to this. You are then discarding the reference to that bound function. Then you try to remove another new function created by a bind call, and it's a no op, since that was never added.

React.createClass auto-binds methods. In ES6, you need to manually bind in your constructor:

@AuthenticatedComponent
class ProductsPage extends React.Component {
  static propTypes = {
    accessToken: PropTypes.string
  };

  constructor() {
    super();
    this._productBatch;
    this._productBatchesNum;
    this._activeProductBatch;
    this._productBlacklist;
    this._searchById;
    this._searchingById;
    this.state = this._getStateFromStore();
    // Bind listeners (you can write an autoBind(this);
    this._onChange = this._onChange.bind(this);
  }

  componentDidMount() {
    // listener pre-bound into a fixed function reference. Add it
    ProductsStore.addChangeListener(this._onChange);
  }

  componentWillUnmount() {
    // Remove same function reference that was added
    ProductsStore.removeChangeListener(this._onChange);
  }

  _onChange() {
    this.setState(this._getStateFromStore());
  }

There are various ways of simplifying binding - you could use an ES7 @autobind method decorator (e.g. autobind-decorator on npm), or write an autoBind function that you call in the constructor with autoBind(this);.

In ES7, you will (hopefully) be able to use class properties for a more convenient syntax. You can enable this in Babel if you like as part of the stage-1 proposal http://babeljs.io/docs/plugins/transform-class-properties/ . Then, you just declare your event listener methods as class properties rather than methods:

_onChange = () => {
    this.setState(this._getStateFromStore());
}

Because the initializer for _onChange is invoked in the context of the constructor, the arrow function auto-binds this to the class instance so you can just pass this._onChange as an event handler without needing to manually bind it.

这篇关于商店的更改侦听器未在componentWillUnmount上移除?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆