
九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、前回「Bootstrap v5がどうなるのか調査してみた」という記事を公開しましたが、実は今後のリリースでもうひとつ気になるものがあります。
それは、JavaScript
開発の強力な味方、
「Vue.js」の新バージョン(v3)
です。
Vue 3
については、以前からたびたび情報が出てましたがついにアルファ版が利用できるようになりました。(2020.3.26現在)
そこで
今回は、近い将来リリースされるであろう Vue 3
の使い方をまとめてみることにしました。
ぜひ参考にしていただけると嬉しいです
【追記:2020.09.20】2020年9月18日に正式リリースされ、変更点があったので、記事を加筆・修正しました。
【追記:2020.10.30】「refの使い方が変わった」を追加しました。
「ついに来ました Vue 3
」
目次 [非表示]
- 1 Vueインスタンスの作り方が変わった
- 2 data変数の設定が関数のみになった
- 3 グローバル・コンポーネントの設定方法も変わった
- 4 配列やオブジェクトのバインディングが改善された
- 5 Portalで別の場所にコンテンツを表示できるようになった
- 6 コンポーネントへの「v-model」が複数対応になった
- 7 コンポーネントは1つのタグで囲まなくてよくなった
- 8 Composition APIで大規模なコンポーネントがつくりやすくなった
- 9 refの使い方が変わった
- 10 Suspenseでコンポーネント準備中は代替表示ができるようになった
- 11 filterはなくなった
- 12 TypeScriptをサポートした
- 13 おまけ:新しいVueを試す方法
- 14 おわりに
Vueインスタンスの作り方が変わった
これまで、Vue
アプリをつくる場合このような書き方をしていました。
(HTML)
<div id="app">
<!-- ここに Vueのコンテンツ -->
</div>
(JavaScript)
new Vue({
el: '#app'
});
それが、今回のアップデートからは、createApp()
を使うようになりました。
import { createApp } from 'vue'
createApp({
// ここに各種設定
}).mount('#app');
なお、CDN
を使う場合はこうなります。
(HTML)
<script src="https://unpkg.com/vue@next"></script>
(JavaScript)
Vue.createApp({
// ここに各種設定
}).mount('#app');
data変数の設定が関数のみになった
これまでdata変数
は以下のようにオブジェクトでの指定ができていました。
//
これはVue 3ではうまく行かない例です
data: {
text: 'test'
},
しかし、Vue 3
では必ず関数として指定する必要があります。
// 関数として指定します
data() {
return {
text: 'test'
}
},
もともとコンポーネントやミックスインではこの形にする必要があったので、統一したのかもしれません。
グローバル・コンポーネントの設定方法も変わった
これまでVueコンポーネント
は、特に設定なしでも有効になる「グローバルな」コンポーネント指定は以下のようにしていました。
//
これはVue 3では間違った例です。
Vue.component('v-user', {
props: ['id', 'email', 'password'],
template: `
<div>
<div v-text="id"></div>
<div v-text="email"></div>
<div v-text="password"></div>
</div>
`
});
しかし、Vue 3
からは以下のような設定方法に変更になっています。
// Vue 3ではこう書きます
Vue.createApp({
// 省略
})
.component('v-user', {
props: ['id', 'email', 'password'],
template: `
<div v-text="id"></div>
<div v-text="email"></div>
<div v-text="password"></div>
`
})
.mount('#app');
もしくは、ドットを分けて以下のようにすることもできます。
// ドット部分を分割してもOK
const app = Vue.createApp({
// 省略
});
app.component('v-user', {
// 省略
});
app.mount('#app');
なお、ローカルなコンポーネントはこれまでと同じです。
// ローカルなコンポーネントは同じです
const userComponent = {
props: ['id', 'email', 'password'],
template: `
<div>
<div v-text="id"></div>
<div v-text="email"></div>
<div v-text="password"></div>
</div>
`
};
const app = Vue.createApp({
components: {
'v-user': userComponent
},
// 省略
}).mount('#app');
配列やオブジェクトのバインディングが改善された
これまでのちょっとした弱点だったのですが、data変数
に存在していないキーを追加してもリアルタイムでHTMLに反映されませんでした。
<html>
<body>
<div id="app">
{{ user }}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
new Vue({
el: '#app',
data: {
user: {
id: 1,
email: 'user@example.com',
password: 'secret'
}
},
mounted() {
this.user.age = 25; //
ageは存在しないので、Vue 2では反映されない
}
});
</script>
</body>
</html>
しかし、これがVue 3
では問題なく動くように改善されました。
ご注意: ただし、存在していない
data変数
を追加することはできません。(上のケースで言うと、 this.customer = { .... };
としても、最初にdata変数
として定義していなのでダメです・・・ということになります。 )
また、配列の場合もより直感的なコードを書くことができます。
Vue.createApp({
el: '#app',
data: {
names: ['山田太郎', '佐藤次郎']
},
mounted() {
this.names.push('田中三郎'); //
Vue 3ならリアルタイムに反映される
}
}).mount('#app');
そして、データの削除も簡単になります。
delete this.user.email; //
emailを削除
なお、Vue 2
までのやりかたは次のとおりでした。
Vue.set(this.user, 'age', 25); // Vue 2の追加
Vue.delete(this.user, 'email'); // Vue 2の削除
Portalで別の場所にコンテンツを表示できるようになった
Portal
は新しく追加された機能で、簡単に言うと「別の場所で表示できる」機能です。
例えば、次の例を見てください。
<div id="app">
<!-- Vueのエリア内 -->
<Portal target="#footer">フッターです!</Portal>
</div>
<div id="footer"></div>
実はこの例では、<Portal> .. </Portal>
の場所には何も表示されず、一番下の<div id="footer"> .. </div>
に表示されることになります。
つまり、Vue
が管理するエリア外にコンテンツを「転送」できるというわけです。
しかも、その転送コンテンツにもVue
の動きを加えることができるので、例えば、閉じるボタンで表示を消すことも可能です。
そして、Portal
の使い道として考えられるのが、モーダルやアラート通知です。
コンポーネントへの「v-model」が複数対応になった
これまでは「v-model」はひとつの値だけしかセットできませんでしたが、次のように複数同時にセットできるようになりました。
<UserForm
v-model:id="user.id"
v-model:email="user.email"
v-model:password="user.password"></UserForm>
なお、v-model:
に続く名前はコンポーネントのprops
に一致します。
props: ['id', 'email', 'password']
コンポーネントは1つのタグで囲まなくてよくなった
これまでのVueコンポーネントの制約のひとつに「必ず特定のタグで囲んで1つの要素だけにしないといけない」というものがありましたが、これは気にしなくてよくなりました。
<!--
Vue 3 ではOK -->
<template>
<div>1つ目</div>
<div>2つ目</div>
<div>3つ目</div>
</template>
Composition APIで大規模なコンポーネントがつくりやすくなった
Composition API
は、大規模なコンポーネントをつくる場合や、多人数での開発に向いています。
なぜなら、Composition API
はコードが大量にあっても管理しやすいからです。(逆に言うと、小さなコンポーネントの場合はあまりメリットはありません)
実際の例をみてみましょう
読み込み時にデータ100件を取得し、その中から1ページ10件ずつ表示するコンポーネントです。
<template>
<div v-for="p in filteredPosts">
<div>
ID: {{ p.id }}、タイトル: {{ p.title }}
</div>
</div>
<div>
<button type="button" @click="prevPage">前へ</button>
{{ pagination.page }}ページ目
<button type="button" @click="nextPage">次へ</button>
</div>
</template>
<script>
import { ref, reactive, computed, watch, onMounted } from 'vue'
export default {
setup() {
// data
const posts = ref([]);
const pagination = reactive({
page: 1, // 現在のページ
limit: 10 // 10件ずつ表示
});
// methods
const prevPage = () => {
return pagination.page--;
};
const nextPage = () => {
return pagination.page++;
};
const getPosts = () => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(data => {
posts.value = data;
});
};
// computed
const filteredPosts = computed(() => {
let filteredPosts = [];
const start = (pagination.page - 1) * pagination.limit;
const end = start + pagination.limit;
for(let i = start ; i < end ; i++) {
if(posts.value[i] !== undefined) {
filteredPosts.push(posts.value[i])
}
}
return filteredPosts;
});
// watch
watch(posts, (value, oldValue) => {
console.log('postsが変更されました');
});
// mounted
onMounted(() => {
getPosts();
});
return {
posts,
pagination,
prevPage,
nextPage,
filteredPosts
}
}
}
</script>
もうお気づきかもしれませんが、Composition API
といっても以前の書き方が変わっているだけで、Vue 2
に慣れていればそれほど難しい変更ではないかと思います。
では、メリットは何かというと「コードの分割(共通化)」がしやすいということです。
例えば、pagination
は別のコンポーネントでも使うなら、外部化しておいた方がいいですよね。そんな場合は次のようすることができます。
UserPosts.vue
import pagination from './pagination'
pagination.js
import { reactive } from 'vue'
export default reactive({
page: 1, // 現在のページ
limit: 10 // 10件ずつ表示
});
このようにしておけば、別のコンポーネントで使う場合にすぐ呼び出せますし、なんなら別の開発者も使うことができます。
refの使い方が変わった
前項目の話の細かい部分になるのですが、Vue 2
のref
は個人的によく使う機能だったので詳しくご紹介します。
まず、ref
とはVue
の中で特定の要素(<div>
とか<input>
とかですね)を簡単に取得できる機能で、以前はthis.$refs['*****']
という使い方をしていました。
そして、Vue 3
からはこのref
を使うにはsetup()
内で指定をするように変更になりました。
Vue.createApp({
// 省略
setup() {
return {
emailInput: Vue.ref(null) //
カッコの中身は初期値です。
};
}
}).mount('#app');
※こちらはcdn
を使った場合です。
そして、ターゲットにしたい要素にref="*****"
をつければOKです。
<input type="text" ref="emailInput">
これで、Vue
内でthis.emailInput
が使えるようになります。
this.emailInput.classList = 'test-class'; // <input> タグのCSSクラス名を変更しています
Suspenseでコンポーネント準備中は代替表示ができるようになった
Suspense
を使うと、コンポーネントの準備中、代替コンテンツを表示できるようになりました。
実際の使い方はこちらです。
<Suspense>
<template #default>
<!-- このコンポーネントの準備を待つ -->
<UserPost></UserPost>
</template>
<template #fallback>
読み込み中...
</template>
</Suspense>
この例では、UserPost
の準備ができらたら自動的に表示が切り替わります。
ちなみに、#fallback
の部分が表示されるのはコンポーネントのasync
が解決されるまでの間で、次のようなコードが必要です。
UserPost.vue
<template>
{{ post }}
</template>
<script>
export default {
async setup () {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
id: 1,
title: 'Postタイトル'
});
}, 3000); // 3秒後に実行
});
const post = await promise;
return {
post
}
}
}
</script>
実際にブラウザで確認すると、まず次のような表示になります。
そして3秒後、表示は自動的にこうなります。
filterはなくなった
次のようなfilter
機能はなくなったようです。(ちなみに私はcomputed
派でなのでノーダメージでした)
{{ message | filterA | filterB }}
TypeScriptをサポートした
TypeScript
は、開発者の間で人気のテクノロジーですので、その流れを受けてVue
でも採用されたようです。
おまけ:新しいVueを試す方法
2020.3.26現在、Vue 3
はまだ正式にリリースしていませんが、アルファ版を先取りして試してみることができます。
【追記:2020.9.20】Vue 3は、2020年9月18日に正式リリースされました。
やり方は、まず適当なフォルダで以下のコマンドを実行します。
git clone https://github.com/vuejs/vue-next-webpack-preview.git
そして、/vue-next-webpack-preview
フォルダに移動しパッケージをインストールします。
npm install
これで準備は完了です。
後は、同じ場所で次のコマンドを実行すればブラウザで「http://localhost:8080/」にアクセスできるようになります。
npm run dev
おわりに
ということで、今回はVue
の次期バージョン3の変更点をまとめてみました。
ちなみに、たくさん追加機能や変更点を紹介したのでVue 2
から相当変わっているような感じるかもしれませんが、カンファレンスの動画でも言及されているとおり(13:49
あたりです)Vue 3
になってもそれほど書き換えはしなくてもよさそうです。
また、Vue 3
はファイルサイズが小さくなり高速化もしているので利用者にとってもメリットがあるんじゃないでしょうか。
私も根っからのVue
ファンとして、ドシドシこれからの開発に役立てたいと思います
ぜひ皆さんもチェックしてみてくださいね。(もしかすると、変更点があるかもないので、リリース後をおすすめします)
ではでは〜!
「うーん、正式リリースが待ち遠しい・・・
(この記事を書いてから半年後に正式リリースとなりました)」