Vue経験者向け Vue3 スタートガイド [実行環境付き]
LAPRAS 社内で開催した Vue3 勉強会の資料を一部内容を変更して公開します。
Vue3 の新機能、Breaking Change に対して Codesandbox でひとつひとつ実行環境を作っています。動作・コードを検証しながら読んでもらえると嬉しいです。
🙋♂️ 対象読者
- Vue2 を Vue3 に移行中 or 移行検討中の人
- 普段 Vue は触らないけど、教養的に Vue3 をキャッチアップしたい人
- Vue3 の機能・注意点を普段フロントエンドを触らないメンバーに説明したい人
🏎️ Vue3 で何が変わる?
- 新たな API の追加
- リアクティブの改善
- Vue2 はObject.definePropertyを利用したリアクティブの実装だったが、Vue3 ではProxy をベースに変更
- パフォーマンスの向上
- 内部実装の変更で、バンドルサイズが低下、パフォーマンスも向上
- ex: LINE MUSIC のパフォーマンスを向上させた Vue3 マイグレーション
- TypeScript 対応の強化
- template 内部での TS 構文の利用が可能に
- 型推論が改善
💫 新たにサポートされた API・構文は?
CSS v-bind
CSS のプロパティに対してリアクティブな値のバインディングが可能になりました
内部的には、CSS 変数の値をリアクティブに更新することで値を書き換えています。Devtools で確認するとおもしろいです。
※ Vue2.7 でも利用可
<template>
<div class="text">hello</div>
</template>
<script setup>
import { ref } from "vue";
const color = ref("red"); // bind対象
</script>
<style>
.text {
color: v-bind(color); /* この値がリアクティブになる */
}
</style>
Teleport
<Teleport>
は、コンポーネントにあるテンプレートの一部を、そのコンポーネントの DOM 階層の外側に存在する DOM ノードへ「テレポート」するものです。
Vue2 でも使えた portal-vue と同等の機能です。新たなスタッキングコンテキストを生成することで z-index での重なり制御を回避できます。
<template>
<div class="app">
<Teleport to="body">
<!-- Teleport内がbody直下に展開される -->
<Modal>teleport</Modal>
</Teleport>
</div>
</template>
Fragments
Fragments を使うことで Vue3 から<template>
直下に同じ階層で複数の要素を配置できるようになりました。不要な wrapper のdiv
を作らなくて済みます。
ただし、fragment を使っている場合、親要素から class などの属性を追加する際に子要素側で、明示的にv-bind="$attrs"
を設定しないと属性が付与されないので注意です。さらに scoped css にしている場合は:deep()
も必要です。
<template>
<div>...</div>
<div v-bind="$attrs">...</div>
<!-- この要素に親要素側で指定した属性が付与される -->
<div>...</div>
</template>
Suspense(Experimental)
<Suspense>
は、コンポーネントツリーの非同期な依存関係を制御するための組み込みコンポーネントです。コンポーネントツリーの下にある複数のネストされた非同期な依存関係が解決されるのを待つ間、ローディング状態をレンダリングできます。
非同期コンポーネントを使う際に有効だが、まだ Experimental な機能で API が変わる可能性もあるので使用は控えたほうが良いかもです。
<Suspense>
<!-- ネストされた非同期な依存関係を持つコンポーネント -->
<Dashboard>
<!-- #fallback スロットでローディング状態を表す -->
<template fallback>
Loading...
</template>
</Suspense>
script setup 構文
単一ファイルコンポーネント(SFC)内で Composition API を使用する際のシンタックスシュガーです。以下のような利点があります。
- ボイラープレートが少なく、より簡潔なコードが書ける
- 純粋な TypeScript を使ってプロパティと発行されたイベントを宣言する機能
- 実行時のパフォーマンスの向上
- IDE で型推論のパフォーマンス向上
※ Vue2.7 でも利用可
<template>
<ChildA />
{{ text }}
<button @click="click">{{ msg }}</button>
<ChildB />
<ChildC />
</template>
<script setup lang="ts">
// 子コンポーネントの定義
import ChildA from "./ChildA.vue";
import ChildB from "./ChildB.vue";
import ChildC from "./ChildC.vue";
// reactiveの定義
const text = ref("");
// propsの定義
const props = withDefaults(defineProps<{ msg?: string }>(), {
msg: "hello",
});
// emitの定義
const emit = defineEmits<{
(e: "change", msg: string): void;
}>();
const click = () => {
emit("change", "ok");
};
</script>
script setup を使わずに書いた場合は...
<template>
<ChildA />
{{ text }}
<button @click="click">{{ msg }}</button>
<ChildB />
<ChildC />
</template>
<script setup lang="ts">
import { defineComponent, ref }
import ChildA from './ChildA.vue'
import ChildB from './ChildB.vue'
import ChildC from './ChildC.vue'
export default defineComponent(() => {
name: "App",
// 子コンポーネントの定義
components: {
ChildA,
ChildB,
ChildC,
},
// propsの定義
props: {
msg: {
type: String,
default: "hello"
}
},
// emitの定義
emits: ['change'],
setup(_, { emit }) {
// reactiveの定義
const text = ref("")
const click = () => {
emit('change', "ok")
}
return {
text,
click,
}
}
})
</script>
Reactive API の追加
リアクティブ周りの API が多数追加されてました。リアクティブの制御で困ったら使ってみても良いかもです。以下一部抜粋。
-
shallowRef()
ref()
の浅いバージョン - customRef() 依存関係の追跡と更新のトリガーを明示的に制御して、カスタマイズされた ref を作成
- toRaw() Vue で作成されたプロキシの元のオブジェクトを返す
- v-memo テンプレートのサブツリーのメモ化
🚨 注意すべき Breaking Changes は?
v-model の仕様変更
Vue3 では v-model の仕様が諸々変更されました。
- v-model を受け取るカスタムコンポーネントの props、event 名が変更
- prop:
value
->modelValue
- event:
input
->update:modelValue
- prop:
-
v-bind.sync
の廃止 -
v-model
の複数定義が可能に -
v-model
のmodifier
が増加
Vue2
Vue3
配列の要素の変更が標準で Watch されない
配列の内部の要素の変更をwatch
で検知しようとしても、検知されなくなりました(配列自体の置き換えは検知する)。配列の要素の変更を検知する場合は、deep
オプションをつけてください。
watch(
reactiveVal,
(newVal, oldVal) => {
console.log(`${oldVal} -> ${newVal}`);
},
{
deep: true, // 配列の要素の変更にはこれが必要
}
);
Array, Object の変更で Vue.set, Vue.remove の利用が不要に
Vue2 では配列や、配列、オブジェクトのキー指定の書き換え・削除がリアクティブにならないという注意点がありましたが、Vue3 では解消され、Vue.set
やVue.remove
が不要になりました。
Vue2
Vue3
deep セレクターの書き方が変更
scoped
環境で子コンポーネントの要素に対してスタイルを当てたい場合のセレクターの指定方法が変更されました。:deep()
で指定する必要があります。
<style scoped>
/* 子コンポーネントの`.b`に適応できる */
.a :deep(.b) {
/* ... */
}
</style>
その他いろいろ
他にもいろいろある。詳細はマイグレーションガイドを確認してください。
-
トランジション機能のクラス名変更
- 古い書き方だと警告もでないので注意
-
v-bind="obj"
は書く位置で動作が変わるように- 以前は、他の属性と被っていると、それで上書きされていた。Vue3 からは、後に書いた属性が優先されるように
-
vm.$listeners は削除
-
$attrs
に統合されました。つまりv-bind="$attrs"
でリスナーも登録される
-
-
vm.$on
、vm.$off
、vm.$once
の削除- 同様のことをするには mitt や tiny-emitterなどのライブラリが必要
-
v-on.native
修飾子の削除-
emits
に定義されていないイベントはすべてネイティブイベントと見做されるように
-
-
ライフサイクルフックの命名変更
-
destroyed
はunmounted
に、beforeDestroy
はbeforeUnmount
に命名変更
-
💬 Q & A
script setup 使う?
コンポーネントの記述方法が代わり、覚えることが増える & Grep で探しにくくなるという懸念はありますが、それ以上に記述量が大幅に減ってコンポーネントの見通しが良くなるという利点のほうが大ききので積極的に使っていきたいです。
ref()と reactive()のどちらを使う?
基本的には ref()
で良いと思います。reactive()
だとリアクティブの消失に意識を向ける必要があるので。もし、独自 store を作りたいケースなどで、reactive を使う場合は、toRef
toRefs
を使って export 時に Ref に変換するのを忘れずに!
参考: vue composition api における props のリアクティブ性について知っておくべきこと
reactive が消失するパターン
const state = reactive({ name: "foo" });
// 1. 分割代入を行うパターン
const { name } = state;
// 2. プロパティを直接参照して代入するパターン
const name = state.name;
// 3. プロパティを直接参照して他の処理に渡すパターン
const { length } = useLength(state.name);
reactive の消失を防ぐには...
const state = reactive({ name: "foo" });
// 1. 分割代入を行うパターン
const { name } = toRefs(state);
// 2. プロパティを直接参照して代入するパターン
const name = toRef(state, "name");
// 3. プロパティを直接参照して他の処理に渡すパターン
const { length } = useLength(toRef(state, "name"));
📔 参考資料
- Vue.js - The Progressive JavaScript Framework | Vue.js
- Vue 3 Migration Guide | Vue 3 Migration Guide
- Special Thanks @Shin2357
おわりに
以上 Vue3 スタートガイドでした。
Vue3 がリリースされてもう 2 年ほど経ちますが、まだまだ Vue2 で開発しているチームも多いと思います。そのようなチームが、いざ Vue3 移行を進める際の説明資料などに使ってもらえたら嬉しいです。
社内勉強会の様子。Codesandbox で実際に編集しながら説明
最後になりますが、LAPRAS ではソフトウェアエンジニアを募集中です!
採用資料
カジュアル面談
Discussion