Vue.js の算出プロパティとウォッチャを勉強する

# 算出プロパティとウォッチャ

## 算出プロパティ
テンプレート構文内に式を書けるのは便利ですが、単一式のような簡単な操作しかできません。
テンプレート構文内に多くのロジックを詰め込むと、コードが肥大化し、メンテナンスが難しくなります。

下記がアンチパターンです。

```html:html部分
<div id="example">
  {{ message.split('').reverse().join('') }}
</div>
```

```javascript:javascript部分
let app2 = new Vue({
  el: '#example',
  data: {
    message: 'Hello Vue.js'
  }
})
```

``` :ブラウザ画面
sj.euV olleH
```

Vue.jsの特徴であるシンプルさも宣言的でもありませんね。
しばらく考えて、やっとこれがmessageを逆にして表示していることに気づく可能性が生まれます。
またmessageをもう一度逆にして表示しようとした場合は、同じ事を二度書く事になります。

以上の理由から、複雑なロジックには**算出プロパティ**を利用すべきです。

### 基本的な例

```html:html部分
<div id="example">
  <p> オリジナル message: "{{ message }}" </p>
  <p> 算出プロパティ reversedMessage: "{{ reversedMessage }}"</p>
</div>
```

```javascript:javascript部分
let app = new Vue({
  el: '#example',
  data: {
    message: 'Hello Vue.js'
  },
  // 算出プロパティ
  computed: {
    // reverseした値を返すgetter関数
    reversedMessage: function(){
      // this.messageはdataプロパティのmessageを示します。
      return this.message.split('').reverse().join('')
    }
  },
})
```

``` :ブラウザの表示
オリジナル message: "Hello Vue.js"

算出プロパティ reversedMessage: "sj.euV olleH"
```

例では、算出プロパティ reversedMessage を宣言しました。 
reversedMessageはmessageを逆にした文字列を返します。

算出プロパティは "インスタンス名.算出プロパティ名" で参照できます。
今回はapp.reversedMessageになります。
そしてreversedMessageはdataプロパティのmessageに依存しています。

```javascript:開発者モードのConsole
app.message = "今日の朝ごはんはパン"
```

``` :ブラウザの表示
オリジナル message: "今日の朝ごはんはパン"

算出プロパティ reversedMessage: "ンパはんはご朝の日今"
```

app.reversedMessageはapp.messageに依存しているためapp.messageに変更が加わるとvm.reversedMessageと依存関係にある全てのバインディングを更新されます。

## 算出プロパティ vs メソッド 比較
公式リファレンスの導入を読んでいれば算出プロパティと同じ結果が実現できるプロパティがある事とに気づいていると思います。

```html:html部分
<div id="example">
  {{ reversedMessage() }}
</div>
```

```javascript:javascript部分
let app = new Vue({
  el: '#example',
  data: {
    message: 'Hello Vue.js'
  },
  // 
  methods: {
    // reverseした値を返す関数
    reversedMessage: function(){
      // this.messageはdataプロパティのmessageを示します。
      return this.message.split('').reverse().join('')
    }
  }
})
```

``` :ブラウザの表示
オリジナル message: "Hello Vue.js"

算出プロパティ reversedMessage: "sj.euV olleH"
```

methodsとcomputedは最終的に完全に同じ結果を返します。
しかし、算出プロパティは**リアクティブな依存関係に基づきキャッシュされる**という違いがある。
どういうことかと言うと、算出プロパティは依存関係にあるプロパティが更新された時だけ再度実行されます。
例で言えばmessageが更新されない限りreversedMessageを何度呼び出しても関数を実行することなく以前行なった処理の結果を即時に返します。

また標準ビルトインオブジェクトのDateやMathはリアクティブな依存関係ではないため、何度呼び出しても算出プロパティは更新されません。

```html:html部分
<div id="example">
  <p> 標準ビルトインオブジェクトDate関数: "{{ nowDate }}"</p>
  <p> 標準ビルトインオブジェクトMath関数: "{{ mathRandom }}"</p>
</div>
```

```javascript:javascript部分
let app = new Vue({
  el: '#example',
  computed: {
    // 現在時刻の値を返すgetter関数
    nowDate: function(){
      return Date.now()
    },
    // ランダムな数値を返すgetter関数
    mathRandom: function(){
      return Math.random()
    }
  }
})
```

```:ブラウザ表示
標準ビルトインオブジェクトDate関数: "1562032047940"

標準ビルトインオブジェクトMath関数: "0.9989563299598017"
```

```javascript:開発者モードのConsole
app.nowDate
// 実行結果 1562032047940

app.mathRandom
// 実行結果 0.9989563299598017

```

対称的に、メソッド呼び出しは、再レンダリングが起きると**常に**関数を実行します。

```html:html部分
<div id="example">
  <p> 標準ビルトインオブジェクトMath関数: "{{ mathRandom() }}"</p>
</div>
```

```javascript:javascript部分
let app = new Vue({
  el: '#example',
  methods: {
    // ランダムな数値を返す関数
    mathRandom: function(){
      return Math.random()
    }
  }
})
```

```javascript:開発者モードのConsole
app.mathRandom()
// 実行結果 0.538831055923352

app.mathRandom()
// 実行結果 0.35241559323620564
```

なぜ算出プロパティで**キャッシング**する必要があるのか？
巨大な配列をループしたり多くの計算を必要とする、非常にコストのかかる処理を常に再レンダリングしていたら無駄な時間が発生してします。キャッシングをしていれば依存関係のあるプロパティが更新されない限り関数は呼ばれず同じを即座に返してくれるということです。

## 算出プロパティ vs 監視プロパティ  
**監視プロパティ(watched property)** があります。 
Vueインスタンス上の**データの変更を監視**し反応させることができる。

```html:html部分
<div id="example">
  <p> {{ fullName }}</p>
</div>
```

```javascript:javascript部分
let app = new Vue({
  el: '#example',
  data: {
    firstName: '田中',
    lastName: '太郎',
    fullName: '田中　太郎'
  },
  watch: {
    // firstNameを監視し値が変化した場合に関数を呼び出す
    firstName: function (val,oldVal) {
      // 第１引数：valは変更された値
      // 第２引数：oldValは変更前の値
      this.fullName = val + ' ' + this.lastName
    },
    // lastNameを監視し値が変化した場合に関数を呼び出す
    lastName: function (val,oldVal) {
      // 第１引数：valは変更された値
      // 第２引数：oldValは変更前の値
      this.fullName = this.firstName + ' ' + val
    }
  }
})
```

```:ブラウザ表示
名前：田中　太郎
```

```javascript:開発者モードのConsole
app.firstName = "鈴木"
```

```:ブラウザ表示
名前：鈴木　太郎
```

```javascript:開発者モードのConsole
app.lastName = "愛佳"
```

```:ブラウザ表示
名前：鈴木　愛佳
```

上記の例ではwatchプロパティでfirstNameとlastNameを監視しデータに変更が入るとwatchプロパティの関数が呼び出されます。

上記のコードは命令的であり冗長です。
監視プロパティは多くの場合算出プロパティを利用して書く事ができます。

```html:html部分
<div id="example">
  <p> {{ fullName }}</p>
</div>
```

```javascript:javascript部分
let app = new Vue({
  el: '#example',
  data: {
    firstName: '田中',
    lastName: '太郎'
  },
  computed: {
    // firstNameかlastNameに変更が行われた場合にフルネームを返すgetter関数
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})
```

全く同じ動作をするコードでも算出プロパティを利用することで冗長がなくなりスッキリとしたコードを書くことができるので、算出プロパティで書けない場合などに監視プロパティを利用することを検討しましょう。

## 算出Setter関数
算出プロパティはデフォルトではgetter関数のみですが、必要であればsetterも使う事ができます。

```html:html部分
<div id="example">
  <p> {{ fullName }}</p>
</div>
```

```javascript:javascript部分
let app = new Vue({
  el: '#example',
  data: {
    firstName: '田中',
    lastName: '太郎'
  },
  computed: {
    fullName:{
      // firstNameかlastNameに変更が行われた場合にフルネームを返すgetter関数
      get: function () {
        return this.firstName + ' ' + this.lastName
      },
      // fullNameに値の変更を行うと呼ばれるsetter関数
      set: function (newValue) {
        var names = newValue.split(' ')
        this.firstName = names[0]
        this.lastName = names[names.length - 1]
      }
    }
  }
})
```
```:ブラウザ表示
名前：田中　太郎
```

```javascript:開発者モードのConsole
app.fullName = "鈴木 太郎"
```

```:ブラウザ表示
名前：鈴木　太郎
```

app.fullNameに鈴木 太郎を渡すとset(setter関数)が呼び出されます。
setter関数でfirstNameとlastNameに値を更新しているので確認

```javascript:開発者モードのConsole
app.firstName
// 鈴木

app.lastName
// 太郎
```

## あとがき
ウォッチャについて算出プロパティでは処理を書けない例がのっていますので確認してみてください。
基本的に算出プロパティで書けない時にwatchを使うって考えで大丈夫だと思ってます。

## 参考資料
[Vue.js公式リファレンス](https://jp.vuejs.org/v2/guide/computed.html)