Vue.js 2.x から 3.0 へ
Vue.jsの開発ロードマップ によると、2020 年 Q2 での公式リリースが予定されています。
β版もリリースされ、だんだんメジャーアップデートに近づいてきた感じがしますね。
Vue.js 3.0 への アップデートに関する記事をチェックしていると、かなり記述方法が変更されそうだな、、という印象を受けました。
かつ記述方法が JS 寄りになった印象もあり、ライトに Vue.js を使用していた方は 3.0 へのアップデートに抵抗がある方も少なくないのではないでしょうか?
自分はそこで抵抗感を持ってしまい、このタイトルの思考に行き着きました。
ただこのセリフを吐いた者がこの後どうなってしまうかは、おそらく皆さんご存知のことかと思います。
悲しい結末を迎えないためにも、しっかりアップデート内容を確認し、追従していきたいところです。
アップデートでの変更点は?
- Composition API
- フラグメント / ポータル
- TypeScripts のフルサポート
などが発表されています。
これらに関しては、Vue.js のコミッターである kazupon さんの解説資料
で一望することができます。
本記事では、この中でもアップデートの中心となりそうな Composition API について確認していきます。
そもそも Composition ってどういう意味?
構成(すること)、組み立て(ること)、合成、混成、組み立てられたもの、構成物、合成物、混合物、(合成)成分、作文1
単純に辞書からの引用ですが、 文字のみから推察するに Vue.js の構成、組み立て方(つまりコードの記述方法自体ですかね。。)に変更を与える機能実装のように思えます。
2.x 系の記述方法はどうなる?
前述しましたが、自分は Composition API の導入により、かなり記述方法が変更されそうだな、、という印象を受けました。
今後 Vue.js を使っていくにあたって、2.x の data や methods などを使用した書き方(Options API ベースの書き方と言うようです)は使えなくなるのでは?、という不安をもたれた方も少なくないのではないでしょうか。
まずこの点について言及しておくと、Vue.js 3.0 では、既存の API と併用 が可能なようです。(削除予定、非推奨となっている物は除きます。)
ただしアップデートによって追加される Composition API は従来の記法である Options API より先に解決されるため、併用する場合は Options API で定義されたプロパティには Composition API からはアクセスできないようです。
Usage Alongside Existing API
The Composition API can be used alongside the existing options-based API.
・ The Composition API is resolved before 2.x options (data, computed & methods) and will have no access to the properties defined by those options.
・ Properties returned from setup() will be exposed on this and will be accessible inside 2.x options.
少し話が逸れますが、Vue.js の開発ロードマップには以下のように記述されています。
Q: As a new user, should I start with Vue 2 now or wait for 3.0?
・ If you are just starting to learn the framework, you should start with Vue 2 now, since Vue 3 does not involve dramatic re-designs and the vast majority of your Vue 2 knowledge will still apply for Vue 3. There's no point in waiting if you are just learning.
新規に Vue.js を使用する場合 2.x 系を使用するか 3.0 を待つ方が良いのか、という話ですが、2.x 系の知識の大部分は 3.0 でも変わらず適用可能なので、新たに Vue.js を学習するユーザーは 2.x 系の記法から始めるべき、ということのようです。
Composition API 導入のモチベーション
Composition API の RFC ページ には、下のような比較図がみられます。アップデートに興味がある方であれば、一度は見たことがあるのではないでしょうか。
Options API が従来の記述方法、Composition API が Vue.js 3.0 で新たに導入される記述方法ですね。
ぱっと見て思いました。
「文字小さくてよく分からないけど、何をどう色分けしてるの? Composition API ではなんとなく色が統一されて、綺麗にはなってるみたいだけど。。」
Options API は、data や methods ごとにまとめられた記述ですので、ロジックを中心にコードを辿った時、断片的に散らばったものになります。
これがもし複数の機能を持つ、粒度の大きなコンポーネントだとどうでしょうか?
「この methods の機能で使われている変数は data の中にあって、computed の方にも記述されていて、、」 と、一つの機能を編集・追加するのにもかなりコード間のジャンプが必要になってしまいます。
また、どのデータがどこで利用されているのか、コンポーネントがどんな機能をもっているのかなど、見通しが悪くなってきそうです。
色の散らばった Options API の図が、この状態ですね。
これに対して Composition API は、コンポーネントの機能ごとに関数を定義し、その中でデータの定義から機能実装までをまとめて記述することができます。
結果、色の整理された図のようにロジックごとにまとまったコードでの記述となり、機能の追加や改修などが進めやすくなる、ということのようです。
Composition API への書き換え
サンプルを辿りつつ、実際に記述方法を確認していきます。
Vue 2.6.11 の環境を Vue CLI で構築し、 Composition API を使用するためのプラグイン@vue/composition-api
を依存関係に追加します。
また、main.js にも VueCompositionAPI を使用するための記述を追加します。
$ vue create __projectname__
$ yarn add @vue/composition-api
import Vue from 'vue'
import App from './App.vue'
import VueCompositionAPI from '@vue/composition-api' // 追加
Vue.use(VueCompositionAPI) // 追加
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
書き換えていくコードがこちら。
ボタンクリックで count をインクリメントしていく、よくあるやつですね。
computed の記述も確認したいので、count の二倍を返す doubleCount を記述しています。
<template>
<div id="app">
<h1>Vue 3.0 Composition API</h1>
<button @click="increment">Click</button>
<p>count: {{ this.count }}</p>
<p>doubleCount: {{ doubleCount }}</p>
</div>
</template>
<script>
export default {
data: function() {
return {
count: 0
}
},
methods: {
increment: function() {
this.count++
}
},
computed: {
doubleCount: function () {
return this.count * 2
}
}
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
border: 3px solid #2c3e50;
}
</style>
では書き換えていきます。
Composition API では、setup という関数の中で Options API での基本的な記述を行うことができ、以下のように書き換えることが可能です。
<template>
<div id="app">
<h1>Vue 3.0 Composition API</h1>
<button @click="increment">Click</button>
<p>count: {{ count }}</p>
<p>doubleCount: {{ doubleCount }}</p>
</div>
</template>
<script>
import { ref, computed } from '@vue/composition-api'
export default {
setup() {
// data での定義
let count = ref(0)
// methods での定義
const increment = () => {
count.value += 1
}
// computed での定義
const doubleCount = computed(() => {
return count.value * 2
})
return {
count,
increment,
doubleCount
}
}
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
border: 3px solid #2c3e50;
}
</style>
setup 関数を使用した記法
Composition API での記法は、新たに追加された setup 関数 が中心となります。この記法には、Options API と比較して大きな変更点が二つあります。
- 使用する Vue.js の機能を import する必要がある
上記のサンプルでは、setup内の記述で ref
および computed
の機能を使用しています。(ref については後述します)
Composition API では computed など Vue.js 独自の記法は import で読み込みが必要になります。
また、こちらも後述しますが、ライフサイクルメソッドに関しても import して記述する必要あります。
- template で 使用する値、関数は return する必要がある
Composition API では、template 内で使用したいものデータや関数は setup 関数内から return しておく必要があるようです。
data はどうなる?
// data での定義
let count = ref(0)
Composition API ではリアクティブなデータを reactive()
、またはref()
で定義します。
ざっくり分けておくと、オブジェクトをリアクティブにしたい場合は reactive 、プリミティブなデータの場合は ref を使用した定義が可能なようです。どちらを利用するかはケースバイケースのようで、設計と併せて今後も議論されていくことかと思います。
こちらに関しては以下の記事が個人的にわかりやすく、とても参考になりました。
Vue.jsのcomposition-apiにおける"ref"と"reactive"って何が違うの?
ref で定義したデータは RefImpl
というオブジェクトでラップされ、定義したデータは RefImpl.value
に格納されます。
そのため、ref で定義したデータを操作する場合は .value を付与して操作する必要があります。
methods はどうなる?
// methods での定義
const increment = () => {
count.value += 1
}
increment は単純な関数として定義され、setup 関数内で methods でラップせずに記述することができます。
count 変数は ref で定義されていますので、前述したように .value を付与して変更を行う必要があります。
computed はどうなる?
// computed での定義
const doubleCount = computed(() => {
return count.value * 2
})
computed で定義していたものは、computed() の中に関数を記述していきます。
computed の import と return を忘れずに記述しておけば、問題なく使用できそうです。
ライフサイクルフックはどうなる?
上記サンプルでは使用していませんが、ライフサイクルフックを使用した記述も setup 関数内で記述することができます。
setup 関数内でライフサイクルフックを記述する場合、以下のように変更されるようです。(beforeCreate, created は setup 関数 に統合されるようです。)
Vue 2.x での記法 | Vue 3.0 での記法 |
---|---|
beforeCreate | setup |
created | setup |
beforeMount | onBeforeMount |
mounted | onMounted |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
errorCaptured | onErrorCaptured |
こちらも <script>
タグ内のみサンプルを記述してみます。
<script>
import { ref, computed, onMounted } from '@vue/composition-api'
export default {
setup() {
// data での定義
let count = ref(0)
// methods での定義
const increment = () => {
count.value += 1
}
// computed での定義
const doubleCount = computed(() => {
return count.value * 2
})
// マウント時のライフサイクルフック
onMounted(() => {
console.log('mounted!')
})
return {
count,
increment,
doubleCount
}
}
};
</script>
import で使用するライフサイクルメソッドを読み込み、setup 関数内で記述することで使用できます。
とりあえず書き換えてみたものの、正直あまり利点が分からない
正直なところ、computed やライフサイクルフックが setup 関数内に移動することで import などの記述量も増えたように感じ、その上でメンテナブルになったかというとそうでもない気がします。
この記述方法だと、機能が増えていくと結局 setup 関数内が肥大化していきそうな感じもしますし、あまりメリットが感じられないようにも思えます。
なので、もう一段階記述方法を変更していきます。
インクリメンタルの機能を setup 関数外に記述し、setup 関数内から呼び出す形に変更してみます。
function を setup 関数外に記述する
useIncrementNumber を export default の外側で記述し、setup 関数内から呼び出す形に変更してみます。
<script>
import { ref, computed } from "@vue/composition-api";
export default {
setup() {
const { count, increment, doubleCount } = useIncrementNumber();
return {
count,
increment,
doubleCount
}
},
};
const useIncrementNumber = () => {
// data での定義
let count = ref(0);
// methods での定義
const increment = () => {
count.value += 1;
};
// computed での定義
const doubleCount = computed(() => {
return count.value * 2;
});
return {
count,
increment,
doubleCount,
};
}
</script>
定義した関数内および setup 内で return する必要があるものの、機能ごとの切り分けはこの方法で行うことができそうです。
今回は一つしか関数を記述していませんが、粒度が大きく複数の機能を持つコンポーネントになると、template 内で使用されている値が setup 関数の return 内にまとまりますし、function の追加や変更時の影響範囲も見やすくなりそうです。
まとめ
- Vue.js 3.0 でも基本的に従来の記述方法は使用可能
- Composition API は新たに導入される setup 関数を使用した記述方法
- 機能ごとにまとめられるので、粒度の大きいコンポーネントだと見通しがよくなりそう
個人的には、小規模のアプリケーション開発であれば Composition API 使用した記述方法は少し冗長な書き方では、という気もします。Options API での記述によるデータや関数ごとのまとまりにも良さがありますし、プロジェクトの大きさによってどう使用していくかを考えた上で使用していくのが良いかもしれないですね。