過去のコードをComposition APIを使って書き直してみる(前編)

VueのメジャーバージョンアップがQ3 2020に予定されており、Roadmap通りであればもうすぐ公開されます。
そこで、Vue3の目玉のひとつである「Composition API」を使って、過去に当ブログでご紹介したコードを書き直してみたいと思います。
今回は前編として、Composition APIについてまとめて行きます。
Composition APIとは?
公式には「コンポーネントロジックの柔軟な構成を可能にする追加の関数ベースのAPI」と記載があります。
具体的には、コードが見やすくなったり、コードの共通化がしやすくなったり、TypeScriptライクになったりするみたいです。
筆者の環境
@vue/cli 4.4.1
環境構築
- presetは「Manually select features」を選択します
- featuresは今回「TypeScript」を指定します。
- Use class-style componentは「No」を選択します
1 2 3 4 5 6 7 |
$ vue create composition-api ? Please pick a preset: Manually select features ? Check the features needed for your project: Babel, TS ? Use class-style component syntax? No ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes ? Where do you prefer placing config for Babel, ESLint, etc.? In package.json ? Save this as a preset for future projects? No |
- Vue2系でComposition APIを使えるよう、「@vue/composition-api」をAddします
1 2 |
$ cd composition-api $ yarn add @vue/composition-api |
- main.tsでComposition APIを宣言します
1 2 3 4 |
import Vue from 'vue' import VueCompositionApi from "@vue/composition-api"; Vue.use(VueCompositionApi); |
以上で、Vue2系でもComposition APIが使えるvue-cliの環境ができました。
書き方の違い
標準(Option API)のTypeScriptとComposition APIを使用した書き方の違いを見ていきます。
定義
Option API
1 2 3 4 5 6 |
<script lang="ts"> import Vue from "vue"; export default Vue.extend({ }); </script> |
Composition API
setup関数 の中にdataやmethodを定義していきます。defineComponent を用いることで型推論を有効化しています。
1 2 3 4 5 6 7 8 |
<script lang="ts"> import { defineComponent } from "@vue/composition-api"; export default defineComponent({ setup() { } }); </script> |
data
Option API
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<script lang="ts"> ... export default Vue.extend({ data: () => { return { title: "option-api", state: { count: 0 } } } }); </script> |
Composition API
ref関数 と reactive関数 を用いて宣言します。<template> 内で扱うdataはsetup関数内でreturnしてあげます。
とあるように、 ref はプリミティブな値、 reactive はオブジェクトを宣言するときに使いますが、まだ明確な使い分けは決まっていないようです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<script lang="ts"> import { defineComponent, ref, reactive } from "@vue/composition-api"; export default defineComponent({ setup() { // data const title = ref<string>("composition-api"); const state = reactive<{ count: number }>({ count: 0 }); return { title, state } } }); </script> |
method
Option API
1 2 3 4 5 6 7 8 9 10 11 |
<script lang="ts"> ... export default Vue.extend({ ... methods: { increment(count: number): void { this.state.count = count; } } }); </script> |
Composition API
methodもdataと同様にsetup関数内で定義し、returnします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<script lang="ts"> ... export default defineComponent({ setup() { ... const state = reactive<{ count: number }>({ count: 0 }); // method const increment = (count: number): void => { state.count = count; }; return { state, onIncrement } } }); </script> |
computed
Option API
1 2 3 4 5 6 7 8 9 10 11 |
<script lang="ts"> ... export default Vue.extend({ ... computed: { double(): number { return this.state.count * 2; } } }); </script> |
Composition API
computedはsetup関数内で computed 関数を用いて定義します。
こちらもdata, methodと同様、returnしてあげます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<script lang="ts"> import { defineComponent, reactive, computed } from "@vue/composition-api"; export default defineComponent({ setup() { const state = reactive<{ count: number }>({ count: 0 }); // computed const double = computed((): number => state.count * 2); return { state, double } } }); </script> |
props, emit
Option API
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 |
// Parent.vue <template> <div> <child :count="state.count" @increment="onIncrement" /> </div> </template> <script lang="ts"> import Vue from "vue"; import Child from "@/components/Child.vue"; export default Vue.extend({ components: { Child }, data: () => { return { state: { count: 0 } } }, methods: { onIncrement(payload: number): void { this.state.count = payload; } } }); </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 |
// Child.vue <template> <div> <button @click="increment"> {{ count }} </button> </div> </template> <script lang="ts"> import Vue from "vue"; export default Vue.extend({ props: { count: { type: Number } }, methods: { increment(): void { this.$emit("increment", this.count + 1); } } }); </script> |
Composition API
親ComponentはOption APIと基本的には同じです。
v-bindで子Componentにpropsでデータを渡し、子ComponentのEmit Eventを受けて発火させたmethodで受け取ったデータを処理します。
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 |
// Parent.vue <template> <div> <!-- v-bindで子Componentにデータを渡し、 Emit Eventでmethodを発火させる。 --> <child :count="state.count" @increment="onIncrement" /> </div> </template> <script lang="ts"> import { defineComponent, reactive } from "@vue/composition-api"; import Child from "@/components/Child.vue"; export default defineComponent({ components: { Child }, setup() { const state = reactive<{ count: number }>({ count: 0 }); // 子Componentから受け取ったデータを処理する const onIncrement = (payload: number): void => { state.count = payload; }; return { state, onIncrement } } }); </script> |
子Componentではpropsで受け取った値をsetup関数に渡します。
emitには this ではなく context を用います。
contextの型は SetupContext です。
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 |
// Child.vue <template> <div> <!-- Click Eventでmethodを発火 --> <button @click="increment"> {{ count }} </button> </div> </template> <script lang="ts"> import { defineComponent, SetupContext } from "@vue/composition-api"; // Propsの型定義 type Props = { count: number; }; export default defineComponent({ props: { count: { type: Number } }, // setup関数にpropsとcontextを渡す setup(props: Props, context: SetupContext) { // Emit Eventで親Componentのmethodを発火させる const increment = () => { context.emit("increment", props.count + 1); }; return { increment }; } }); </script> |
Lifecycle Hooks
Option API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<script lang="ts"> ... export default Vue.extend({ ... mounted() { console.log("mounted!"); }, updated() { console.log("updated!"); }, destroyed() { console.log("destroyed!"); } }); </script> |
Composition API
setup関数内で各関数を定義し、その中にロジックを書きます。
マッピングは下記の表の様になっています。
created と beforeCreate はsetup関数に内包されています。
Composition API | |
---|---|
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
activated | onActivated |
deactivated | onDeactivated |
errorCaptured | onErrorCaptured |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<script lang="ts"> import { defineComponent } from "@vue/composition-api"; import { onMounted, onUpdated, onUnmounted } from "@vue/composition-api"; export default defineComponent({ setup() { onMounted(() => { console.log("mounted!"); }); onUpdated(() => { console.log("updated!"); }); onUnmounted(() => { console.log("destroyed!"); }); } }); </script> |
まとめ
筆者自身がフロントエンド開発経験が少ないこともありますが、 this に縛られ、思うようにmethodを切り出せなかったり、TypeScriptの型推論と格闘したりと苦い思い出があります。 今回Composition APIを導入してみて、ストレスフリーな書き味になったなと感じました。
次回は、当ブログの過去記事「JavaScriptフレームワークの「Vue.js」を使ってToDoリストを実装してみよう」前編 後編 のコードを実際にComposition APIで書き直しながらリファクタリングしていきたいと思います。

ばね

最新記事 by ばね (全て見る)
- webサイトにInstagramの投稿を表示する「Facebook Graph API」の使い方(準備編) - 2021年1月18日
- 過去のコードをComposition APIを使って書き直してみる(後編) - 2020年11月16日
- 過去のコードをComposition APIを使って書き直してみる(前編) - 2020年9月14日
- CodeIgniter3にTwigを連携して使う - 2020年7月16日
- Markdownドキュメントを共同編集できるツール「HackMD」を使って作業効率をアップさせよう! - 2020年5月14日
おすすめ関連記事
最新記事