LWCのコールバック関数の指定方法でハマった話

## 目次
- 概要
- 何にハマったのか？
- 原因
- 解決方法
- 結論
## 概要
- LWCでプラットフォームイベントを受信して、あれこれしたくなったため、[リファレンス](https://developer.salesforce.com/docs/component-library/bundle/lightning-emp-api/documentation)を眺めながら実装した。リファレンスに記載されたサンプルコードを入力したのにうまくいかなかった。その後、色々試して解決できたので本記事で記録を残しておこうと思った。

## 何にハマったのか？
### 結論
<strong>subscribe関数への引数で渡すコールバック関数内で、グローバル変数を参照しようとした際に`undefined`にってしまう、、、</strong>

経緯踏まえて説明します
- サンプルコードの記載がある[リファレンス](https://developer.salesforce.com/docs/component-library/bundle/lightning-emp-api/documentation)
- 上記リファレンスから抜粋したコードに独自にコメントを追加(コメント以外はリファレンスのままです)
```javascript
    // プラットフォームイベント登録メソッド
    handleSubscribe() {
        // プラットフォームイベント受信時にコールされるCallback関数
        const messageCallback = function (response) {
            console.log('New message received: ', JSON.stringify(response));
            // Response contains the payload of the new message received
        };

        // プラットフォームイベントのサブスク登録メソッド
        subscribe(this.channelName, -1, messageCallback).then((response) => {
            // Response contains the subscription information on subscribe call
            console.log(
                'Subscription request sent to: ',
                JSON.stringify(response.channel)
            );
            this.subscription = response;
            this.toggleSubscribeButton(true);
        });
    }
```
- 上記のコードって、一見して何も問題なさそうに見えて、結構簡単に動かせるんだなぁと思い、コードを貼り付けてデプロイしました
- 実際にプラットフォームイベントを受信することができ、コールバック関数がコールされることを確認しました
- よしよし、では実際のロジックを書いていこうと思いました
- その中でコールバック関数内でグローバル変数で定義している値（this.xxxxで参照する値）を参照しようとした時、なぜかグローバル変数の値が`undefined`になっていました


## 原因
<strong>strictModeでコールバック関数に、アロー関数以外を渡す場合（functionで定義した関数）は`this`の参照先が`undefined` になってしまうため</strong>
```diff_javascript
    // プラットフォームイベント登録メソッド
    handleSubscribe() {
        // プラットフォームイベント受信時にコールされるCallback関数
        const messageCallback = function (response) {
            console.log('New message received: ', JSON.stringify(response));
            // Response contains the payload of the new message received
+           console.log(this.xxxx) // <=参照できない。undefinedになる。他では参照できるのに
        };

        // プラットフォームイベントのサブスク登録メソッド
        subscribe(this.channelName, -1, messageCallback).then((response) => {
            // Response contains the subscription information on subscribe call
            console.log(
                'Subscription request sent to: ',
                JSON.stringify(response.channel)
            );
            this.subscription = response;
            this.toggleSubscribeButton(true);
        });
    }
```
- 関連リファレンス
  - [thisに関するMDNのリファレンス](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/this#The_bind_method)
  - [LWCにおけるStrict modeについて説明しているリファレンス](https://developer.salesforce.com/docs/platform/lwc/guide/security-locker-strict-mode.html)

情報を整理していきます。
- jsにはstrict modeというモードがある（[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Strict_mode)）
- 通常strict modeを有効化する場合、　下記のように定義する（上記MDNから抜粋）
    ```javascript
    // スクリプト全体の厳格モード構文
    "use strict";
    const v = "こんにちは！厳格モードのスクリプト！";
    ```
- strict modeが有効なときは、関数がstrict モードで実行コンテキストに入るときに this 値を明示的に設定しないと、 undefined になる（つまりbindする必要がある。ただしアロー関数は、ソースコード上のコンテキストの this の値が設定される）
- LWCってどうなっているんだろうって調べたら、LWCは暗黙的（use strictの宣言なし）にstrict modeが有効になっているとリファレンスにちゃんとかいてあった。
## 解決方法
- 上記事実をもとにサンプルコードを見ると、thisの参照先がundefinedになることが理解できた
- どうすればよかったかコードを記載します
  - 方法1（thisをbindする）
    ```diff_javascript
        // プラットフォームイベント登録メソッド
        handleSubscribe() {
            // プラットフォームイベント受信時にコールされるCallback関数
            const messageCallback = function (response) {
                console.log('New message received: ', JSON.stringify(response));
                // Response contains the payload of the new message received
    +            console.log(this.xxxx) // <=参照できる
            };
    
            // プラットフォームイベントのサブスク登録メソッド
    +        subscribe(this.channelName, -1, messageCallback.bind(this)).then((response) => {
                // Response contains the subscription information on subscribe call
                console.log(
                    'Subscription request sent to: ',
                    JSON.stringify(response.channel)
                );
                this.subscription = response;
                this.toggleSubscribeButton(true);
            });
        }
    ```

      - 方法2（コールバック関数をアロー関数で定義する）
    ```diff_javascript
        // プラットフォームイベント登録メソッド
        handleSubscribe() {
            // プラットフォームイベント受信時にコールされるCallback関数
    +          const messageCallback = (response) => {
                console.log('New message received: ', JSON.stringify(response));
                // Response contains the payload of the new message received
    +            console.log(this.xxxx) // <=参照できる
            };
    
            // プラットフォームイベントのサブスク登録メソッド
            subscribe(this.channelName, -1, messageCallback).then((response) => {
                // Response contains the subscription information on subscribe call
                console.log(
                    'Subscription request sent to: ',
                    JSON.stringify(response.channel)
                );
                this.subscription = response;
                this.toggleSubscribeButton(true);
            });
        }
    ```

## 結論
- リファレンスはしっかり読みましょう
- 1個のリファレンスだけを信用するのではなく、関連するリファレンスも読み、照らし合わせて情報を整理しましょう
- 機械は嘘つかない


色々学べました。この記事が誰かのためになれば幸いです。