とりあえずVue.jsのComposition APIで書くためのまとめ


自分含め、これまで Vue.js 2.x でコードを書いていた人に向けて書きます。
あまり深いところや細かいところまでは掘らずに、Vue.js 2.x で書いてきた基本的な記述をComposition APIで書くために、と言う視点で書いてみました。

# 目次

- Composition API
- Vue.js 2.xで慣れ親しんだ書き方からの移行
- ロジックの抽出と再利用

<!--
- 型推論のサポート
-->

# Composition API

これまでのVue.jsでは `data` `computed` `methods` などのoptionごとにリアクティブなデータや関数などを書いてきたが（`Options API`というらしい）、大きなコンポーネントになってくると必要な機能ごとにコードをまとめた方がいい。そのための関数ベースのAPI群。

Vue.jsのv3から利用可能予定。
v2でも[プラグイン](https://github.com/vuejs/composition-api/)として利用可能。

現在はRFCとして仕様を揉んでいるところなのでここに記載の内容は変更される可能性があります。
https://vue-composition-api-rfc.netlify.app/


## メリット

- 自由なコードの構成
- ロジックの抽出と再利用
- 型推論の強化

これまでのVue.jsのOptions APIを上書きするのものではなく、上記のメリットを受けられるような大規模なプロジェクトで使うのが良い。
Options APIはそれ自体がコードの規約となるので小・中規模なプロジェクトでとても有用と思う。

## setup()

`setup`は新しいOptionで、コンポーネント内でComposition API を使うときのエントリーポイントになります。
`setup`関数内にリアクティブなデータや算出プロパティ、メソッドなどを定義していきます。



```html
<template>
 ...
</template>

<script>
import { computed, ref, reactive } from 'vue' // 必要なAPIはimportが必要
export default {
  setup() {
    // 
  }
}
</script>

<style>
 ...
</style>
```

`setup`関数内で使う`computed`, `ref`, `reactive` などの関数（後述）はimportしておく必要があります。

```javascript
import { computed, ref, reactive } from 'vue'
```


## templateで使うものは return { } が必要

`template`で使う値や関数は`setup`関数の最後で`return`しておく必要がある。
下の例でいうとreturnされた `count` などは、いままでのOptions APIで言うところの `this.count` のような形でぶら下ります。
いちいちこれを書く手間がある一方、`template`でどの値/関数が使われているかがわかりやすいというメリットもあります。

```html
<template>
  <div>
    {{ count }} * 2 = {{ double }}
    <button @click="increment">+</button>
  </div>
</template>

<script>
import { computed, ref, reactive } from 'vue'
export default {
  setup() {
    const count = ref(0)
    const double = computed(() => {
      return count.value * 2
    }(

    const increment = () => {
      count.value++
    }
    
    // template で出力するデータはここで渡す必要がある
    return { count, double, increment }
  }
}
</script>
```


# Options APIからCompositio APIへの移行

これまで慣れ親しんだOptions APIでの書き方をComposition APIで書くときのまとめです。
`data`や`computed`などoptionごとに書いてます。

## data

`ref()`、または`reactive()`でリアクティブなデータを扱うことができます。

### ref()

`ref`関数は数値や文字列、booleanなどのプリミティブ値を引数にとり、リアクティブなrefオブジェクトを返す
`setup`関数内でリアクティブな値を使う場合は`.value`プロパティを参照します。

```html
<template>
  <div>
    {{ count }}
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  setup() {
    const count = ref(0) // リアクティブな値をもつオブジェクトを返す
    console.log(count.value) // 0
    
    count.value++
    console.log(count.value) // 1
    
    const increment = () => {
      count.value++
    }
    return { count } // return する場合はリアクティブなrefオブジェクトごと返す。 .value は不要
  }
}
</script>
```

### reactive()

オブジェクトを引数にとり、リアクティブプロキシーを返します。Vue.js 2.x系の`Vue.observable()`と等しいようです。
(参考：[Proxy - MDN web docs](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy))

The reactive conversion is "deep": it affects all nested properties. In the ES2015 Proxy based implementation, the returned proxy is not equal to the original object. It is recommended to work exclusively with the reactive proxy and avoid relying on the original object.


```html
<template>
  <div>
    {{ state.count }}
    {{ state.code }}
  </div>
</template>

<script>
import { reactive } from 'vue'
export default {
  setup() {
    const state = reactive({
      count: 1,
      code: 'ready'
    }
    
    state.count++ // 2 refオブジェクトと違い.valueを参照しない

    return { state }
  }
}
</script>
```

## computed

算出プロパティは`computed`関数を使います。
引数にgetter関数をとり、イミュータブルな`ref`オブジェクトを返します。
templateで使う場合はもちろん`return`する必要があります。

```javascript
import { ref, computed } from 'vue'
export default {
  setup() {
    const count = ref(2) 
    count.value++ // 3
 
    const double = computed(() => {
      return count.value * 2
    })
    console.log(double.value) // 6
    double.value++ // error

    return { double }
  }
}
```

## methods
Composition APIのなかで`methods`は単純なJavaScriptの関数として宣言します。
`template`内で利用する場合はリアクティブな値と同様、`setup`関数の最後に`return`してあげる必要があります。


```html
<template>
  <div>
    Count is {{ count }} 
    <button @click="increment">+</button>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  setup() {
  const count = ref(1)
  
    // 単純なJSの関数として宣言する
    const increment = () => {
      count.value++
    }

    return { count, increment }
  }
}
</script>
```

## props

これまで同様、`setup`関数の外で指定。`setup(props)`のように引数に指定し使う。setupに渡されたpropsオブジェクトはリアクティブであり、

```html
<template>
  <div>ID : { id }</div>
</template>
<script>
export default {
  props: {
    id: string
  },
  setup(props) {
    // setup関数内では props.id で扱うことができる
    
    return { props }
  }
}
</script>
```

ただし`props`オブジェクトからプロパティだけを取り出すとリアクティビティは失われます。

```javascript
export default {
  props: {
    id: Number
  },
  setup({ id }) {
    watchEffect(() => {
    console.log(`id is: ` + id) // リアクティブではなくなる。
    })
  }
}
```

## created(), mounted(), updated() 

RFCのリファレンスには以下のような対応表があります。
https://vue-composition-api-rfc.netlify.app/api.html#lifecycle-hooks

- `beforeCreate` -> `setup()`を使う
- `created` -> `setup()`を使う
- `beforeMount` -> `onBeforeMount`
- `mounted` -> `onMounted`
- `beforeUpdate` -> `onBeforeUpdate`
- `updated` -> `onUpdated`
- `beforeDestroy` -> `onBeforeUnmount`
- `destroyed` -> `onUnmounted`
- `errorCaptured` -> `onErrorCaptured`

### created()
`setup`関数はpropの初期値が解決された後、`beforeCreated`フックと`created`フックの前に呼ばれます。
そのため`setup`自体を`beforeCreated`, `created`として扱うのがいいようです。

なお、`setup`はいままでの`created`よりも前に解決されるため、

- setup関数内からはこれまでの data() のプロパティやmethodsの関数にアクセスできない
- 逆に`setup`でreturn された値/関数はcreated() やmethodsの中で `this.xxx`の表記でアクセスできる。

となります。

### mounted(), updated()

`created`, `beforeCreated`以外のライフサイクルフック、例えば`mounted`, `updated`なんかは
`onXxxx`の形でimportした上で`setup`関数内で`onMounted()` `onUpdated()` のように使います。

```javascript
import { onMounted, onUpdated } from 'vue'
export default {
  setup() {
    onMounted(() => {
      console.log('mounted.')
    }
    onUpdated(() => {
      console.log('updated.')
    })
  }
}
```




### そのほかのライフサイクルフック

`beforeMount()`, `beforeUpdate()`, `beforeDestroy()`, `destroyed()`, `errorCaptured()`などのそのほかのLife Cycle Hookも同様に関数をimportして`setup`内で使います。

```javascript
import { onBeforeMount, onBeforeUpdate, onBeforeUnmount, onUnmounted, onErrorCaptured   } from 'vue'
export default {
  setup() {
    onBeforeMount(() => { ... }) // = beforeMount()
    onBeforeUpdate(() => { ... }) // = beforeUpdate()
    onBeforeUnmount(() => { ... }) // = beforeDestroy()
    onUnmounted(() => { ... }) // = destroyed()
    onErrorCaptured(() => { ... }) // = errorCaptured()
  }
}
```

## emit, attrs, slots

`emit`, `attrs`, `slots`は、`setup`関数の第二引数に渡される`context`オブジェクトを通してアクセス可能です。

```javascript

export default {
  setup(props, context) {
    context.attrs
    context.slots
    context.emit
  }
}
```

## this.$refs

公式のDocにあったものではないですが、同様なことはこんな感じでできそう。

```html
<template>
  <h1 ref="hogeTitle">タイトル</h1>
</template>
<script>

import { ref, onMounted } from 'vue'
export default {
  setup() {
    // 空のリアクティブなオブジェクトを作成
    const hogeTitle = ref(null)
    console.log(hogeTitle.value) // 当然 null。 setup() はcreatedの前、templateが解決される前に実行されるので

    onMounted(() => {
      // templateが解決され、このタイミングでアクセスできる
      console.log(hogeTitle.value) // <h1>タイトル</h1>
    })
    return { hogeTitle } // これが必要
  }
}
</script>
```


# ロジックの抽出と再利用

Composition APIを使うメリットの一つにロジックの抽出とその再利用があります。

いくつかのコンポーネント間で利用されるような機能のロジックや、膨大で複雑なコードの機能を切り出して再利用することができ、見通しがよく効率的なコードが書けます。
ロジックを抽出して再利用する際にも、Composition APIのコード構成に対する非常に高い柔軟性が活きてきます。

以下の例はクリックで増えるカウントに対する機能を抽出しています。

```javascript
// count.vue 
import { ref, computed, onMounted } from 'vue'

export function count() {
  const count = ref(0)
  
  const update = (e) => {
    count.value++
  }

  const displayCount = computed(() => {
    return count.value >= 10 ? '10+' : count.value
  })
  
  onMounted(() => {
    window.addEventListener('click', update)
  })

  return { cont, displayCount }
}
```

抽出した`count`関数を利用します


```javascript
import { count } from './count'

export default {
  setup() {
    const { count, displayCount } = count()
    
    return { count, displayCount }
  }
}
```

<!--
# 型推論のサポート
-->



# 終わりに

Compositio APIの基本的な書き方、そしてこれまでのVue.jsに慣れ親しんだ人からの目線での書き方をまとめてみました。

Composition APIにはここで紹介していないAPIがまだあります。
冒頭の[メリット](#メリット)でも触れたように、自由なコード構成、型サポートの強化、ロジックの抽出など、特に大規模なプロジェクトでは非常に可能性を感じさせる新機能となることでしょう。

一方、RFCである現在（2020/06）はAPIの追加や破壊的変更がされる可能性があります。
そのため業務での利用はまだ難しいですが、
来るべきVue.js 3.0の正式リリースに備え今から概要を掴んでいくのは良いかなと思います。