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

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

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

```vuejs:template部分
<template>
{{ obj }}
<button @click="updateObjDefault">updateObjDefault</button>
<button @click="updateObjAdditional">updateObjAdditional</button>
</template>
```

```vuejs: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()`はレンダリングされたあとに実行されるため

## リアクティブになるプロパティ追加
```vuejs: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()`の説明は公式ドキュメントの[この辺](https://jp.vuejs.org/v2/guide/list.html#%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E5%A4%89%E6%9B%B4%E6%A4%9C%E5%87%BA%E3%81%AE%E6%B3%A8%E6%84%8F)とか[この辺](https://jp.vuejs.org/v2/guide/reactivity.html#%E5%A4%89%E6%9B%B4%E6%A4%9C%E5%87%BA%E3%81%AE%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85)とか

# 配列の「中」はリアクティブではない
## リアクティブに反映されないパターン
```vuejs:template
<template>
{{ arr }}
<button @click="updateArrFirst">updateArrFirst</button>
<button @click="updateArrSecond">updateArrSecond</button>
</template>
```

```vuejs: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()`で上書きと追加を行うと初期表示にすら反映されない

## リアクティブに反映されるパターン
```vuejs:template
<template>
<li>{{ arr }}</li>
<button @click="updateArrFirst">updateArrFirst</button>
<button @click="updateArrSecond">updateArrSecond</button>
</template>
```

```vuejs: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 配列ごと上書きする
  - コピーして変更して丸ごと上書きとか
  - 中の要素はリアクティブではないが**配列そのものはリアクティブ**なので上書きする代入で再描画される
  - [公式ドキュメント](https://jp.vuejs.org/v2/guide/list.html#%E9%85%8D%E5%88%97%E3%81%AE%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88)
- ※2 `splice()`,`push()`などリレンダリングのトリガーになってるメソッドを使う
    - 詳しい説明やその他のメソッドは[公式ドキュメント](https://jp.vuejs.org/v2/guide/list.html#%E5%A4%89%E6%9B%B4%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89)に
- ※3 `this.$set()`を使って代入することでリレンダリングさせる
  - ただしオブジェクトと違い`this.$set`してもそれ以降リアクティブになるわけではない
  - あくまでリレンダリングのトリガーとして使う
  - [公式ドキュメント](https://jp.vuejs.org/v2/guide/list.html#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85)とか


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

## 参考資料
公式の
- [ライフサイクル](https://jp.vuejs.org/v2/guide/instance.html)
- [リアクティブ](https://jp.vuejs.org/v2/guide/reactivity.html)
- [リストレンダリング](https://jp.vuejs.org/v2/guide/list.html)
あたりを読むとわかりやすい