Vue 3.xの新機能、Composition APIを触ってみた
TOC
2020年9月18日にVueのv3.0.0が正式にリリースされました。 そこで搭載された新機能であるComposition APIを、今更ではありますが使ってみたいと思います。
環境構築
Vue3を利用できるようにする方法はCLIを利用するなどいくつかありますが、今回は公式ドキュメントでも取り上げられていたViteというビルドツールを利用して環境構築をしてみたいと思います。
Viteとは
ViteはVue.jsの作者のEvan You氏が開発しているビルドツールです。 Vue.jsだけでなくReact・Preactの環境も構築することができ、またTypeScriptにも対応させることができます。 特徴としてはES モジュールをそのままインポートするため、バンドル処理がなく高速に動作するとのことです。
インストール
では早速ターミナルからコマンドを入力しインストールを行います。
$ npm init @vitejs/app sample-project入力後、どのtemplateを利用するか選択するする項目が出るので、今回はvue-tsを選択します。
Scaffolding project in /Users/takumi/Desktop/vue3/sample-project...
? Select a template: …
vanilla
vue
❯ vue-ts
react
react-ts
preact
preact-ts
lit-element
lit-element-ts完了後、ディレクトリに移動し、パッケージのインストールを行い開発環境を起動します。
$ cd sample-project
$ npm install
$ npm run dev
Vue3を扱う環境が完成しました!
Composition APIとは
では実際にコードを書きながらComposition APIがどのようなものか体験していきましょう。
公式サイトによるとComposition APIは、
Introducing the Composition API: a set of additive, function-based APIs that allow flexible composition of component logic
https://vue-composition-api-rfc.netlify.com/#summary
「コンポーネント ロジックの柔軟な構成を可能にする、関数ベースの相加的な API」との事で、TypeScriptを使用した時の型推論の改善や、コードの可読性・共通化がしやすくなるそうです。
setupコンポーネントオプション
Composition APIで新しく追加されたコンポーネントオプションで、インスタンスが作成される前に呼び出され、コンポーネントのエントリーポイントとなるオプションです。
インスタンス前に生成されるのでこの中にthisはありません。
つまり、渡されてきたpropsを除いたコンポーネント内で定義されているcomputedやmethodsにはアクセスする事ができないということになります。
では実際にコンポーネントにsetupを使ってみましょう。
まずsrc/componentsディレクトリにSample.vueを作成し、src/App.vueから呼び出します。
<template>
<Sample />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import Sample from './components/Sample.vue'
export default defineComponent({
name: 'App',
components: {
Sample
}
})
</script>コンポーネントを作成
はじめにdefineComponentを使いコンポーネントを作成します。 defineComponentの中身的は渡されたオブジェクトをただ返すだけのようですが、defineComponentからコンポーネントを作成する事で自然に型推論が効くようになります。 TypeScriptを利用していない場合は使用する必要はありません。
<script lang="ts">
import { defineComponent} from "vue";
export default defineComponent({
setup() {},
})
</script>リアクティブプロパティの定義
setup内でのリアクティブプロパティの定義は、ref関数とreactive関数を使い定義を行います。
この2つの関数は登録する値によって使い分けを行います。
- ref ... プリミティブ値
- reactive ... オブジェクト
定義した値をtemplateで利用する場合は必ずreturnをする必要があります。
<template>
<div>
{{ count }}回目の {{ state.text }}!!
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from "vue";
export default defineComponent({
setup() {
const count = ref<number>(1)
const state = reactive<{ text: string }>({
text: 'Vue3'
})
return {
count,
state
}
},
})
</script>refを使う際の注意点
ref関数で定義したリアクティブプロパティをsetup関数内で変更する時は、property.valueとする必要があります。
先ほど定義した変数countの数をインクリメントするメソッドを作成し、挙動を確認してみましょう。
<template>
<div>
{{ count }}回目の {{ state.text }}!!
</div>
<button @click="countIncrement">+</button>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from "vue";
export default defineComponent({
setup() {
let count = ref<number>(1)
const state = reactive<{ text: string }>({
text: 'Vue3'
})
const countIncrement = () => {
count.value++
}
return {
count,
state,
countIncrement
}
},
})
</script>コンソールでcountを確認してみると、RefImplと言うオブジェクトでラップされている事が確認できます。
propsを扱う
propsはdefineComponentに渡すオブジェクト内に設定します。 propsの中身はVue2と変わらずtypeやdefaultなどを定義することができます。
コードをわかりやすくする為、先程まで記述していたcount、 state、countIncrementは削除しておきます。
まずは、親コンポーネントからmessageという文字列を、propsMessageとして子コンポーネントに渡します。
<template>
<Sample :propsMessage="message" />
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import Sample from './components/Sample.vue'
export default defineComponent({
name: 'App',
components: {
Sample
},
setup() {
let message = ref<string>('Propsを利用する')
return {
message
}
}
})
</script>子コンポーネントのpropsプロパティに、親コンポーネントから渡されたpropsMessageを定義します。
export default defineComponent({
props: {
propsMessage: {
type: String,
required: true,
default: 'デフォルト値',
}
},
setup() {setup()内でpropsの値を利用する時は、setup()の第一引数で受け取ります。
<template>
<div>
{{ propsMessage }}
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from "vue";
export default defineComponent({
props: {
propsMessage: {
type: String,
required: true,
default: 'デフォルト値',
}
},
setup(props) {
console.log(props.propsMessage) // Propsを利用する
},
})
</script>emitを扱う
emitを利用する場合はsetup()の第二引数に渡されるcontextから呼び出します。
今回は親コンポーネントのmessageを変更するchange-messageというemitを作成し、実際に動かしてみましょう。
<template>
<div>
{{propsMessage}}
</div>
<button @click="changeMessage">テキストを変更</button>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
props: {
propsMessage: {
type: String,
required: true,
default: 'デフォルト値',
}
},
setup(props, context) {
const changeMessage = () => {
context.emit('change-message', 'emitを利用してテキストを変更しました')
}
return {
changeMessage,
}
},
})
</script>また、Vue3からはpropsのように、コンポーネント内で利用するemitも明示的に宣言をしてあげないと警告が出るようになりました。
宣言の仕方は、emitsオプションに利用するemit名を配列で定義します。
export default defineComponent({
props: {
propsMessage: {
type: String,
required: true,
default: 'デフォルト値',
}
},
emits: ['change-message'], // 追加
setup(props, context) {
const changeMessage = () => {
context.emit('change-message', 'emitを利用してテキストを変更しました')
}
return {
changeMessage,
}
},
})emitで渡された値を親コンポーネント内で受け取ります。
<template>
<Sample :propsMessage="message" @change-message="changeMessage" />
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import Sample from './components/Sample.vue'
export default defineComponent({
name: 'App',
components: {
Sample
},
setup() {
let message = ref<string>('Propsを利用する')
const changeMessage = (emitedMessage: string) => {
message.value = emitedMessage
}
return {
message,
changeMessage
}
}
})
</script>ライフサイクルフック
setup()でのライフサイクルフックは以下のように置き換わります。
| Options API | setup() |
|---|---|
| beforeCreate | - |
| created | - |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeUnmount | onBeforeUnmount |
| unmounted | onUnmounted |
| errorCaptured | onErrorCaptured |
| renderTracked | onRenderTracked |
| renderTriggered | onRenderTriggered |
export default {
setup() {
onMounted(() => {
console.log('コンポーネントがマウント時に実行されます')
})
}
}感想
非常に簡単ではありますが、Composition APIを実際に触って動かしてみました。 setup()内で完結できるようになった為、1つのロジックに対し、data/computed/methodsなど上から下まで読まないといけないということがなくなり、見通しやすいコードが描けるようになるのではないかなと思っています。 まだまだ触りの部分しか調べられていないので、実際にサンプルアプリなどを作ってみて、書き味の違いをもっと体験してみたいなと思いました。
参考
https://v3.vuejs.org/guide/composition-api-introduction.html#setup-component-option https://qiita.com/eyuta/items/9a81a1697f55fbde4bdc https://qiita.com/ryo2132/items/f055679e9974dbc3f977 https://qiita.com/mgr/items/a5e35636d371969e0a4d
