Vue.js v3.xのRFC(Request for Comments) となっているComposition API を色々触ってみたのでまとめます。
Vue Composition API とは?
Introducing the Composition API: a set of additive, function-based APIs that allow flexible composition of component logic
とある通り、コンポーネントのロジックの柔軟なコンポジションを可能にする関数ベースのAPIです。
型推論の改善と、合成関数によるロジックの整理が可能になっています。
@vue/composition-api
を追加することで、Vue2系でも使用することができます。
環境構築
vue-cli
でプロジェクトを作成します。
ポイントはManually select features
を選びTypeScript
を選択すること。class-component
は使わないので N
を選んで下さい。
$ vue create practice-composition-api
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS, Linter
? Use class-style component syntax? No # class-componentの利用では `N`を選んで下さい
...
$ cd practice-composition-api
$ yarn add @vue/composition-api
続いて main.ts
で@vue/composition-api
の利用を宣言します。
import Vue from "Vue";
import VueCompositionApi from "@vue/composition-api";
Vue.use(VueCompositionApi);
これでComposition APIを使う準備はOKです。
コンポーネントの作成 ~ defineComponent ~
defineComponent
関数によりコンポーネントを作成する事で、型推論が効くようになります。
また、今までのmethods
、data
、ライフサイクルフック
などは全て、defineComponent
に渡すオブジェクトのsetup()
関数内で宣言します。
<template>
...
</template>
<script lang="ts">
import { defineComponent } from "@vue/composition-api";
export default defineComponent({
setup() {
// ここにリアクティブなデータ、関数を定義
}
})
</script>
リアクティブなプロパティの宣言 ref, reactive
今までのdata()
に相当するリアクティブなプロパティは、setup()
内でref()
またはreactive()
で宣言します。
(正確には、reactive
はvue.observable()
相当)
ref
は今回のRFCで新しく出た概念で、toRefs
や、isRef
など新しいメソッドが追加されています。
<template>
で使う値・関数は必ず、setup()
のreturnでオブジェクトとして返す ことを忘れないで下さい。
今までと異なり冗長な気もしますが、スコープが狭まるので個人的には良いと思っています。
以下、messageOne
とmessageTwo
はどちらもリアクティブな値です。
サンプルではreactive, refともに、ジェネリクスで型注釈加えていますが、記載しなくても型推論が効きます。
<template>
<div>
<h2>{{ state.messageOne }}</h2>
<h2>{{ messageTwo }}</h2>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from "@vue/composition-api";
export default defineComponent({
setup() {
const state = reactive<{ messageOne: string }>({
messageOne: "Hello"
});
const messageTwo = ref<string>("こんにちは");
return {
state,
messageTwo
};
}
});
</script>
ここで気になるのが、reactive()
と、ref()
の使い分けですが、ドキュメントによるとどちらの動作も理解した上で使い分けることが良いと記載されています。
私自身まだ、あまり良く理解できてないので、また後で別記事でまとめたいです。
コンポーネント間での値の受け渡し props, emit
props
はdefineComponent()
に渡すオブジェクト内で、props
プロパティとして宣言します。
今までと同様に、props
に対してtypeや、default、requiredなどの制約も設定できます。
型推論を効かせたい場合は、別に型を定義して、setup()
の引数としてprops
を渡す際に型アノテーションをつけます。
type Props = {
message: string;
};
export default defineComponent({
props: {
message: {
type: String,
default: "default Value"
}
},
setup(props: Props) {
props.message //string型として型推論される
}
})
emit
はsetup()
に渡す第2引数context
のメソッドとして使用できます。
型定義は SetupContext
です。他にも今まで this
に対して呼んでいた slot
や parent
, root
なども,context
から呼ぶことができます。
setup(props: Props, context: SetupContext) {
const emitSample = () => {
context.emit("emit-sample", "サンプル");
};
}
以下、実際に props
と emit
を使ったサンプルです。
子コンポーネントのボタンで、親から受け取った値を変形し、emit
で伝播させています。
<template>
<div>
<button @click="upperCaseMessage">To upper case</button>
</div>
</template>
<script lang="ts">
import { defineComponent, SetupContext } from "@vue/composition-api";
type Props = {
message: string;
};
export default defineComponent({
props: {
message: {
type: String,
default: "default Value"
}
},
setup(props: Props, context: SetupContext) {
const upperCaseMessage = () => {
context.emit("change-message", props.message.toUpperCase());
};
return {
upperCaseMessage
};
}
});
</script>
<template>
<div>
<h1>{{ state.message }}</h1>
<ChildComponent
:message="state.message"
@change-message="changeMessage"
></ChildComponent>
</div>
</template>
<script lang="ts">
import ChildComponent from "@/components/ChildComponent.vue";
import { defineComponent, reactive } from "@vue/composition-api";
export default defineComponent({
components: {
ChildComponent
},
setup() {
const state = reactive({
message: "Hello"
});
const changeMessage = (message: string) => {
state.message = message;
};
return {
state,
changeMessage
};
}
});
</script>
ライフサイクルフック
vueのライフサイクルに合わせて処理を実行するライフサイクルフックは、setup()
で、onXXX
の形式で設定できます。
import { onMounted, onUpdated, onUnmounted } from 'vue'
const MyComponent = {
setup() {
onMounted(() => {
console.log('mounted!')
})
onUpdated(() => {
console.log('updated!')
})
onUnmounted(() => {
console.log('unmounted!')
})
}
}
以下前のインターフェイスとの対応表です。
created系は、setup関数に内包されたことに注意してください。
Vue 2.x | Compostion API |
---|---|
beforeCreate | - |
created | - |
beforeMount | onBeforeMount |
beforeUpdate | onBeforeUpdate |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
errorCaptured | onErrorCaptured |
その他新たにonRenderTracked
, onRenderTriggered
も追加されているようです。(まだ未確認)
おわりに
以上、Vue Composition APIの簡単な紹介でした。
デコレータを多用するClass Componentよりだいぶ分かりやすいインターフェースになった気がします。
defineComponent
を使えば、型注釈ほぼ必要なく自然に型推論が効くのも良いです。
まだまだ、検討中ですが採用されて、より使いやすいVueになると良いですね。
参考
公式ドキュメント
分かりやすく情報がまとまっているのでオススメです。