# Nuxt Composition APIでv2との書き方の違いを確認した
Composition API
を使うことになったので、今までの書き方がComposition API
ではどう書けばいいのか確認した。
1つのコンポーネント内での違い(data, computed, methods, watch, created, mounted)と、親子のコンポーネント間の違い(v-model, prop, emit, sync)をコードを書いて確認している。
プロジェクトでNuxtを使うことが多いので、nuxt-app
でアプリケーションを初期化し、@nuxtjs/composition-api
を入れて動かした。
# 下準備
npm init nuxt-app
でNuxtアプリケーションを作成した。
cssフレームワークやlint、formatterはお好みでいれる。
別途@nuxtjs/composition-api
をインストールし、nuxt.config.js
のbuildModules
に設定する。
$ npm init nuxt-app composition-api-example
$ cd composition-api-example
$ gibo dump Node VisualStudioCode >> .gitignore
$ npm run dev
$ npm install @nuxtjs/composition-api --save
nuxt.config.js
{
buildModules: [
'@nuxtjs/composition-api',
// 他...
]
}
# 1つのコンポーネント内での違い data, computed, methods, watch, created, mounted
今まではdata
プロパティやmethods
、created
のようなライフサイクルフックなどそれぞれ分けて定義していたが、Composition API
ではすべてsetup
関数の中で定義する。
# data → ref, reactive
data
はComposition API
でref
あるいはreactive
で表現される。
ref
はプリミティブな値を管理し、reactive
はオブジェクトや配列を管理する。
そのため、reactive
の方が今までの使い方に近い。
ただし、ref
にオブジェクトや配列を渡すと、内部でreactive
が呼ばれるため問題なく使える。
次のコードはdata
をreactive
で管理し、title
をref
で管理している。
setTimeout
でdata
やtitle
の値が変わったら、テンプレートの値も変わることを確認している。
<template>
<div>
<h1>{{ title }}</h1>
<ul>
<li v-for="u in data.users" :key="u.id">{{ u.id }} {{ u.name }}</li>
</ul>
</div>
</template>
<script lang="js">
import { defineComponent, reactive, ref } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const data = reactive({
users: [
{ id: 1, name: '加藤かな' },
{ id: 2, name: '田中紘一' },
{ id: 3, name: '山田太郎' },
],
})
const title = ref("タイトル")
setTimeout(() => {
data.users.push({ id: 4, name: '新藤誠' })
title.value = "タイトルが変更できること"
}, 1000);
return {
data,
title,
}
},
})
</script>
# computed → computed
computed
(算出プロパティ)はsetup
関数のなかで呼び出す。
今まではthis.users
のようにthis
を経由してもとになる値を参照していたが、
Composition API
ではsetup
関数内に定義された変数をそのまま参照する。
次のコードはdata.users
の件数をもとにuserNum
というユーザー件数を算出して表示できるようにしている。
<template>
<div>
<p>ユーザー件数: {{ userNum }}</p>
</div>
</template>
<script lang="js">
import { defineComponent, reactive, computed } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const data = reactive({
users: [
{ id: 1, name: '加藤かな' },
{ id: 2, name: '田中紘一' },
{ id: 3, name: '山田太郎' },
],
})
const userNum = computed(() => data.users.length)
return {
data,
userNum,
}
},
})
</script>
# methods → 普通の関数
methods
はcomputed
のようなVue
独自の決まりにしたがって書く必要はなく普通の関数として書く。
次のコードは「ユーザー追加」ボタンを押すとdata.users
にユーザを追加するaddUser
関数を定義している。
<template>
<div>
<form @submit.prevent>
<div>
<label for="name">お名前</label>
<input id="name" type="text" v-model="data.form.name">
</div>
<div>
<button @click="addUser">ユーザー追加</button>
</div>
</form>
<div style="margin-top:16px;">
<p>ユーザー件数: {{ userNum }}</p>
<ul>
<li v-for="u in data.users" :key="u.id">{{ u.id }} {{ u.name }}</li>
</ul>
</div>
</div>
</template>
<script lang="js">
import { defineComponent, reactive, computed, ref } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const data = reactive({
form: {
name: '',
},
users: [
{ id: 1, name: '加藤かな' },
{ id: 2, name: '田中紘一' },
{ id: 3, name: '山田太郎' },
],
})
const userNum = computed(() => data.users.length)
const addUser = () => {
const id = Math.max(...data.users.map(u=>u.id)) + 1
data.users.push({id: id, name: data.form.name})
data.form.name = ''
}
return {
data,
addUser,
userNum,
}
},
})
</script>
なお、今までのdata
は1つしかなかったが、Composition API
ではreactive
の部分は1つにしなければいけないというルールはないため、2つ以上定義してもよい。
const data = reactive({
users: [
{ id: 1, name: '加藤かな' },
{ id: 2, name: '田中紘一' },
{ id: 3, name: '山田太郎' },
],
})
const form = reactive({
name: '',
})
# watch → watch, watchEffect
watch
はComposition API
でwatch
あるいはwatchEffect
で定義する。
watch
の方が今までと同じようにプロパティを監視して何らかの処理を実行できる。
一方でwatchEffect
はプロパティを指定せず、第1引数に渡すコールバック関数で使われる値を監視して何らかの処理を実行する。
ここでは明示的にプロパティを指定するwatch
を使う。
次のコードはwatch
内の第1引数で監視するプロパティを指定し、第2引数でプロパティの値が変更された際の値をconsole
に表示している。
第2引数のコールバックは第1引数が変更後の値、第2引数が変更前の値になる。
<template>
<div>
<form @submit.prevent>
<div>
<label for="name">お名前</label>
<input id="name" v-model="form.name" type="text" />
</div>
<div>
<label for="email">email</label>
<input id="email" v-model="form.email" type="text" />
</div>
</form>
</div>
</template>
<script lang="js">
import { defineComponent, reactive, computed, ref, watch } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const form = reactive({
name: '',
email: '',
})
watch(
() => form.name,
(currentName, prevName) => {
console.info('currentName: ', currentName, 'prevName: ', prevName)
},
)
return {
form,
}
},
})
</script>
オブジェクトの各プロパティを監視したい場合は第3引数に{deep: true}
を追加する。
watch(
() => form,
(currentForm, prevForm) => {
console.info('currentForm: ', currentForm, 'prevForm: ', prevForm)
},
{ deep: true},
)
# created → 普通の関数呼び出しとして書く
created
はcomputed
のようなVue
独自の決まりにしたがって書く必要はなく普通の関数として書く。
created
はDOMを参照できないが、setup関数内の変数は参照することができるため、APIによる値の初期化などに使われる。
次のコードはAPIでdata
を初期化するのを模倣して、setTimeout
で3秒後に初期化されることを確認している。
<template>
<div>
<div style="margin-top: 16px">
<p>ユーザー件数: {{ userNum }}</p>
<ul>
<li v-for="u in data.users" :key="u.id">{{ u.id }} {{ u.name }}</li>
</ul>
</div>
</div>
</template>
<script lang="js">
import { defineComponent, reactive, computed } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const data = reactive({
users: [],
})
const userNum = computed(() => data.users.length)
// created ... DOMにさわれることが保証されてない。APIからデータ取得する処理などを書く
setTimeout(() => {
data.users.push(...[
{ id: 1, name: '加藤かな' },
{ id: 2, name: '田中紘一' },
{ id: 3, name: '山田太郎' },
])
}, 3000);
return {
data,
userNum,
}
},
})
</script>
# mounted → onMounted
mounted
はComposition API
ではon
がつき、onMounted
になった。
onMounted
はDOMを参照できる。
DOMの参照は、今まではthis.$refs.emailInput
のようにthis.$refs
で参照していた。
しかし、Composition API
ではref(null)
でsetup
関数内に変数を用意しておいて、
テンプレートでref
によりその変数と紐づけをすることでDOMを参照できる。
次のコードはメールアドレスのinput
タグにフォーカスが当たるようにしている。
<template>
<div>
<form @submit.prevent>
<div>
<label for="email">email</label>
<input ref="emailInput" id="email" type="text" />
</div>
</form>
</div>
</template>
<script lang="js">
import { defineComponent, reactive, onMounted, ref } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const emailInput = ref(null);
onMounted(()=>{
// composition api以前は this.$refs.emailInputのような形で取得していた
emailInput.value.focus()
})
console.info("before mount", emailInput.value)
return {
emailInput,
}
},
})
</script>
# 親子のコンポーネント間の違い - v-model, prop, emit, sync
# props → props
親コンポーネントから子コンポーネントへの渡し方は:users="data.users"
のように渡す。親コンポーネントはComposition API
による違いはない。
子コンポーネントでprops
の値を使って何らかの処理をするにはsetup
の第1引数からprops
を取得して操作するようになっている。
次のコードは親コンポーネントindex.vue
から子コンポーネントUserList.vue
へユーザーの一覧を渡して表示している。
子コンポーネントUserList.vue
ではsetup
関数内でprops
でわたってきたユーザーの一覧からユーザー数を算出している。
親コンポーネントindex.vue
<template>
<div>
<UserList :users="data.users" />
</div>
</template>
<script lang="js">
import { defineComponent, reactive } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const data = reactive({
users: [
{ id: 1, name: '加藤かな' },
{ id: 2, name: '田中紘一' },
{ id: 3, name: '山田太郎' },
],
})
setTimeout(() => {
data.users.push({ id: 4, name: '木下武' })
}, 3000);
return {
data
}
},
})
</script>
子コンポーネントUserList.vue
<template>
<div>
<p>ユーザー件数: {{ userNum }}</p>
<ul>
<li v-for="u in users" :key="u.id">{{ u.id }} {{ u.name }}</li>
</ul>
</div>
</template>
<script lang="js">
import { defineComponent, computed } from '@nuxtjs/composition-api'
export default defineComponent({
props: {
users: {
type: Array,
required: true
}
},
setup(props) {
const userNum = computed(() => props.users.length)
return {
userNum
}
},
})
</script>
# v-model → v-model, this.$emit → context.emit
親コンポーネントから子コンポーネントへ値を渡し、子コンポーネントから親コンポーネントへ変化した値を返すには、親コンポーネントでv-model="値"
のようにする。親コンポーネントはComposition API
による違いはない。
なお、Vueのv3ではv-model
の破壊的変更 (opens new window)があるようだがこちらはふれない。
子コンポーネントではinput
タグのインラインで$emit
する方法と、setup
関数でcontext.emit
する方法がある。前者はComposition API
による違いはない。 後者はsetup
関数の第2引数からcontext
を取得しcontext.emit
する。第2引数をsetup(_, {emit})
のように分割代入して、setup
関数内でemit
単体で扱ってもよい。
次のコードは親コンポーネントindex.vue
から子コンポーネントInputName.vue
へ値を渡し、InputName.vue
内のinput type="text"
でinput
イベントが起こるたびに親コンポーネントへ変化した値を渡している。
v-model
はinputのtypeごとに挙動が違い、ここではinputのtypeがtextの場合を示している。
親コンポーネントindex.vue
<template>
<div>
<form @submit.prevent>
<InputName v-model="form.name" />
</form>
</div>
</template>
<script lang="js">
import { defineComponent, reactive } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const form = reactive({
name: '山田',
})
return {
form
}
},
})
</script>
子コンポーネントInputName.vue
inputタグでインラインに$emitする書き方
<template>
<div>
<label for="name">お名前</label>
<input id="name" :value="value" @input="$emit('input', $event.target.value)" type="text" />
</div>
</template>
<script lang="js">
import { defineComponent, toRefs, isRef } from '@nuxtjs/composition-api'
export default defineComponent({
props: {
value: {
type: String,
required: true
},
},
})
</script>
子コンポーネントInputName.vue
setup関数でcontext.emit
する書き方
<template>
<div>
<label for="name">お名前</label>
<input id="name" :value="value" type="text" @input="updateValue" />
</div>
</template>
<script lang="js">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
props: {
value: {
type: String,
required: true
},
},
setup(_, context) {
const updateValue = (e) => {
context.emit('input', e.target.value)
}
return {
updateValue
}
},
})
</script>
子コンポーネントInputName.vue
setup関数で第2引数を分割代入してemit
単体で扱う書き方(一部抜粋)
setup(_, {emit}) {
const updateValue = (e) => {
emit('input', e.target.value)
}
return {
updateValue
}
},
# .sync → .sync
親コンポーネントから子コンポーネントへ値を渡し、子コンポーネントから親コンポーネントへ変化した値を返すには、親コンポーネントでv-model="値"
とする他に:prop名.sync="値"
とも書ける。
子コンポーネントでは@input="$emit('update:prop名', $event.target.value)"
あるいはsetup
関数でemit('update::prop名', e.target.value)
のように書く。
親コンポーネントindex.vue
<template>
<div>
<form @submit.prevent>
<InputName :value.sync="form.name" />
</form>
</div>
</template>
<script lang="js">
import { defineComponent, reactive } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const form = reactive({
name: '山田',
})
return {
form
}
},
})
</script>
子コンポーネントInputName.vue
inputタグでインラインに$emitする書き方
<template>
<div>
<label for="name">お名前</label>
<input
id="name"
:value="value"
type="text"
@input="$emit('update:value', $event.target.value)"
/>
</div>
</template>
<script lang="js">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
props: {
value: {
type: String,
required: true
},
},
})
</script>
子コンポーネントInputName.vue
setup関数でemitする書き方
<template>
<div>
<label for="name">お名前</label>
<input id="name" :value="value" type="text" @input="updateValue" />
</div>
</template>
<script lang="js">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
props: {
value: {
type: String,
required: true
},
},
setup(_, {emit}) {
const updateValue = (e) => {
emit('update:value', e.target.value)
}
return {
updateValue
}
},
})
</script>
参考
https://v3.vuejs.org/guide/composition-api-introduction.html#why-composition-api (opens new window)