自分含め、これまで Vue.js 2.x でコードを書いていた人に向けて書きます。
あまり深いところや細かいところまでは掘らずに、Vue.js 2.x で書いてきた基本的な記述をComposition APIで書くために、と言う視点で書いてみました。
目次
- Composition API
- Vue.js 2.xで慣れ親しんだ書き方からの移行
- ロジックの抽出と再利用
Composition API
これまでのVue.jsでは data
computed
methods
などのoptionごとにリアクティブなデータや関数などを書いてきたが(Options API
というらしい)、大きなコンポーネントになってくると必要な機能ごとにコードをまとめた方がいい。そのための関数ベースのAPI群。
Vue.jsのv3から利用可能予定。
v2でもプラグインとして利用可能。
現在はRFCとして仕様を揉んでいるところなのでここに記載の内容は変更される可能性があります。
https://vue-composition-api-rfc.netlify.app/
メリット
- 自由なコードの構成
- ロジックの抽出と再利用
- 型推論の強化
これまでのVue.jsのOptions APIを上書きするのものではなく、上記のメリットを受けられるような大規模なプロジェクトで使うのが良い。
Options APIはそれ自体がコードの規約となるので小・中規模なプロジェクトでとても有用と思う。
setup()
setup
は新しいOptionで、コンポーネント内でComposition API を使うときのエントリーポイントになります。
setup
関数内にリアクティブなデータや算出プロパティ、メソッドなどを定義していきます。
<template>
...
</template>
<script>
import { computed, ref, reactive } from 'vue' // 必要なAPIはimportが必要
export default {
setup() {
//
}
}
</script>
<style>
...
</style>
setup
関数内で使うcomputed
, ref
, reactive
などの関数(後述)はimportしておく必要があります。
import { computed, ref, reactive } from 'vue'
templateで使うものは return { } が必要
template
で使う値や関数はsetup
関数の最後でreturn
しておく必要がある。
下の例でいうとreturnされた count
などは、いままでのOptions APIで言うところの this.count
のような形でぶら下ります。
いちいちこれを書く手間がある一方、template
でどの値/関数が使われているかがわかりやすいというメリットもあります。
<template>
<div>
{{ count }} * 2 = {{ double }}
<button @click="increment">+</button>
</div>
</template>
<script>
import { computed, ref, reactive } from 'vue'
export default {
setup() {
const count = ref(0)
const double = computed(() => {
return count.value * 2
}(
const increment = () => {
count.value++
}
// template で出力するデータはここで渡す必要がある
return { count, double, increment }
}
}
</script>
Options APIからCompositio APIへの移行
これまで慣れ親しんだOptions APIでの書き方をComposition APIで書くときのまとめです。
data
やcomputed
などoptionごとに書いてます。
data
ref()
、またはreactive()
でリアクティブなデータを扱うことができます。
ref()
ref
関数は数値や文字列、booleanなどのプリミティブ値を引数にとり、リアクティブなrefオブジェクトを返す
setup
関数内でリアクティブな値を使う場合は.value
プロパティを参照します。
<template>
<div>
{{ count }}
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0) // リアクティブな値をもつオブジェクトを返す
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
const increment = () => {
count.value++
}
return { count } // return する場合はリアクティブなrefオブジェクトごと返す。 .value は不要
}
}
</script>
reactive()
オブジェクトを引数にとり、リアクティブプロキシーを返します。Vue.js 2.x系のVue.observable()
と等しいようです。
(参考:Proxy - MDN web docs)
The reactive conversion is "deep": it affects all nested properties. In the ES2015 Proxy based implementation, the returned proxy is not equal to the original object. It is recommended to work exclusively with the reactive proxy and avoid relying on the original object.
<template>
<div>
{{ state.count }}
{{ state.code }}
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({
count: 1,
code: 'ready'
}
state.count++ // 2 refオブジェクトと違い.valueを参照しない
return { state }
}
}
</script>
computed
算出プロパティはcomputed
関数を使います。
引数にgetter関数をとり、イミュータブルなref
オブジェクトを返します。
templateで使う場合はもちろんreturn
する必要があります。
import { ref, computed } from 'vue'
export default {
setup() {
const count = ref(2)
count.value++ // 3
const double = computed(() => {
return count.value * 2
})
console.log(double.value) // 6
double.value++ // error
return { double }
}
}
methods
Composition APIのなかでmethods
は単純なJavaScriptの関数として宣言します。
template
内で利用する場合はリアクティブな値と同様、setup
関数の最後にreturn
してあげる必要があります。
<template>
<div>
Count is {{ count }}
<button @click="increment">+</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(1)
// 単純なJSの関数として宣言する
const increment = () => {
count.value++
}
return { count, increment }
}
}
</script>
props
これまで同様、setup
関数の外で指定。setup(props)
のように引数に指定し使う。setupに渡されたpropsオブジェクトはリアクティブであり、
<template>
<div>ID : { id }</div>
</template>
<script>
export default {
props: {
id: string
},
setup(props) {
// setup関数内では props.id で扱うことができる
return { props }
}
}
</script>
ただしprops
オブジェクトからプロパティだけを取り出すとリアクティビティは失われます。
export default {
props: {
id: Number
},
setup({ id }) {
watchEffect(() => {
console.log(`id is: ` + id) // リアクティブではなくなる。
})
}
}
created(), mounted(), updated()
RFCのリファレンスには以下のような対応表があります。
https://vue-composition-api-rfc.netlify.app/api.html#lifecycle-hooks
-
beforeCreate
->setup()
を使う -
created
->setup()
を使う -
beforeMount
->onBeforeMount
-
mounted
->onMounted
-
beforeUpdate
->onBeforeUpdate
-
updated
->onUpdated
-
beforeDestroy
->onBeforeUnmount
-
destroyed
->onUnmounted
-
errorCaptured
->onErrorCaptured
created()
setup
関数はpropの初期値が解決された後、beforeCreated
フックとcreated
フックの前に呼ばれます。
そのためsetup
自体をbeforeCreated
, created
として扱うのがいいようです。
なお、setup
はいままでのcreated
よりも前に解決されるため、
- setup関数内からはこれまでの data() のプロパティやmethodsの関数にアクセスできない
- 逆に
setup
でreturn された値/関数はcreated() やmethodsの中でthis.xxx
の表記でアクセスできる。
となります。
mounted(), updated()
created
, beforeCreated
以外のライフサイクルフック、例えばmounted
, updated
なんかは
onXxxx
の形でimportした上でsetup
関数内でonMounted()
onUpdated()
のように使います。
import { onMounted, onUpdated } from 'vue'
export default {
setup() {
onMounted(() => {
console.log('mounted.')
}
onUpdated(() => {
console.log('updated.')
})
}
}
そのほかのライフサイクルフック
beforeMount()
, beforeUpdate()
, beforeDestroy()
, destroyed()
, errorCaptured()
などのそのほかのLife Cycle Hookも同様に関数をimportしてsetup
内で使います。
import { onBeforeMount, onBeforeUpdate, onBeforeUnmount, onUnmounted, onErrorCaptured } from 'vue'
export default {
setup() {
onBeforeMount(() => { ... }) // = beforeMount()
onBeforeUpdate(() => { ... }) // = beforeUpdate()
onBeforeUnmount(() => { ... }) // = beforeDestroy()
onUnmounted(() => { ... }) // = destroyed()
onErrorCaptured(() => { ... }) // = errorCaptured()
}
}
emit, attrs, slots
emit
, attrs
, slots
は、setup
関数の第二引数に渡されるcontext
オブジェクトを通してアクセス可能です。
export default {
setup(props, context) {
context.attrs
context.slots
context.emit
}
}
this.$refs
公式のDocにあったものではないですが、同様なことはこんな感じでできそう。
<template>
<h1 ref="hogeTitle">タイトル</h1>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
// 空のリアクティブなオブジェクトを作成
const hogeTitle = ref(null)
console.log(hogeTitle.value) // 当然 null。 setup() はcreatedの前、templateが解決される前に実行されるので
onMounted(() => {
// templateが解決され、このタイミングでアクセスできる
console.log(hogeTitle.value) // <h1>タイトル</h1>
})
return { hogeTitle } // これが必要
}
}
</script>
ロジックの抽出と再利用
Composition APIを使うメリットの一つにロジックの抽出とその再利用があります。
いくつかのコンポーネント間で利用されるような機能のロジックや、膨大で複雑なコードの機能を切り出して再利用することができ、見通しがよく効率的なコードが書けます。
ロジックを抽出して再利用する際にも、Composition APIのコード構成に対する非常に高い柔軟性が活きてきます。
以下の例はクリックで増えるカウントに対する機能を抽出しています。
// count.vue
import { ref, computed, onMounted } from 'vue'
export function count() {
const count = ref(0)
const update = (e) => {
count.value++
}
const displayCount = computed(() => {
return count.value >= 10 ? '10+' : count.value
})
onMounted(() => {
window.addEventListener('click', update)
})
return { cont, displayCount }
}
抽出したcount
関数を利用します
import { count } from './count'
export default {
setup() {
const { count, displayCount } = count()
return { count, displayCount }
}
}
終わりに
Compositio APIの基本的な書き方、そしてこれまでのVue.jsに慣れ親しんだ人からの目線での書き方をまとめてみました。
Composition APIにはここで紹介していないAPIがまだあります。
冒頭のメリットでも触れたように、自由なコード構成、型サポートの強化、ロジックの抽出など、特に大規模なプロジェクトでは非常に可能性を感じさせる新機能となることでしょう。
一方、RFCである現在(2020/06)はAPIの追加や破壊的変更がされる可能性があります。
そのため業務での利用はまだ難しいですが、
来るべきVue.js 3.0の正式リリースに備え今から概要を掴んでいくのは良いかなと思います。