「其の壱」とは書いたものの、其の弐があるかわからないが、ひとまず Vue2 から Vue3 へ移行するときの肝を示しておく。
Vue2 から Vue3 の大きな変更点は、Options API から Composition API によって関数の使い方が変更になったということなのだが、あいにく Vue2 の経験の浅い私としては、どちらでも構わない。2年程前から Vue.js を教えていたときは Vue2 であって、去年の途中から Vue3 が盛り上がってきて、今年に至っては Vue3 を使わざるを得ない≒教えざるを得ない状況になっている。
新人にとっては、Vue2 だろうと、Vue3 だろうと関係ないのだが、いざ配属された後では既存の Vue2 を修正する機会が出てくるだろう。あるいは Vue2 から Vue3 に変更するしないといけないかもしれない。さらに言えば、現状では Vue3 の情報(特に日本語の情報)が少なく、インターネットで検索する限り Vue2 の情報がでてきてしまう。古めの Vue2 の情報に引っ掛からないようにするためには、Vue2 の書き方から Vue3 へ自らが書き換えられるようにならないといけない。
Vue3 の参考書
既に、Vue3 の参考書はいくつかでいるのだが、どうもしっくりこなかった。
やむなく、「基礎から学ぶ Vue.js」 https://www.amazon.co.jp/dp/B08JYTSY4T を使っていたのだが、つい5月に技術評論社から「Vue.jsポケットリファレンス」 https://www.amazon.co.jp/dp/B0C26WFHFX が出版されたので急遽これを使っている。
「~ポケットリファレンス」のほうは、Vue3.js を網羅している。今回は、通常のポケットリファレンスとは違って、テクニックの部分よりも基本的なところに紙面を割いている。この部分がちょうど新人研修に向いている。
移行する画面
テスト的に、現在新人研修で使おうとしている画面を Vue2 から Vue3 に移行してみよう。実際のところは、既に Vue3 で作った画面を Vue2 形式に移植しているので、Vue2 特有の関数の使い方になっていないかもしれないが、書き方のスタイルとしては、3年ほど前に私が業務で使った書き方に揃えてある。
画面にリストが表示されて、項目をクリックすると詳細が表示される。一覧や詳細はいちいち Web API を呼び出すようにしてある。
本来ならばコンポーネント化して props と $emit を使ったほうがいいのだが、ここでは Vue2 から Vue3 移行への雰囲気を味わうために省いている。後日、 props と $emit を使ったパターンはやってみようと思う。
template
実は Vue2 でも Vue3 でも template は全く同じものが使える。移行前の Vue2 でどれだけトリッキーなことをやっているかという度合いにもよるのだが、素直な画面であれば変更は必要はない。とはいえ、実際のところはアニメーションやキーイベントなどを細かい操作が入ってい来るから、Vue3 に変更になった時に変更されたイベントには対応しないといけないだろう。
<template>
<div>
<h1>Mos Main by vue2</h1>
<div>
<ul>
<li v-for="it in items" :key="it.id">
{{ it.id }} : <a @click="onClickItem(it)">{{ it.title }}</a>
</li>
</ul>
</div>
<hr />
<div>
<table class="table">
<tr>
<td>id: </td><td>{{ cur.id }}</td>
</tr>
<tr>
<td>title: </td><td>{{ cur.title }}</td>
</tr>
<tr>
<td>image: </td><td>{{ cur.image }}</td>
</tr>
<tr>
<td>作成日時: </td><td>{{ cur.created_at }}</td>
</tr>
<tr>
<td>更新日時: </td><td>{{ cur.updated_at }}</td>
</tr>
</table>
</div>
</div>
</template>
template からは項目をクリックしたときに onClickItem メソッドを呼び出して、下のほうにある cur.id などが変更されるようにする。items がコレクションで、cur がカレントアイテムということになる。
Vue2 のコード
Vue2 で書くとこんな風になる。MosMain.vue というファイルの script となる。
import axios from 'axios'
export default {
name: 'MosMain',
props: {
},
data() {
return {
items: [], // カテゴリ一覧
cur: {}, // 選択したカテゴリ
}
},
mounted() {
axios.get('http://localhost:8000/api/category')
.then ( response => {
console.log( response )
this.items = response.data
})
},
methods: {
getCategoryById( id )
{
axios.get(`http://localhost:8000/api/category/${id}`)
.then ( response => {
console.log( response )
this.cur = response.data
})
},
onClickItem( item )
{
console.log( "onClickItem " + item.title )
this.getCategoryById( item.id )
},
}
}
- items や cur は、data の return にする
- 画面を表示するときに mounted 中で記述する。リストを this.items にいれておく
- template からのイベントを methods の中に記述する。methods の中の関数を呼び出すときは this を付ける
- 全体を export default { … } で囲って、公開しておく。これを App.vue から使う。
書き方に慣れれば、 props, data, mounted, methods の中に順序よく関数やデータを書くことになる。算出プロパティ(computed)とか監視プロパティ(watch)も同じように分類される。
が、慣れればこの書き方でうまくいくのだが、最初のうちは無理矢理型にはめられている気分であまりよくない。後で解るのだが、それぞれのブロックは、vue.js からのフックするときの情報となっているので、Java の EJB みたいな感じになっている。
Vue3 のコード
この Vue2 のコードを Vue3 に書き換えてみる(実際は、Vue3 から Vue2 を書き起こしている)。
コード的には TypeScript で書いているので、型を指定するために Category クラスを作っているが、概ね Vue2 のコードと機能的には変わらない。
import { ref, onMounted } from 'vue'
import axios from 'axios'
/**
* カテゴリのクラス
*/
class Category {
id: number
title: string
category: string
image: string
created_at: string | null
updated_at: string | null
is_delete: boolean
}
// カテゴリ一覧
const items = ref([] as Category[])
// 選択したカテゴリ
const cur = ref({} as Category)
/**
* カテゴリ一覧を取得する
*/
function getCategories() {
axios.get('http://localhost:8000/api/category')
.then ( response => {
console.log( response )
items.value = response.data
})
}
/**
* ひとつのカテゴリを取得する
*/
function getCategoryById( id: number ) {
axios.get(`http://localhost:8000/api/category/${id}`)
.then ( response => {
console.log( response )
cur.value = response.data
})
}
/**
* ひとつのカテゴリを表示する
* @param item 選択したカテゴリ
*/
function onClickItem( item : Category) {
console.log( "onClickItem " + item.title )
getCategoryById( item.id )
}
// ページ表示時に、カテゴリ一覧を取得する
onMounted(()=>{
getCategories()
})
- 画面へ表示する場合は、ref か reactive を使う
- 呼び出す関数は function で記述しておく。
- 画面から呼び出される onClickItem も function で記述しておく
- 画面の初期表示では onMouted にラムダ式を渡す
Vue2 の書き方で items, cur のところが、items.value, cur.value を使うところが一番分かりづらいと思う。リアクティブという仕組みを使って画面に値変更を通知するのだが、実は Vue2 では items, cur が仮想 DOM を使って通知されていたものが、Vue3 になってリアクティブという形でむき出しになってしまった形のため、こうなっている。
リアクティブ自体は、C# では MVVM パターン、Rx という形で10年以上前から使われているものなので、C# や Java 界隈では知っていれば知っているというパターンなんだけど、Vue3 で出て来たので混乱するというパターンだろう。ちなみに「~ポケットリファレンス」では、MVVM パターンと一緒に解説が付けられている。
見た目上、items.value のほうに真の値が入っていて、items には getter/setter で来るんである、と考えればよい。ちょうど算出/監視プロパティの両方が同時に入っているイメージだ。
何故、Vue3 にこのリアクティブの部分がむき出しになったのかと考えるに、Fultter の Stateありと Stateless のコンポーネントにちなんでではないかと思う。Fullter の場合、画面内の変更がない場合は Stateless で作ることが多い。画面内で変更がないので表示が早いためだ。この部分を「関数型の画面表示」ということもあるが、つまりは、State(状態)を持つと仮想 DOM の扱いで負担が大きいのだろう。なので、画像や説明を表示するだけのコンポーネントであれば、ref や reactive を使わずに、状態変更なしで軽い形で表示できる(のではないか?)のが Vue3 の特徴と類推される。
あと、Vue 3.0 の頃では setup に色々と詰め込まなければ駄目だったが(これはかなり非道い仕様だと思う)、Vue 3.1 からは scrupt setup を使うことで、上記のような素直なコードが書ける。というか、setup の中身がそのまま書かれただけなのだが、読みやすのは確かである。
Vue2 の mounted のブロックが、Vue3 では onMounted となっている。算出/監視プロパティの書き方も同じようにラムダ式を渡す形になる。つまり、Vue2 はあらかじめ mounted というブロックに強制的に書かなければいけなかったが、Vue3 ではコードを書く側で onMounted を呼び出して能動的に登録することができる。データ設定の向きの違いであるけれども、私としては Vue3 のほうが、縛られない感じがして気分がいい。まあ、気分の問題だけど。
関数の部分だが、次のようにラムダ式を使っても書ける。
const onClickItem = ( item : Category ) => {
console.log( "onClickItem " + item.title )
getCategoryById( item.id )
}
私としては、変数や定数は const、関数は function にしたほうが検索性が高いと思うのだが、このように const で揃えてある実例も多い。「~ポケットリファレンス」この const で統一されている。
Vue2 と Vue3 のコーディングスタイルの違い
Vue2 のがちがちにフォーマットされたスタイルから、Vue3 のちょっと自由に書けるスタイルになったわけだが、C# のコンポーネント(クラス)の場合には、MVVM パターンとイベント登録をすればよいわけで、Vue3 の書き方がのほうが、素直にクラス設計が出来そうだがどうだろうか?ルールの部分が少なくて済むという利点がある。
参考先
Vue3の衰退を招いたのはとCompositionAPIかもしれない という考察 – Qiita https://qiita.com/fruitriin/items/81691ce68cf3678f3bda