SIerだけど技術やりたいブログ

5年目のSIerのブログです

Vuejsでdataプロパティに値を動的に追加する方法(といいつつJSの基本を理解してなかったという話)

Vuejsのバージョン

この記事はVuejs v2.3.3 で動作確認をしました。

  ...
  "dependencies": {
    "vue": "^2.3.3",
    "vue-router": "^2.6.0"
  },
  ...

Vuejsのdataプロパティ

Vuejsはdataオブジェクトのプロパティをリアクティブな値として扱う。つまり、dataオブジェクトのプロパティの値が書き換わったタイミングで、HTMLも書き換わる。

例を挙げると

<template>
  <div>
    <h1>{{ msg }}</h1>
    <button type="button" @click="change">change</button>
  </div>
</template>

<script>
export default {
  name: 'sample',
  data () {
    return {
      msg: 'hello world'
    }
  },
  methods: {
    change: function () {
      this.msg = 'good bye'
    }
  }
}
</script>

<style scoped>
</style>

この単一コンポーネントのファイルをブラウザで表示すると、以下のように {{ msg }} 部分がdataオブジェクトのmsgプロパティの値である「hello world」に置き換えられたものが表示される。
f:id:kimulla:20170724215420p:plain

ボタンをクリックすると、メッセージが「good bye」に変わる。
f:id:kimulla:20170724215507p:plain

これは、

  1. HTMLのボタンをクリックするとclickイベントが発生し
  2. @click(イベントハンドラ的なもの)で指定したchangeメソッドが呼ばれ
  3. Vueインスタンスのdataオブジェクトのmsgプロパティが書き換わり
  4. dataオブジェクトのプロパティの変更をVueが検知して
  5. VuejsがHTMLに値を反映するため


このプロパティの変更の検知は、JSの機能的な制限(Object.observe)によってプロパティ自体の追加には対応していない。

つまり以下のような処理では、動的に追加したプロパティは無視されてしまう。

<template>
  <div>
    <ul>
      <li v-for="(value,key) in favorites">
        {{value}} likes {{key}}
      </li>
    </ul>
    <button type="button" @click="add">add charie</button>
  </div>
</template>

<script>
export default {
  name: 'sample',
  data () {
    return {
      favorites: {
        'alice': 'apple',
        'bob': 'banana'
      }
    }
  },
  methods: {
    add: function () {
      // 動的にプロパティとして値を追加する
      this.favorites.charie = 'cherry'
    }
  }
}
</script>

<style scoped>
</style>

実際にブラウザでボタンをクリックし、動的にdataオブジェクトにプロパティを追加してみる。
f:id:kimulla:20170724220021p:plain

Chrome Developer Toolで確認すると、favoritesに値が追加されているにもかかわらず、その値が画面に反映されていない。
f:id:kimulla:20170724220508p:plain


で、これをどうするかというのも公式に書いてある。
https://jp.vuejs.org/v2/guide/reactivity.html#変更検出の注意事項

抜粋すると

Vue はすでに作成されたインスタンスに対して動的に新しいルートレベルのリアクティブなプロパティを追加することはできません。しかしながら Vue.set(object, key, value) メソッドを使うことで、ネストしたオブジェクトにリアクティブなプロパティを追加することができます:
グローバル Vue.set の単なるエイリアスとなっている vm.$set インスタンスメソッドを使用することもできます:
Vue.set(vm.someObject, 'b', 2)


ここからが自分にとっては本題で、これを単一コンポーネントでどう使えばいいの?と悩んでしまった。

リファレンスみたいにVueコンポーネントなんてimportしてないし、vmインスタンスなんて変数に格納してないし、どうすればいいの?みたいな


でひとまずメソッド内ではthisがVueを指す(dataオブジェクトへの値の変更もthis.msgとしているように)とリファレンスに載っていたので、とりあえずchangeメソッド内でthisをChrome Developer Toolsで表示してみた。

すると

f:id:kimulla:20170724223116p:plain

$setプロパティがない・・・?



いちおう this.$set も試すと・・・

f:id:kimulla:20170724220443p:plain

$setプロパティがある・・・?ある!



JSでは、prototypeで定義されたメソッドはそのオブジェクトのプロパティとしては見えないらしい。
vividcode.hatenablog.com


コンソールで試してみると、確かにその通り。
f:id:kimulla:20170724220750p:plain

代わりに for..in.. を使えば全プロパティ表示される。なるほど。
f:id:kimulla:20170724220801p:plain


Vueでも確かめてみると確かに$setプロパティある!
f:id:kimulla:20170724220840p:plain

プロパティを片っ端から調べたければ for..in..で調べれば良かったのか。


ということで以下の通りにすれば動作します。

...
  methods: {
    add: function () {
      this.$set(this.favorites, 'charie', 'cherry')
    }
  }
...

めでたしめでたし。
f:id:kimulla:20170724223747p:plain


ちなみにディレクティブの値に直接書くならthisは不要。JSのthisはコンテキストによって指す場所変わるから・・・

    ...
    <button type="button" @click="$set(favorites, 'charie', 'cherry')">add charie</button>
  </div>

結論

Vueうんぬんの前にJSのスコープ周りをいちから勉強し直そう。

ついでに

テンプレート内の key,valueの順番を間違えて、「Apple likes Alice」みたいな食人植物が誕生してるけど、スクショ張り直すのめんどくさいので放置。

...
      <li v-for="(value,key) in favorites">
    // 誤
        {{value}} likes {{key}}
    // 正
        {{key}} likes {{value}}
      </li>
...