LoginSignup
18
12

More than 5 years have passed since last update.

今更Vue.jsのリアクティブを探求してみた

Posted at

Vue.jsにおけるオブジェクトと配列は普通の変数とは挙動が違って手こずったので
しっかりと調べ直してみました

オブジェクトにdata()で定義していないプロパティを後から追加してもリアクティブにならない場合がある

リアクティブにならないプロパティ追加

template部分
<template>
{{ obj }}
<button @click="updateObjDefault">updateObjDefault</button>
<button @click="updateObjAdditional">updateObjAdditional</button>
</template>
script部分
<script>
data() {
  return {
    obj: { default: 1 }
  }
},
methods: {
  updateObjDefault() {
    // templateに即時反映される
    this.obj.default += 1;
  },
  updateObjAdditional() {
    // templateに即時反映されない
    this.obj.additional += 1;
  }
},
created() {
  this.obj.additional = 1;
  // ↓これもダメ
  // this.obj = Object.assign(this.obj, { additional: 1 });
}
</script>

みたいにすると

ブラウザでページ開いたときに
{ "default": 1, "additional": 1 }

と初期表示はされるがadditionalの方はリアクティブになっていないので
クリックイベント(updateObjAdditional)でobj.additionalの値が変更されたとしてもビューに反映されない

  • ※他のトリガーによる再描画のタイミングでやっと反映される
  • mounted()でプロパティ追加すると"additional": 1は初期表示すらされない
    • created()はレンダリングの前、mounted()はレンダリングされたあとに実行されるため

リアクティブになるプロパティ追加

script
<script>
created() {
  this.$set(this.obj, 'additional', 1);
  // もしくは
  // this.obj = Object.assign({}, this.obj, { additional: 1 });
}
</script>

みたいにすると

ブラウザを開いたときに
{ "default": 1, "additional": 1 }

という初期表示で、additionalもリアクティブになっているので
クリックイベント(updateObjAdditional)でobj.additionalの値が変更されると
ビューに即時反映(リレンダリング)される

  • ※この方法でプロパティ追加すると追加や変更のタイミングで再描画される
    • リアクティブになるのでmounted()でやってもadditionalが表示され、変更も即時反映される
  • this.$set()Object.assign()の説明は公式ドキュメントのこの辺とかこの辺とか

配列の「中」はリアクティブではない

リアクティブに反映されないパターン

template
<template>
{{ arr }}
<button @click="updateArrFirst">updateArrFirst</button>
<button @click="updateArrSecond">updateArrSecond</button>
</template>
script
<script>
data() {
  return {
    arr: [1]
  }
},
methods: {
  updateArrFirst() {
    this.arr[0] += 1;
  },
  updateArrSecond() {
    this.arr[1] += 1;
  }
},
created() {
  this.arr[0] += 1;
  this.arr[1] = 1;
}
</script>

みたいにすると

ブラウザを開いたときに
[ 2, 1 ]

という初期表示になるが配列の中身はリアクティブじゃないので
クリックイベントなどで代入による要素上書きや追加をしてもリアクティブにビューに反映されない

  • ※リアクティブになっていないオブジェクトのプロパティと同様に他のトリガーによる再描画のタイミングでやっと反映される
    • mounted()で上書きと追加を行うと初期表示にすら反映されない

リアクティブに反映されるパターン

template
<template>
<li>{{ arr }}</li>
<button @click="updateArrFirst">updateArrFirst</button>
<button @click="updateArrSecond">updateArrSecond</button>
</template>
script
<script>
data() {
  return {
    arr: [1]
  }
},
methods: {
  updateArrFirst() {
    // this.arr[0] += 1 をリアクティブに反映させる書き方 ※1
    this.arr[0] = [this.arr[0] + 1, this.arr[1]];
  },
  updateArrSecond() {
    // this.arr[1] += 1 をリアクティブに反映させる書き方 ※2
    // indexが1(2番目)の要素から1つ取り除き、`this.arr[1] + 1`を挿入する
    this.arr.splice(1, 1, this.arr[1] + 1);
  }
}
created() {
  // this.arr[0] += 1 をリアクティブに反映させる書き方 ※3
  this.$set(this.arr, 0, this.arr[0] + 1);

  // this.arr[1] = 1 をリアクティブに反映させる書き方 ※2
  this.arr.push(1);
}
</script>
  • ※1 配列ごと上書きする
    • コピーして変更して丸ごと上書きとか
    • 中の要素はリアクティブではないが配列そのものはリアクティブなので上書きする代入で再描画される
    • 公式ドキュメント
  • ※2 splice(),push()などリレンダリングのトリガーになってるメソッドを使う
  • ※3 this.$set()を使って代入することでリレンダリングさせる
    • ただしオブジェクトと違いthis.$setしてもそれ以降リアクティブになるわけではない
    • あくまでリレンダリングのトリガーとして使う
    • 公式ドキュメントとか

入れ子(配列の中のオブジェクト、オブジェクトの中の配列)の場合も

同じ

参考資料

公式の
- ライフサイクル
- リアクティブ
- リストレンダリング
あたりを読むとわかりやすい

18
12
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
18
12