【Vue.js】イベント発生時にajax通信で取得した値が表示されずに嵌った話


はじめに

脱jQueryを目指すという流れで、ボタンをクリックしてajax通信を行う処理をVue.jsとaxiosで実装していたのですが、「変数の中身は変更されているのに表示に反映されない、なんで💢」と嵌りました。無事解決しましたので、備忘録として書き残しておきます。。。
結論だけ言うと、「thenの後にはアロー関数を使おう」です。

また当方htmlやjavascriptの初心者なので、以下に記述しているサンプルコードにはひどい書き方のものも多いと思いますが、目をつぶってやってください。

追記:原因について

javascript初心者の嵌りどころである thisのスコープの違い が原因で Vueに値が渡っていなかった 可能性が高いようです。意外とシンプルな原因だけど、気付きづらかった…。

jQueryでの書き方

今までこういう処理は以下のような書き方で慣れていました。ajaxでGETし、responseのdataを表示に反映させるというもの。(本当はPOST処理を書くことが多いですが、簡略化のためGETにしています。)

index.html
<body>
<div id="app">
  <button id="display-btn">表示する</button>
  <div id="name-area"></div>
  <div id="age-area"></div>
</div>
<!-- my ajax script -->
<script type="text/javascript" src="myscript.js"></script>
</body>
myscript.js
$(function() {
  // ボタンが押されたらAPIを呼び出し、結果を表示する
  $('#display-btn').click(function() {
    $.ajax({
      url: '/get-json',
      type:"GET",
      dataType:"json",
      timespan:1000,
      success: function(data) {  // 通信成功
        console.log('Success!');
        const results = data.ResultSet;
        // 名前表示
        var name = document.createElement("p");
        var name_text = document.createTextNode("名前:" + results['name']);
        name.appendChild(name_text);
        document.getElementById("name-area").appendChild(name);
        // 年齢表示
        var age= document.createElement("p");
        var age_text = document.createTextNode("年齢:" + results['age']);
        age.appendChild(age_text);
        document.getElementById("age-area").appendChild(age);
      }
    })
    .fail(function(data){  // 通信失敗
      alert("ERROR!! occurred in Backend.");
    });
  });
});

返ってくるJSONデータは以下をイメージ。

{
    "name": "たなか",
    "age": 22
}

Vue.jsとaxiosで書いてみる

html側はこんな感じ。調べてみればすぐ分かる範囲ですし、問題ないかと。

index2.html
<body>
<div id="app">
  <button v-on:click="getJson">表示する</button>
  <div v-if="data">
    <p>名前:[[data.name]]</p>
    <p>年齢:[[data.age]]</p>
  </div>
</div>
<!-- my ajax script -->
<script type="text/javascript" src="myscript2.js"></script>
</body>

axiosのresponseを受け取ったときにfunctionを使うと嵌った

ボタンをクリックしたらGETして取得したデータを表示するスクリプトを書いていきます。
そのとき、私は以下のページなどを参考に書いていたら嵌りました。

結局は最適な記事が見つからず自己流で書いてたのが悪かったのですが…。
以下のように書いたら、コンソールからはきちんと変数が入っているのが確認できるのに、リアクティブになっていないのか知らんが画面表示が更新されないという現象になり、うまく行きませんでした。
コンソールでconsole.log(this.data)に対してObserverになっていないことも確認できたので、responseを代入する際にリアクティブではなくなっていることが原因なのかと思ったのですが、後からリアクティブにする方法を一生懸命探しても上手く行かずでした。

myscript2.js
var app = new Vue({
  el: "#app",
  data () {
    return {
      data: null
    }
  },
  methods: {
    getJson: function(){
      axios.get("/get-json")
      // thenで成功した場合の処理を書ける
      .then(function(responce){
        this.data = response.data;
        console.log(this.data);  // オブジェクトが代入されたか確認
      // catchでエラー時の挙動を定義する
      }).catch(function(error){
        console.log('ERROR!! occurred in Backend.');
      });
    }
  },
  delimiters: ['[[', ']]']
})

アロー関数を使わないとダメだった

はい、解決しました。上と比べてもらうと違いが分かるでしょうか。
.then(function(responce){と書くのが駄目だったらしく、.then(response => {というように、=>(アロー関数)を使用するように修正すると、画面表示が更新されるようになりました。

myscript2.js(修正版)
var app = new Vue({
  el: "#app",
  data () {
    return {
      data: null
    }
  },
  methods: {
    getJson: function(){
      axios.get("/get-json")
      // thenで成功した場合の処理を書ける
      .then(response => {   // !!ココが修正箇所!!
        this.data = response.data;
        console.log(this.data);  // オブジェクトがきちんと代入されたか確認
      // catchでエラー時の挙動を定義する
      }).catch(error => {   // !!ココが修正箇所!!
        console.log('ERROR!! occurred in Backend.');
      });
    }
  },
  delimiters: ['[[', ']]']
})

おわりに

後から調べると、methods内のaxiosでのajax通信ではアロー関数を使って書いていた記事が多かったわけですが。。。
なぜアロー関数じゃないとダメなのか、javascript初心者ですのでいまだによく分かっていません(よく調べきっていないというのもある)。知見者がいたら教えていただきたいです。 thisのスコープの違いが原因だろうと教えていただいたので、「はじめに」に追記しました。
ただこの罠に嵌っていたとき、値をその場で宣言して代入したりすると上手く表示されたりして「本当になんなんだ……!?」と怒りに震えたりしましたので、もし同じ罠に嵌った人の参考になれば幸いです。

また、以下のような記事も後から見つけました。フロントエンドもきちんと作るなら、このくらい構造的に書いた方がいいのかもしれないです。