Composition APIを色々触ってみたので使い方等をメモしておきます📝
Composition APIとは
Compostion APIとはVue.js 3.0から導入される予定のAPIです✨
Composition API: a set of additive, function-based APIs that allow flexible composition of component logic. https://composition-api.vuejs.org/#summary
概要は上記の通り、コンポーネントのロジックをfunctionベースの記載できるようなAPIとなっており、 公式ドキュメントに記載されたComposition APIが導入されたモチベーションは下記のようなものみたいです。
Logic Reuse & Code Organization
Better Type Inference
https://composition-api.vuejs.org/#summary
Composition APIを使うとコードの再利用とTypeScriptのサポートが受けやすくなるようです👀
※ちなみに従来の記載方法はOptions APIと呼ばれているようです。
次から実際の使い方を軽く見ていきます。
簡単な使い方
install
Vue.js 3はまだ正式にリリースされているわけではないので、今回はnpm packageとして切り出されたComposition APIを使っていきます📦
npm install --save-prod @vue/composition-api
有効化
Vue.useを使ってCompostion APIを有効化します⚡
import Vue from "vue"; import VueCompositionApi from "@vue/composition-api"; Vue.use(VueCompositionApi);
Componentの定義
Componentを定義するためにはdefineComponentを使用します。
<template> <div /> </template> <script lang="ts"> import Vue from "vue"; import VueCompositionApi, { defineComponent } from "@vue/composition-api"; Vue.use(VueCompositionApi); export default defineComponent({ name: "Sample", }); </script> <style></style>
data
dataを使う場合はreactiveを使用します⚡
setup内でreactiveの引数にobjectを継承した方を持つ値を渡すことでリアクティブな属性を定義できます。
dataの型を定義するにはreactiveのジェネリック型として任意の型を渡してあげます。
そしてsetupでreturn { state }として値を返してあげます。
※従来のようにtemplate内で{{message}}と呼び出したい場合はreturn { ...toRefs(state) }としてあげます。toRefsをつけないとリアクティブでなくなってしまう💦
<template> <div>{{ state.message }}</div> </template> <script lang="ts"> import Vue from "vue"; import VueCompositionApi, { defineComponent, reactive } from "@vue/composition-api"; Vue.use(VueCompositionApi); type State = { message: string }; export default defineComponent({ name: "Sample", setup() { const state = reactive<State>({ message: "" }); return { state }; }, }); </script> <style></style>
computed
computedを使う場合はcomputedを使用します⚡
setup内でcomputedの引数に関数を渡して上げることで定義することが出来ます。
そしてsetupでreturn { state, strongMessage}としてcomputedで定義したものを合わせて返してあげます。
<template>
<div>
<p>{{ state.message }}</p>
<p>{{ strongMessage }}</p>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import VueCompositionApi, { defineComponent, reactive, computed } from "@vue/composition-api";
Vue.use(VueCompositionApi);
type State = { message: string };
export default defineComponent({
name: "Sample",
setup() {
const state = reactive<State>({ message: "" });
const strongMessage = computed(() => state.message.toUpperCase());
return {
state,
strongMessage,
};
},
});
methods
methodsは単純に関数を定義して、setupでreturnするオブジェクトに定義した関数を入れてあげます。
<template>
<div>
<p>{{ state.message }}</p>
<p>{{ strongMessage }}</p>
<p>{{ weakMessage(state.message) }}</p>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import VueCompositionApi, { defineComponent, reactive, computed } from "@vue/composition-api";
Vue.use(VueCompositionApi);
type State = { message: string };
export default defineComponent({
name: "Sample",
setup() {
const state = reactive<State>({ message: "" });
const strongMessage = computed(() => state.message.toUpperCase());
const weakMessage = (message: string) => message.toLowerCase();
return {
state,
strongMessage,
weakMessage,
};
},
});
</script>
<style></style>
props
propsを使う場合は従来どおりpropsを定義したあとにsetupの引数を定義することで使用出来ます。
propsの型を定義するにはsetupの引数に型を定義してあげます。
<template>
<div>
<p>{{ state.message }}</p>
<p>{{ strongMessage }}</p>
<p>{{ weakMessage(state.message) }}</p>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import VueCompositionApi, { defineComponent, reactive, computed } from "@vue/composition-api";
Vue.use(VueCompositionApi);
type State = { message: string };
type Props = { initializeMessage: string };
export default defineComponent({
name: "Sample",
props: {
initializeMessage: {
type: String,
default: "",
},
},
setup(props: Props) {
const state = reactive<State>({ message: "" });
const strongMessage = computed(() => state.message.toUpperCase());
const weakMessage = (message: string) => message.toLowerCase();
return {
state,
strongMessage,
weakMessage,
};
},
});
</script>
<style></style>
emit
emitを使うにはsetupの第2引数を定義して使用します。
あとはthis.$emitの代わりにcontext.emitを使用してあげます。
<template>
<div>
<p>{{ state.message }}</p>
<p>{{ strongMessage }}</p>
<p>{{ weakMessage(state.message) }}</p>
<button @click="handleOnOK">
OK
</button>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import VueCompositionApi, { defineComponent, reactive, computed, SetupContext } from "@vue/composition-api";
Vue.use(VueCompositionApi);
type State = { message: string };
type Props = { initializeMessage: string };
export default defineComponent({
name: "Sample",
props: {
initializeMessage: {
type: String,
default: "",
},
},
setup(props: Props, context: SetupContext) {
const state = reactive<State>({ message: "" });
const strongMessage = computed(() => state.message.toUpperCase());
const weakMessage = (message: string) => message.toLowerCase();
const handleOnOK = () => {
context.emit("ok");
};
return {
state,
strongMessage,
weakMessage,
handleOnOK,
};
},
});
</script>
<style></style>
Options APIからComposition APIへの書き換え
実際に適当なComponentを書き換えたみたものを一応のせておきます。
Options API
<template> <div class="ts-counter"> <button @click="decrement(1)"> - </button> {{ count }} <button @click="increment(1)"> + </button> </div> </template> <script lang="ts"> import Vue from "vue"; interface Data { count: number; } export default Vue.extend({ props: { initCount: { type: Number, default: 0, }, }, data(): Data { return { count: this.initCount, }; }, methods: { decrement(num: number) { this.count -= num; }, increment(num: number) { this.count += num; }, }, }); </script> <style lang="scss" scoped> .ts-counter { color: blue; } </style>
Composition API
<template> <div class="ts-counter"> <button @click="decrement(1)"> - </button> {{ count }} <button @click="increment(1)"> + </button> </div> </template> <script lang="ts"> import Vue from "vue"; import VueCompositionApi, { defineComponent, reactive, toRefs } from "@vue/composition-api"; Vue.use(VueCompositionApi); interface Data { count: number; } interface Props { initCount: number; } export default defineComponent({ props: { initCount: { type: Number, default: 0, }, }, setup(props: Props) { const state = reactive<Data>({ count: props.initCount }); const increment = (n: number) => { state.count += n; }; const decrement = (n: number) => { state.count -= n; }; return { ...toRefs(state), increment, decrement, }; }, }); </script> <style lang="scss" scoped> .ts-counter { color: blue; } </style>
おわりに
Composition APIを使っていろいろやってみたのですが、最初のモチベーション部分のコードの再利用等はまだ大規模なフロントエンドを経験したことが無いので、ちょっとわからない部分も多かったのですが、タイプスクリプトのサポートの方はthisに依存するコードがなくなって非常に快適になったように感じました。
Option APIのほうがComponentのデータとロジックがプロパティで別れていてClassぽくて分かりやすいかなと個人的には思っていたのですが、Composition APIも書きやすいですね✨