商店的更改侦听器没有在 componentWillUnmount 上被删除?
Stores' change listeners not getting removed on componentWillUnmount?
我正在 reactjs-flux 上编写一个简单的应用程序,一切正常,除了我收到来自 reactjs 的警告,告诉我我正在对未安装的组件调用 setState。
我发现这是因为未从 componentWillUnmount
上的商店中删除挂钩组件的更改监听器。我知道它是因为当我从 Eventemitter
打印监听器列表时,我看到应该被销毁的监听器仍然存在,并且随着我多次 mount/unmount 相同的组件,列表变得更大。
我从我的 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());
}
}
这让我很抓狂。有什么想法吗?
谢谢!
尝试从 addChangeListener
和 removeChangeListener
中删除 .bind(this)
。它们在被调用时已经绑定到您的组件。
所以我找到了解决方案,事实证明我只需要将 this._onChange.bind(this)
分配给内部 属性,然后再将其作为参数传递给 removechangelistener
和 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());
},
可能的解决方案
目前正在为我锻炼以下,但有点脾气暴躁。将调用包装在 function/named-function.
中
ViewStateStore.addChangeListener(function (){this._onChange});
大家也可以试试
ViewStateStore.addChangeListener(function named(){this._onChange});
理论
EventEmitter 出于某种原因对识别要删除的回调感到困惑。使用命名函数可能会有所帮助。
我决定了
class Tooltip extends React.Component {
constructor (props) {
super(props);
this.state = {
handleOutsideClick: this.handleOutsideClick.bind(this)
};
}
componentDidMount () {
window.addEventListener('click', this.state.handleOutsideClick);
}
componentWillUnmount () {
window.removeEventListener('click', this.state.handleOutsideClick);
}
}
这是一个es6问题。 React.createClass 为其范围内定义的所有函数正确绑定 'this'。
对于es6,你必须自己做一些事情来绑定正确的'this'。但是,每次调用 bind(this) 都会创建一个新函数,并且将其 return 值传递给 removeChangeListener 将不匹配传递给由先前的 bind(this) 调用创建的 addChangeListener 的函数。
我在这里看到一个解决方案,其中为每个函数调用一次 bind(this) 并保存 return 值并在以后重新使用。那会很好的。一个更流行、更简洁的解决方案是使用 es6 的箭头函数。
componentDidMount() {
ProductsStore.addChangeListener(() => { this._onChange() });
}
componentWillUnmount() {
ProductsStore.removeChangeListener(() => { this._onChange());
}
箭头函数捕获封闭上下文的 'this',而无需每次都创建新函数。它是为这样的东西设计的。
简短版本:expect(f.bind(this)).not.toBe(f.bind(this));
更长的解释:
问题的原因是 EventEmitter.removeListener
要求您传递一个您之前在 EventEmitter.addListener
注册的函数。如果您传递对任何其他函数的引用,则它是一个静默的空操作。
在您的代码中,您将 this._onChange.bind(this)
传递给 addListener。 bind
returns 绑定到此的 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 函数 autoBind(this);
.
在 ES7 中,您将(希望)能够使用 class 属性以获得更方便的语法。如果你愿意,你可以在 Babel 中启用它作为 stage-1 提案 http://babeljs.io/docs/plugins/transform-class-properties/ 的一部分。然后,您只需将事件侦听器方法声明为 class 属性而不是方法:
_onChange = () => {
this.setState(this._getStateFromStore());
}
因为 _onChange 的初始值设定项是在构造函数的上下文中调用的,箭头函数会自动将 this
绑定到 class 实例,因此您只需将 this._onChange
作为无需手动绑定的事件处理程序。
由于您已经知道解决方案 ,我将尝试解释发生了什么。
根据 ES5 标准,我们曾经编写以下代码来添加和删除监听器。
componentWillMount: function() {
BaseStore.addChangeListener("ON_API_SUCCESS", this._updateStore);
},
componentWillUnmount: function() {
BaseStore.removeChangeListener("ON_API_SUCCESS", this._updateStore);
}
在上面的代码中,回调函数(即:this._updateStore)的内存引用是相同的。因此,removeChangeListener 将查找引用并将其删除。
因为 ES6 标准默认缺少自动绑定 this
,您必须将 this
显式绑定到该函数。
Note: Bind method returns new reference for the callback.
有关绑定
的更多信息,请参阅 here
这就是问题所在。当我们执行 this._updateStore.bind(this)
时,绑定方法 returns 该函数的新引用。因此,您作为参数发送给 addChangeListener 的引用与 removeChangeListener 方法中的引用不同。
this._updateStore.bind(this) != this._updateStore.bind(this)
解法:
有两种方法可以解决这个问题。
1. 将事件处理程序(ie: this._updateStore)
作为成员变量存储在构造函数中。 (您的解决方案)
2. 在商店中创建一个自定义的 changeListener 函数,它将为您绑定 this
。 (来源:here)
解法一解释:
constructor (props) {
super(props);
/* Here we are binding "this" to _updateStore and storing
that inside _updateStoreHandler member */
this._updateStoreHandler = this._updateStore.bind(this);
/* Now we gonna user _updateStoreHandler's reference for
adding and removing change listener */
this.state = {
data: []
};
}
componentWillMount () {
/* Here we are using member "_updateStoreHandler" to add listener */
BaseStore.addChangeListener("ON_STORE_UPDATE", this._updateStoreHandler);
}
componentWillUnmount () {
/* Here we are using member "_updateStoreHandler" to remove listener */
BaseStore.removeChangeListener("ON_STORE_UPDATE", this._updateStoreHandler);
}
在上面的代码中,我们将 this
绑定到 _updateStore 函数并将其分配给构造函数内部的成员。稍后我们将使用该成员来添加和删除更改侦听器。
方案二解释:
在这个方法中,我们修改了 BaseStore 的功能。想法是修改 BaseStore 中的 addChangeListener 函数以接收第二个参数 this
并且在该函数中我们将 this
绑定到回调并存储该引用,以便在删除更改侦听器时我们可以使用该引用删除。
找到完整的代码要点
我正在 reactjs-flux 上编写一个简单的应用程序,一切正常,除了我收到来自 reactjs 的警告,告诉我我正在对未安装的组件调用 setState。
我发现这是因为未从 componentWillUnmount
上的商店中删除挂钩组件的更改监听器。我知道它是因为当我从 Eventemitter
打印监听器列表时,我看到应该被销毁的监听器仍然存在,并且随着我多次 mount/unmount 相同的组件,列表变得更大。
我从我的 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());
}
}
这让我很抓狂。有什么想法吗?
谢谢!
尝试从 addChangeListener
和 removeChangeListener
中删除 .bind(this)
。它们在被调用时已经绑定到您的组件。
所以我找到了解决方案,事实证明我只需要将 this._onChange.bind(this)
分配给内部 属性,然后再将其作为参数传递给 removechangelistener
和 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());
},
可能的解决方案
目前正在为我锻炼以下,但有点脾气暴躁。将调用包装在 function/named-function.
中ViewStateStore.addChangeListener(function (){this._onChange});
大家也可以试试
ViewStateStore.addChangeListener(function named(){this._onChange});
理论
EventEmitter 出于某种原因对识别要删除的回调感到困惑。使用命名函数可能会有所帮助。
我决定了
class Tooltip extends React.Component {
constructor (props) {
super(props);
this.state = {
handleOutsideClick: this.handleOutsideClick.bind(this)
};
}
componentDidMount () {
window.addEventListener('click', this.state.handleOutsideClick);
}
componentWillUnmount () {
window.removeEventListener('click', this.state.handleOutsideClick);
}
}
这是一个es6问题。 React.createClass 为其范围内定义的所有函数正确绑定 'this'。
对于es6,你必须自己做一些事情来绑定正确的'this'。但是,每次调用 bind(this) 都会创建一个新函数,并且将其 return 值传递给 removeChangeListener 将不匹配传递给由先前的 bind(this) 调用创建的 addChangeListener 的函数。
我在这里看到一个解决方案,其中为每个函数调用一次 bind(this) 并保存 return 值并在以后重新使用。那会很好的。一个更流行、更简洁的解决方案是使用 es6 的箭头函数。
componentDidMount() {
ProductsStore.addChangeListener(() => { this._onChange() });
}
componentWillUnmount() {
ProductsStore.removeChangeListener(() => { this._onChange());
}
箭头函数捕获封闭上下文的 'this',而无需每次都创建新函数。它是为这样的东西设计的。
简短版本:expect(f.bind(this)).not.toBe(f.bind(this));
更长的解释:
问题的原因是 EventEmitter.removeListener
要求您传递一个您之前在 EventEmitter.addListener
注册的函数。如果您传递对任何其他函数的引用,则它是一个静默的空操作。
在您的代码中,您将 this._onChange.bind(this)
传递给 addListener。 bind
returns 绑定到此的 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 函数 autoBind(this);
.
在 ES7 中,您将(希望)能够使用 class 属性以获得更方便的语法。如果你愿意,你可以在 Babel 中启用它作为 stage-1 提案 http://babeljs.io/docs/plugins/transform-class-properties/ 的一部分。然后,您只需将事件侦听器方法声明为 class 属性而不是方法:
_onChange = () => {
this.setState(this._getStateFromStore());
}
因为 _onChange 的初始值设定项是在构造函数的上下文中调用的,箭头函数会自动将 this
绑定到 class 实例,因此您只需将 this._onChange
作为无需手动绑定的事件处理程序。
由于您已经知道解决方案
根据 ES5 标准,我们曾经编写以下代码来添加和删除监听器。
componentWillMount: function() {
BaseStore.addChangeListener("ON_API_SUCCESS", this._updateStore);
},
componentWillUnmount: function() {
BaseStore.removeChangeListener("ON_API_SUCCESS", this._updateStore);
}
在上面的代码中,回调函数(即:this._updateStore)的内存引用是相同的。因此,removeChangeListener 将查找引用并将其删除。
因为 ES6 标准默认缺少自动绑定 this
,您必须将 this
显式绑定到该函数。
Note: Bind method returns new reference for the callback.
有关绑定
这就是问题所在。当我们执行 this._updateStore.bind(this)
时,绑定方法 returns 该函数的新引用。因此,您作为参数发送给 addChangeListener 的引用与 removeChangeListener 方法中的引用不同。
this._updateStore.bind(this) != this._updateStore.bind(this)
解法:
有两种方法可以解决这个问题。
1. 将事件处理程序(ie: this._updateStore)
作为成员变量存储在构造函数中。 (您的解决方案)
2. 在商店中创建一个自定义的 changeListener 函数,它将为您绑定 this
。 (来源:here)
解法一解释:
constructor (props) {
super(props);
/* Here we are binding "this" to _updateStore and storing
that inside _updateStoreHandler member */
this._updateStoreHandler = this._updateStore.bind(this);
/* Now we gonna user _updateStoreHandler's reference for
adding and removing change listener */
this.state = {
data: []
};
}
componentWillMount () {
/* Here we are using member "_updateStoreHandler" to add listener */
BaseStore.addChangeListener("ON_STORE_UPDATE", this._updateStoreHandler);
}
componentWillUnmount () {
/* Here we are using member "_updateStoreHandler" to remove listener */
BaseStore.removeChangeListener("ON_STORE_UPDATE", this._updateStoreHandler);
}
在上面的代码中,我们将 this
绑定到 _updateStore 函数并将其分配给构造函数内部的成员。稍后我们将使用该成员来添加和删除更改侦听器。
方案二解释:
在这个方法中,我们修改了 BaseStore 的功能。想法是修改 BaseStore 中的 addChangeListener 函数以接收第二个参数 this
并且在该函数中我们将 this
绑定到回调并存储该引用,以便在删除更改侦听器时我们可以使用该引用删除。