過去のコードを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」を選択します
$ 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します
$ cd composition-api
$ yarn add @vue/composition-api
- main.tsでComposition APIを宣言します
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
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
});
</script>
Composition API
setup関数 の中にdataやmethodを定義していきます。defineComponent を用いることで型推論を有効化しています。
<script lang="ts">
import { defineComponent } from "@vue/composition-api";
export default defineComponent({
setup() {
}
});
</script>
data
Option API
<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 はオブジェクトを宣言するときに使いますが、まだ明確な使い分けは決まっていないようです。
<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
<script lang="ts">
...
export default Vue.extend({
...
methods: {
increment(count: number): void {
this.state.count = count;
}
}
});
</script>
Composition API
methodもdataと同様にsetup関数内で定義し、returnします。
<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
<script lang="ts">
...
export default Vue.extend({
...
computed: {
double(): number {
return this.state.count * 2;
}
}
});
</script>
Composition API
computedはsetup関数内で computed 関数を用いて定義します。
こちらもdata, methodと同様、returnしてあげます。
<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
// 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>
// 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で受け取ったデータを処理します。
// 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 です。
// 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
<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 |
<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で書き直しながらリファクタリングしていきたいと思います。
この記事を書いた人
-
東京で2年半エンジニアとしての経験を積み、浜松にUターンの後、アーティスへ入社。
ソリューション事業部のWebエンジニアとして、システムの設計・開発・保守・運用からインフラまで幅広く従事している。
フルスタックエンジニア目指して現在も勉強の日々。車が好き。
この執筆者の最新記事
関連記事
最新記事
FOLLOW US
最新の情報をお届けします
- facebookでフォロー
- Twitterでフォロー
- Feedlyでフォロー