開発が進められている Vue.js 3.0 にて追加される、 Composition API を使った書き方を紹介します。
現在、 Vue.js 3.0.0 beta がリリースされており、こちらを使って実装してきます。
はじめに、 Composition API の概要について簡単に紹介します。
こちらに書いてあるとおり、 Composition API とは、コンポーネントロジックの柔軟な構成を可能にする、関数ベースのAPIです。
詳しくは、紹介するコードを見てほしいのですが、 setup 関数の引数に context を渡すことで、emit や $router や $store などに関する処理を外部に切り出すことができます。
プロジェクトの作成は、 Vue CLI で行ってください。
プロジェクトを作成したら、プロジェクトのルートディレクトリで以下のコマンドを実行し、 Vue.js 3.0.0 beta を使用できるようにします。
$ vue add vue-next
それでは、実際に Composition API での書き方を紹介します。
まず、 Composition API を使って書かれた以下のコードを見てください。
<template> <p>Count is {{state.count}}</p> </template> <script> import { reactive } from 'vue' export default { setup() { const state = reactive({ count: 0 }) return { state } } } </script>
このコードは、画面に「 Count is 0 」と表示するだけのシンプルなコードです。
注目すべき点は、見慣れない setup 関数と reactive 関数です。 Composition API では、リアクティブな値は reactive 関数の引数として渡し、それを setup 関数内で return する Object に含める必要があります。
ここで言う count は、従来の書き方における data に相当します。
他にも、 function 、 watch 、 lifecycle hooks なども全て setup 関数内に定義します。
先程のコードにボタンをクリックした時に count の値を1ずつ増加させる処理を追加したコードがこちらです。
<template> <p>Count is {{state.count}}</p> <!-- 追加 --> <button @click="increment"> increment </button> </template> <script> import { reactive } from 'vue' export default { setup() { const state = reactive({ count: 0 }) // 追加 function increment() { state.count++ } return { state, increment // 追加 } } } </script>
コードを見れば分かる通り、 Composition API では function を methods に含める必要がなくなり、代わりに setup 関数の return で返却する Object に含める必要があります。
先程のコードに、 count の2倍の値を返却する computed である double を追加したコードがこちらです。
<template> <p>Count is {{state.count}}</p> <!-- 追加 --> <p>Double count is {{state.double}}</p> <button @click="increment"> increment </button> </template> <script> import { reactive, computed } from 'vue' // 追加 export default { setup() { const state = reactive({ count: 0, double: computed(() => state.count * 2) // 追加 }) function increment() { state.count++ } return { state, increment } } } </script>
computed も data と同様に reactive 関数の引数に追加し、 setup 関数で return する object に含めます。
count が3の倍数の時に hoge を、それ以外のときには fuga と表示させるようにします。
<template> <p>Count is {{state.count}}</p> <p>Double count is {{state.double}}</p> <!-- 追加 --> <p>{{state.message}}</p> <button @click="increment"> increment </button> </template> <script> import { reactive, computed, watch } from 'vue' // 追加 export default { setup() { const state = reactive({ count: 0, message: "", // 追加 double: computed(() => state.count * 2) }) function increment() { state.count++ } // 追加 watch(() => { if (state.count % 3 === 0) { state.message = "hoge" } else { state.message = "fuga" } }) return { state, increment } } } </script>
watch は setup 関数の中で watch 関数の引数に渡す関数として定義します。 watch を複数個定義したい場合は、 watch 関数を必要な数だけ書けば良いだけです。
mounted 内で count に初期値を設定するようにしたコードがこちらです。
<template> <p>Count is {{state.count}}</p> <p>Double count is {{state.double}}</p> <p>{{state.message}}</p> <button @click="increment"> increment </button> </template> <script> import { reactive, computed, watch, onMounted } from 'vue' // 追加 export default { setup() { const state = reactive({ count: 0, message: "", double: computed(() => state.count * 2) }) function increment() { state.count++ } watch(() => { if (state.count % 3 === 0) { state.message = "hoge" } else { state.message = "fuga" } }) // 追加 onMounted(() => { state.count = 1 }) return { state, increment } } } </script>
lifecycle hooks は onMounted のように onXXX の形式で提供されてるものを import して定義します。
ただし、 created と beforeCreate に関しては、 setup に置き換わっています。
その他の lifecycle hooks についてはこちらを御覧ください。
ref を使うと、 state.count のように Object にまとめる必要がなくなり、 template タグ内でも直接参照することができます。
値を更新したい場合は、以下のように .value を付ける必要があります。
<template> <p>Count is {{state.count}}</p> <!-- 追加 --> <p>Count is {{countRef}}</p> <p>Double count is {{state.double}}</p> <p>{{state.message}}</p> <button @click="increment"> increment </button> </template> <script> import { reactive, computed, watch, onMounted, ref } from 'vue' // 追加 export default { setup() { const state = reactive({ count: 0, message: "", double: computed(() => state.count * 2) }) const countRef = ref(0) // 追加 function increment() { state.count++ countRef.value++ // 追加 } watch(() => { if (state.count % 3 === 0) { state.message = "hoge" } else { state.message = "fuga" } }) onMounted(() => { state.count = 1 }) return { state, increment, countRef // 追加 } } } </script>
カウントを加算するボタンを別のコンポーネントにしたコードがこちらです。
<template> <p>Count is {{state.count}}</p> <p>Count is {{countRef}}</p> <p>Double count is {{state.double}}</p> <p>{{state.message}}</p> <!-- 追加 --> <IncrementButton :count="state.count" @emit-increment="setCount"/> </template> <script> import { reactive, computed, watch, onMounted, ref } from 'vue' import IncrementButton from "./IncrementButton.vue" export default { components:{IncrementButton}, setup() { const state = reactive({ count: 0, message: "", double: computed(() => state.count * 2) }) const countRef = ref(0) // 追加 function setCount(num) { state.count = num } watch(() => { if (state.count % 3 === 0) { state.message = "hoge" } else { state.message = "fuga" } }) onMounted(() => { state.count = 1 }) return { state, countRef, setCount // 追加 } } } </script>
<template> <button @click="increment"> increment </button> </template> <script> import { reactive } from 'vue' export default { props: { count: { type: Number, default: 0 } }, setup(props, context) { const state = reactive({ num:props.count }) function increment() { state.num++ context.emit("emit-increment", state.num) } return { state, increment } } } </script>
親コンポーネントから子コンポーネントへ count が渡り、子コンポーネントから emit-increment が emit されます。
props は setup 関数の引数に props として渡し、props.count のようにアクセスします。
emit は setup 関数の引数に context を渡し、 context.emit の引数として実行します。
今回は Composition API と TypeScript を組み合わせるところまでは調べきれなかったのですが、型推論が改善されるようなので、7月に予定されている RC版 がリリースされたら再度調べてみたいと思います。