Vue.js初心者向け：Vue.jsとaxiosでJsonを取得してコンポーネントに反映するメモ

# はじめに

Vue と Ajax を組み合わせて非同期で Json を取得するサンプルは意外と見付かりません。今回の実装もものすごく初歩的な方法ですが、自分的には丸1日悩んだ結果なので今後のためにメモを残しておきます。

むしろエレガントな方法を教えてください。

# やりたいこと

- Vue インスタンス作成前ではなく、作成後にコンポーネント側で Json を取得したい。
- データは専用のストアを用意して複数のコンポーネントで使い回したい。
- しかし Vuex は制約がキツいので使いたくない。
- Json を取得したらコンポーネントに反映したい。
- コンポーネントは単一ファイルコンポーネント（.vue）を使いたい。

# jQuery ではなく axios を使う

axios とは Vue のドキュメントで推奨されている Ajax通信ライブラリです。jQuery を使ってもいいのですが、せっかくなので試してみました。

axiosについては下記のページが大変詳しいです。
[≫axiosを乗りこなす機能についての知見集](http://qiita.com/inuscript/items/ccb56b6fc05aa7821c42)

まずは npm をインストールします。

```bash:
$ npm install --save-dev axios
```

# ストア専用 Vue インスタンスを作る

大元の Vue インスタンスでデータを管理する方法ではコンポーネントが増えるとデータ受け渡し地獄があります。規模が大きくなるなら [Vuex](https://vuex.vuejs.org/ja/intro.html) がおすすめのようですが、制約が厳しく、小さいアプリを作るには向いていません。

かといって Vue のドキュメントにある[シンプルな Object のストア](https://jp.vuejs.org/v2/guide/state-management.html#シンプルな状態管理をゼロから作る)ではイベントハンドリングがちょっと面倒になります（たぶん）。

この記事では Vue インスタンスを使い、イベントハンドリングの管理も含めた専用ストアを作ります。

```js:store.js
import Vue from 'vue';
// Ajax通信ライブラリ
import axios from 'axios';

// Json取得のベースURL
const URL_BASE = '/api/search/';

// Vue.js のインスタンス
module.exports = new Vue({
  data: {
    // Jsonデータ格納用
    search_list: []
  },
  methods: {
    // Ajax通信でJsonを取得し、特定のプロパティに格納する
    // 取得したら GET_AJAX_COMPLETE で通知する
    get_ajax(url, name) {
      return axios.get(URL_BASE + url)
      .then((res) => {
        Vue.set(this, name, res.data);
        this.$emit('GET_AJAX_COMPLETE');
      });
    },
    // プロパティ名を指定してデータを取得
    get_data(name) {
      return this.$data[name];
    }
  }
});
```

## 解説

```js:store.js
// Vue.js のインスタンス
module.exports = new Vue({
  data: {
    // Jsonデータ格納用
    search_list: []
  },
```

普通に使うなら `el` パラメータで HTML上の id を指定しますが、データ管理専用なので使いません。

Jsonデータ格納用のプロパティはあらかじめ用意しておきます。本当は動的に追加させたかったのですが、上手くいきませんでした（後述）。

```js:store.js
    // Ajax通信でJsonを取得し、特定のプロパティに格納する
    // 取得したら GET_AJAX_COMPLETE で通知する
    get_ajax(url, name) {
      return axios.get(URL_BASE + url)
      .then((res) => {
        Vue.set(this, name, res.data);
        this.$emit('GET_AJAX_COMPLETE');
      });
    },
```

axios は Promise オブジェクトを返すので `.done()`、`.catch()`、`.then()` などで結果を受け取ります。

引数 `res` の中にステータス、ステータステキスト、データが格納されています。`res.status`、`res.statusText`、`res.data` で取得します。

取得したデータは `Vue.set()` で登録します。`this.$data[name] = res.data;` と書かないのはデータが配列やオブジェクトを含んでいるため、**そのまま代入するとリアクティブにならない**からです（データ更新しても画面に反映されない）。

データの登録が完了したら `this.$emit()` でイベント通知します。`$.emit()`、`$.on()` は jQuery の `$.trigger()`、`$.on()` と考えれば理解が早いと思います（よくわかってません）。

```js:store.js
    // プロパティ名を指定してデータを取得
    get_data(name) {
      return this.$data[name];
    }
```

Vue インスタンスの `$data` を直接使わせるのではなく、必ずメソッド経由にします。将来的にはプロパティ名に応じて加工したデータを返すなどが出来ますね。

```
追記：2017.11.3
up9cloud さんがコメントで await / async 版を投稿してくださいました。
$on()、$emit()を使うよりシンプルになります。
```


# メインのコンポーネントから Json 取得を呼び出す

```html:app.vue
<template>
  <main id="app">
    <ul>
      <list-item v-for="item in search_list"></list-item>
    </ul>
  </main>
</template>

<script>
import Vue from 'vue';
// データ管理ストア
import store from './store';
// 子コンポーネント
import ListItem from './list-item.vue';

export default {
  data() {
    return {
      // Jsonのデータを格納
      search_list: []
    };
  },
  components: {
    // 子コンポーネント登録
    'list-item': ListItem
  },
  created() {
    // Json取得
    store.get_ajax('', 'search_list');
    // Json取得後に呼び出される
    store.$on('GET_AJAX_COMPLETE', () => {
      this.search_list = store.get_data('search_list');
    });
  }
};
</script>
```

## 解説

```js:app.vue
// データ管理ストア
import store from './store';
```

先ほど作ったデータ管理スクリプトを呼び出しています。今後増える各コンポーネントでこのように呼び出せばデータ共通化が楽になります。

```js:app.vue
  data() {
    return {
      // Jsonのデータを格納
      search_list: []
    };
  },
```

データの受け入れ先を用意しておきます。

```js:app.vue
  created() {
```

コンポーネントの初期化が完了したら Json を取得するようにします。`created` の実行タイミングについては[公式ドキュメントのライフサイクル](https://jp.vuejs.org/v2/guide/instance.html#%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB%E3%83%80%E3%82%A4%E3%82%A2%E3%82%B0%E3%83%A9%E3%83%A0)が参考になります。

```js:app.vue
    // Json取得
    store.get_ajax('', 'search_list');
```

Json を取得して `search_list` というプロパティに格納します。URL の部分が空なのは `store.js` でベースURLを指定してあるためです。

```js:app.vue
    // Json取得後に呼び出される
    store.$on('GET_AJAX_COMPLETE', () => {
      this.search_list = store.get_data('search_list');
    });
```

axios による Json 取得が完了すると `GET_AJAX_COMPLETE` のイベントが発行されます。`$on('GET_AJAX_COMPLETE', function(){})` によって受け取り、データに格納します。

# ハマりポイント

## ①コンポーネントの data プロパティについて勘違いしていた

公式ドキュメントに[「data は関数でなければならない」](https://jp.vuejs.org/v2/guide/components.html#data-は関数でなければならない)と書かれていたので**「コンポーネントってデータ格納できないの？」**と思っていました。

しかし下記のスクリプトからわかるように、`data` に格納はできるようです。

```js:app.vue
      this.search_list = store.get_data('search_list');
```

## ②コンポーネントの props プロパティにデータを直接入れると警告が出る

上記①が判明する前は `props` にデータを格納するのかと考えていました。

```js:app.vue
export default {
  props: {
    search_list: Array
  },
  components: {
    // 子コンポーネント登録
    'list-item': ListItem
  },
  〜略〜
    // Json取得後に呼び出される
    store.$on('GET_AJAX_COMPLETE', () => {
      // props のプロパティにデータを格納
      this.search_list = store.get_data('search_list');
    });
```

しかし本来 `props` は親から受け取ったデータを格納する場所なので下記のような警告が出ました。

```
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "search_list"
```

## ③Vue.set() を使わないと反映されない

当初のストアは Vue インスタンスではなくただのオブジェクトでした。

```js:main.js
new Vue({
  el: '#app',
  data: store.data // 親 Vue インスタンスのデータとして登録
  〜略〜
})
```

```js:store.js
module.exports = {
  data: [],
  get_ajax(url, name) {
    // 〜 中略：axios によるデータ取得 〜
    // ストアデータは更新されるが、Vueには反映されない
    this.data = jsondata;
  }
}
```

データを格納するのに Vue のメソッドを使わず直接代入したら、ストアデータは更新されているのに、画面に反映されていませんでした。

これは、`Vue.set(Vueインスタンス, プロパティ名, データ)` を使うことで解消されました。

## ④データ格納先プロパティはあらかじめ用意する必要がある

あとから自由にデータが追加できるように、`data` プロパティに中身は記載していませんでした。

```js:store.js
// Vue.js のインスタンス
module.exports = new Vue({
  data: {},
```

しかしこの状態で `Vue.set()` を使っても下記のようなエラーが出るだけです。

```
[Vue warn]: Avoid adding reactive properties to a Vue instance or its root $data at runtime - declare it upfront in the data option.
```

これはあらかじめ登録先を用意することで解消されました。無計画はいけないってことですねｗ

```js:store.js
// Vue.js のインスタンス
module.exports = new Vue({
  data: {
    // Jsonデータ格納用
    search_list: []
  },
```

# おわりに

Vue.js は便利ですが、一歩先に進むとまだまだわからないことだらけです。日本語の情報もまだまだ少ないです。皆さんの知見が共有されていくことを願います。