はじめに
開発が進められている Vue.js 3.0 にて追加される、 Composition API を使った書き方を紹介します。
現在、 Vue.js 3.0.0 beta がリリースされており、こちらを使って実装してきます。
Composition APIとは
はじめに、 Composition API の概要について簡単に紹介します。
こちらに書いてあるとおり、 Composition API とは、コンポーネントロジックの柔軟な構成を可能にする、関数ベースのAPIです。
詳しくは、紹介するコードを見てほしいのですが、 setup 関数の引数に context を渡すことで、emit や $router や $store などに関する処理を外部に切り出すことができます。
環境構築
プロジェクトの作成は、 Vue CLI で行ってください。
プロジェクトを作成したら、プロジェクトのルートディレクトリで以下のコマンドを実行し、 Vue.js 3.0.0 beta を使用できるようにします。
1 | $ vue add vue-next |
Composition API での書き方
それでは、実際に Composition API での書き方を紹介します。
まず、 Composition API を使って書かれた以下のコードを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <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 関数内に定義します。
function
先程のコードにボタンをクリックした時に count の値を1ずつ増加させる処理を追加したコードがこちらです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <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 に含める必要があります。
computed
先程のコードに、 count の2倍の値を返却する computed である double を追加したコードがこちらです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <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 に含めます。
watch
count が3の倍数の時に hoge を、それ以外のときには fuga と表示させるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | <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 関数を必要な数だけ書けば良いだけです。
lifecycle hooks
mounted 内で count に初期値を設定するようにしたコードがこちらです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <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
ref を使うと、 state.count のように Object にまとめる必要がなくなり、 template タグ内でも直接参照することができます。
値を更新したい場合は、以下のように .value を付ける必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <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> |
props と emit
カウントを加算するボタンを別のコンポーネントにしたコードがこちらです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <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> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <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版 がリリースされたら再度調べてみたいと思います。