0
0

More than 1 year has passed since last update.

typescriptでEventListenerにクラス内メソッドを使用する際のthisの取り扱い

Last updated at Posted at 2023-05-13

結論

最終的にtypescriptは関係がない。
Function.bind()で参照したいオブジェクトをバインドさせるとスマート。

背景

クラスメソッドでEventTargetに追加したListenerを削除する必要ができたため、変更したが、期待した挙動をしなかった。

元の状態

class Temp{
    private state: boolean
    constructor(){
        this.state = true;
    }

    public start(): void{
        ...
        document.addEventListener('keydown', (e)=>{
            ...
            switch(e.code){
                case...:
                    this.move();
                    break;
            }
        });
    };
    ...
}

Listenerを削除するため、無名関数を関数へ変更

EventTargetに追加されるEventListenerは関数の参照が渡されるため、addEventListener内で無名関数を定義すると参照が保持されない。
そのため、EventTargetに追加された無名関数は後々削除することができない。
Listenerを削除する必要がある場合は、 基本的に、addEventListener内で無名関数を記述しないほうが良さそう。

    public start(): void{
        ...
        document.addEventListener('keydown', this.moveCtrl);
    }

    public moveCtrl(e: KeyBoardEvent): void{
            ...
            switch(e.code){
                case...:
                    this.move();
                    break;
            }
    };

thisの参照問題

ここでthisはどこを参照してくるか?

    // 実行時
    this.moveCtrl => Temp.moveCtrl
    this.state    => document.state() // undefined
    this.move     => document.move() // undefined

    // 理想
    this.moveCtrl => Temp.moveCtrl
    this.state    => Temp.state()
    this.move     => Temp.move()

moveCtrl()内のthisの参照をTempにさせる

thisをTempにさせるには以下の2つ

  1. リスナーにオブジェクトとして渡す。
  2. 渡す関数にTempをバインドする。

1. リスナーにオブジェクトとして渡す。

リスナーにオブジェクトを渡した場合、あらゆるイベントを捕捉する handleEvent()を指定することで実現できる。
ただし、イベントタイプで実行するメソッドを分けたい場合、handleEvent()内で条件分岐させる必要がありそう。

    public start(): void{
        ...
        document.addEventListener('keydown', this);
    }

    // public moveCtrl(): void{
    public handleEvent(): void{
            ...
            if(this.state){
                switch(e.code){
                    case...:
                        this.move();
                        break;
                }
            }else{...}
    };

2. 渡す関数にTempをバインドする。

Function.prototype.bind()で関数内のthisの参照を指定することができる。

    constructor(){
        this.moveCtrl = this.moveCtrl.bind(this)
    }

    public start(): void{
        ...
        document.addEventListener('keydown', this.moveCtrl);
    }

    public moveCtrl(e: KeyBoardEvent): void{
        ...
        switch(e.code){
            case...:
                this.move();
                break;
        }
    };

番外

初めにいろいろこねくり回した結果。

type HandleEvent = {
    handleEvent: Function,
    manager: Temp,
}

class Temp{
    private state: boolean
    private handleEvent: HandleEvent | EventListenerOrEventListenerObject;

    constructor(){
        this.state = true;
        this.handleEvent = {
            handleEvent: this.moveCtrl,
            manager: this,
        }
    }

    public start(): void{
        document.addEventListener("keydown", (this.handleEvent as EventListenerOrEventListenerObject));
    }

    public moveCtrl(e: KeyBoardEvent): void{
        ...
        switch(e.code){
            case...:
                (this as any as HandleEvent).manager.move();
                break;
        }
    };
}

参考文献

https://developer.mozilla.org/ja/docs/Web/API/EventTarget/addEventListener
https://note.com/yamanoborer/n/n2e4cc40328b7

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0