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