このコードを見て「そりゃあそうだろ」と思う方は読む価値はございません。
var func = function(){};
console.log(func === func);//true
console.log(func.bind(this) === func.bind(this));//false
(とりあえずChromeのみで確認してます)
事の発端はReact+Flux+ES6でとあるアプリを開発中、下記のウォーニングに見舞われデバッグで手間取りました。
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 undefined component.
読んだ通りの内容で、マウントされていないComponentにsetState
を呼ぶと出るウォーニングですが、どこをどう見てもマウントしてるんですね。で、デバッガーやらconsole.log
なんかを駆使して、やっと気づいたのですが、UnmountしたComponentのイベントが削除されていなかったのが原因でした。
問題のあるComponentのイベント登録部分の実装です。
export default class BaseComponent extends React.Component
{
constructor(props) {
super(props);
this.store = this.initStore();
this.state = this.store.getInitialState();
}
initStore(){
throw 'You must implements initStore().';
}
onStoreChange(callback){
this.setState(this.store.getUpdatedState(), callback);
}
componentWillMount() {
this.store.addChangeListener(this.onStoreChange.bind(this));
}
componentWillUnmount() {
this.store.removeChangeListener(this.onStoreChange.bind(this));
}
}
this.store
の各メソッドは下記の通り(一部抜粋)。
export default class BaseStore extends Events.EventEmitter
{
//...
addChangeListener(callback) {
this.on('change', callback);
}
removeChangeListener(callback) {
this.removeListener('change', callback);
}
}
callback === callback
がfalse
なんで破棄されてなかったんですね。いろんな方法があると思いますが、とりあえず下記のようにして対応しました。
export default class BaseComponent extends React.Component
{
constructor(props) {
super(props);
this.store = this.initStore();
this.state = this.store.getInitialState();
this.storeChangeCallback = (callback) => {
this.setState(this.store.getUpdatedState(), callback);
};
}
initStore(){
throw 'You must implements initStore().';
}
componentWillMount() {
this.store.addChangeListener(this.storeChangeCallback);
}
componentWillUnmount() {
this.store.removeChangeListener(this.storeChangeCallback);
}
}
ちなみにes6の() => {}
内のthis
は外スコープのthis
になります(Babelを使用してます)。