商店的更改侦听器未在 componentWillUnmount 上删除?

IT技术 reactjs reactjs-flux flux
2021-04-24 05:04:33

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

我发现这是因为没有从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());
  }
}

这让我在这一点上非常疯狂。有任何想法吗?

谢谢!

4个回答

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

更长的解释:

问题的原因是EventEmitter.removeListener要求您传递先前注册的函数EventEmitter.addListener如果您传递对任何其他函数的引用,则它是一个静默的空操作。

在您的代码中,您将传递this._onChange.bind(this)给 addListener。bind返回一个绑定到 this函数。然后您将丢弃对该绑定函数的引用。然后您尝试删除由绑定调用创建的另一个函数,但它没有操作,因为它从未添加过。

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);.

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

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

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

所以我找到了解决方案,结果证明我只需要在将this._onChange.bind(this)内部属性作为参数传递给removechangelistenerand之前分配给它addchangelistener这是解决方案:

  componentDidMount() {
    this.changeListener = this._onChange.bind(this);
    ProductsStore.addChangeListener(this.changeListener);
    this._showProducts();
  }
  componentWillUnmount() {
    ProductsStore.removeChangeListener(this.changeListener);
  }

但是,我不知道为什么这可以解决问题。有任何想法吗?

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the exports component.

我在多个react组件中使用完全相同的实现。即这在几个 .jsx 组件中重复。

componentDidMount: function() {
    console.log('DidMount- Component 1');
   ViewStateStore.addChangeListener(this._onChange);
},

componentWillUnmount: function() {
    console.log('DidUnMount- Component 1');
   ViewStateStore.removeChangeListener(this._onChange);
},

_onChange:function()
{
    console.log('SetState- Component 1');
    this.setState(getStateFromStores());
},

可能的解决方案

目前,以下内容对我有用,但它有点脾气暴躁。将回调包装在函数/命名函数中。

ViewStateStore.addChangeListener(function (){this._onChange});

也可以尝试

ViewStateStore.addChangeListener(function named(){this._onChange});

理论

EventEmitter 出于某种原因对识别要删除的回调感到困惑。使用命名函数可能对此有所帮助。

尝试.bind(this)从您的addChangeListener和 中删除removeChangeListener当它们被调用时,它们已经绑定到您的组件。