ReactのEventEmitterで登録したリスナが削除できない問題の解決法
どうも、くずきです。
EventEmitter
で登録したリスナが削除できない問題について解決した方法をメモしときたいと思います。
構成
- EventEmitter
- ErrorStore(
EventEmitter
を継承したクラスをStore
として利用) - サーバーサイドから取得したエラーを登録したリスナに通知
- ErrorStore(
- Component
- App.js
- エラー時の関数を
EventEmitter(Store)
に登録する
EventEmitter
本記事では書いてないが、Action
でサーバー側に通信した後エラーだった場合に呼ばれるStore
。
this.on
で通知するリスナを登録している。
class ErrorStore extends EventEmitter { handleActions(action) { if(action.type === "ERROR"){ // エラー通知 if(action.res.hasOwnProperty("statusCode")) { switch (action.res.statusCode) { case 500: break; case 400: this.emit(EVENT.ERROR.VALIDATION, action.res.body.errors); break; case 401: this.emit(EVENT.ERROR.AUTHENTICATION, action.res); break; default: break; } } } } addValidListener(listener) { this.on(EVENT.ERROR.VALIDATION, listener); } removeValidListener(listener) { this.removeListener(EVENT.ERROR.VALIDATION, listener); } addAuthListener(listener) { this.on(EVENT.ERROR.AUTHENTICATION, listener); } removeAuthListener(listener) { this.removeListener(EVENT.ERROR.AUTHENTICATION, listener); } } const errorStore = new ErrorStore(); dispatcher.register(errorStore.handleActions.bind(errorStore)); export default errorStore;
初期のComponent
一番初期(問題があるとき)につくったコンポーネント。
handleError
関数をErrorStore
のaddValidListener
でリスナに登録している。
class App extends Component { constructor(props) { super(); this.state = { errors: null }; } componentWillMount() { errorStore.addValidListener(this.handleError.bind(this)); } componentWillUnmount() { errorStore.removeValidListener(this.handleError.bind(this)); } handleError(_errors) { // エラーの内容を解析する関数を呼ぶ let errors = Validation.getValidationErrors(_errors); this.setState({ errors: errors }); } render() { if (this.state.errors) { .... } } }
当初はcomponentWillUnmount
で既に登録したリスナ(this.handleError.bind(this)
)を消すはずが
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.
というエラーを吐き、できなかった。
EventEmitter
のコードを追い、removeListenerで消す条件を確認したところ、関数同士を比較してるようだ。
実際にthis.handleError.bind(this) === this.handleError.bind(this)
を行なったところfalse
になり、bind
した関数同士はイコールにならないみたい。
bind関数の仕様をみると、確かに関数を新たに作成してるっぽいのでこれはイコールにはならない。。。
改善版Component
ここまで分かればもう簡単で、コンストラクタでbind
した関数を保持し、それを利用すれば良い。
class App extends Component { constructor(props) { super(); this.state = { errors: null }; this.handleError = this.handleError.bind(this); } componentWillMount() { errorStore.addValidListener(this.handleError); } componentWillUnmount() { errorStore.removeValidListener(this.handleError); } handleError(_errors) { // エラーの内容を解析する関数を呼ぶ let errors = Validation.getValidationErrors(_errors); this.setState({ errors: errors }); } render() { if (this.state.errors) { .... } } }
これでエラーも消えてめでたしめでたし。
ただjavascript
はこういったエラーを気にしないと気付かずバグをうんだりするので、もっと理解しないとなと思います。