仕事でもプライベートでもVue2しか書かず、Vue2一筋でVue3を食わず嫌いして勉強を先延ばしにしていたのですが「そろそろ勉強せんとな...」ということで最近やっと手をつけました。しかし最終的には「Vue3超イイネ!」となって嬉しかったので、学習した際のメモを元にチュートリアル記事にしてみました。
Vue2と比べて何が良いのか、目玉機能のCompositionAPIについてまとめましたので、同じくVue3の学習を始めたVue2好きの同志のお役に立てると幸いです😊
Vue3
2016年のVue2リリースから4年ぶりのメジャーアップデートとなったVue3は、2020年9月にリリースされました。余談ですが、リリース名はOne Pieceでした!Vueの生みの親、Evan Youはアニメ好きとして知られています。
Vue2からの改良点
容量の軽量化
Vueは容量が小さいことが特徴の一つとして挙げられますが、Vue3ではこの強みが更に改良されています。
Vue2では~23KBほどあった本体容量は、Vue3では約1/2の10KBへの軽量化に成功しています。TreeShaking(使用されていないコードのバンドルを除外する機能)の導入と、グローバルAPIとヘルパーの大部分をESモジュールベースに実装し直したことが大幅なサイズ削減に繋がりました。とにかく軽くなったのです。
パフォーマンスの向上
Vue3では、仮想DOMを利用した描画ロジックとレンダリング処理の見直しがされました。これにより、Vue2からスクリプト実行にかかる時間とメモリの使用量を1/2に削減することに成功しました。ノードツリーの中で、どのノードを再レンダリングするかを判断する処理などが大幅にアップグレードされたみたいです。とにかく早くなったのです。
TypeScriptのサポート
TypeScriptとの相性はVue2の大きな課題でした。それを解決する為に、Vue3ではVueの実装をTypeScriptベースで全て書き直されています。これにより、より精度の高い型推論が実現します。
CompositionAPI(setupオプションの登場)
Vue3で追加された最大の機能です。
今までは、以下のようにdata
,methods
,computed
,watch
といったオプションをそれぞれで定義していました。この記法をOptionsAPIと呼びます。特徴としては、メソッドはmethods
内に、ゲッターはcomputed
内にといった感じできっちりとフレームワークのルールに沿ってコードを書く必要がありました。普段Vue2を利用している方には見慣れた書き方だと思います。
export default {
data () {
return {
firstName: 'Evan',
lastName: 'You'
};
},
computed: {
fullName () => `${this.firstName} ${this.lastName}`
},
methods: {
sayHello () {
console.log(`Hello, ${this.fullName}!`);
}
}
};
そこに、Vue3からsetup
という新人オプションが追加されました。これこそがCompositionAPIをCompositionAPIたらしめる超重要機能なのです!
この新人は恐ろしく優秀で、先輩の仕事を全て1人でこなすことができます。今まで使用していたdata
,methods
,computed
などのオプションを全てsetup
内で表現することが可能なのです。次項からそれぞれの書き方を説明しますが、上記のプログラムは以下のように書き換えることができます。
setup
の登場により、今までのオプションが全て必要なくなりました。
+ import { ref, computed } from 'vue';
export default {
+ setup () {
+ let firstName = ref('Evan');
+ let lastName = ref('You');
+
+ const fullName = computed(() => {
+ return `${firstName.value} ${lastName.value}`
+ });
+
+ const sayHello = () => {
+ console.log(`Hello, ${fullName}!`)
+ };
+
+ return {
+ firstName,
+ firstName,
+ fullName,
+ sayHello
+ };
+ },
- data () {
- return {
- firstName: 'Evan',
- lastName: 'You'
- };
- },
- computed: {
- fullName () => `${this.firstName} ${this.lastName}`
- },
- methods: {
- sayHello () {
- console.log(`Hello, ${this.fullName}!`);
- }
- }
};
また、Vue3ではCompositionAPIは強制されるものではありません。OptionsAPIで記述することも可能です。
CompositionAPI
script setup
setup()
の登場により、data
,methods
,computed
などのオプションが不要になりました。つまり、CompositionAPIでスクリプトを記述する場合、基本的には以下のようにexport default
の中にはsetup()
しか書かなくてOKということです。
<script>
export default {
setup () {
// 実際のコード
return {
// template内で使用するプロパティ
}
}
};
</script>
setup()
しか書かないのに、上のような雛形をいちいち書くのは面倒なので、Vue3(3.2以降)ではscript setup
がサポートされました。これにより、script
タグの中には、setup()
の中身に書きたいことのみ記述することができるようになりました。
また、script setup
を使用しない場合はtemplate
内で使用するプロパティをsetup()
の返り値としてリターンする必要がありましたが、script setup
では、この冗長な処理を省略することができます。
こちらの方がscript
内がより簡潔になり可読性が上がるので、基本的にはscript setup
を使用することをお勧めします。
<script setup>
// 実際のコード
</script>
尚、以下サンプルコードは全てscript setup
を使用したものになります。
data
ref()
またはreactive()
を使用することでリアクティブなデータを表現します。
ref()
は、オブジェクト以外の値をリアクティブにする際に使用します。注意点として、ref
を使用して定義された変数の値には、value
プロパティを用いてアクセスする必要があります。template
内で参照する際は、.value
は必要ありません。
reactive()
は、オブジェクトをリアクティブにする際に使用します。こちらはデータを参照/更新する際に.value
を付与する必要はありません。また、reactive()
で定義したオブジェクトから分割代入した値に関しては、リアクティブ性が失われる点に注意してください。
<template>
<div>
<button @click="countUp()">Count up</button>
<p>Count: {{ count }}</p>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
const person = reactive({
firstName: 'Evan',
lastName: 'You'
});
let count = ref(0);
const countUp = () => {
console.log(person.firstName);
count.value++;
};
</script>
『ref()
は、オブジェクト以外の値をリアクティブにする際に使用します』と紹介しましたが、厳密にはref()
を使用してオブジェクトをリアクティブにすることも可能ではあります。ですが、特定のプロパティにアクセスする際に{変数名}.value.{アクセスするプロパティ}
という書き方をする必要があります。これは少し冗長なので、オブジェクトをリアクティブにする際はreactive()
を使用することが推奨されています。
methods
通常の関数を定義する形で表現します。
<template>
<button v-on:click="sayHello()">Say hello</button>
</template>
<script setup>
const sayHello = () => {
console.log('Hello!');
};
</script>
computed
computed()
を使用することで表現します。引数として渡されたコールバック関数が実処理になります。
<template>
<div>
<p>FullName: {{ fullName }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const firstName = ref('Evan');
const lastName = ref('You');
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});
</script>
getterとsetterを定義する場合は以下のように記述します。
<script setup>
import { ref, computed } from 'vue';
const firstName = ref('Evan');
const lastName = ref('You');
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`;
},
set(newVal) {
[firstName.value, lastName.value] = newVal.split(' ')
}
});
</script>
また、以下のように通常の関数を定義することでも、computedのゲッターとしての役割を再現をするこができますが、computedの特徴であるキャッシュの恩恵を受けることができません(computedのキャッシュについては、こちらの記事を参照してください)。基本的には、算出プロパティにはcomputed()
を使用しましょう。
const fullName = () => {
return `${firstName.value} ${lastName.value}`;
};
props
defineProps()
を使用することで表現します。
オプションを指定しない場合は、配列形式で名前のみ定義することができます。オプションを指定する場合には、オブジェクト形式でそれぞれのpropsの名前をキーにして、型、必須フラグ、デフォルト値、バリデーターなどを指定することができます。
基本的には、変数props
にdefineProps()
の返り値を代入することpropsw定義し、propsにアクセスする際はprops.isDisabled
のようにアクセスします。
<template>
<button :disabled="props.isDisabled">Button</button>
</template>
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
firstName: {
type: String,
required: true,
default: 'Evan',
validator (val) {
return val.length > 0;
}
},
isDisabled: {
type: Boolean,
required: true,
default: false
}
});
</script>
<script setup>
import { defineProps } from 'vue';
const props = defineProps([ 'firstName', 'isDisabled' ]);
</script>
watch
watch()
またはwatchEffetct()
を使用することで表現します。
watch()
を使用する場合、第一引数に監視対象のプロパティ(ref
またはreactive
によって定義されたリアクティブな値)、第二引数に監視対象に変更があった場合に実行するコールバック関数を指定します。コールバック関数の引数には、変更後の値、変更前の値が渡されます。
watchEffetct()
を使用する場合、監視対象は指定しません。引数にはコールバック関数を渡します。このコールバック関数内は、実処理内で使用されているリアクティブな値が変更されたタイミングで発火されます。特徴として、初期レンダリング時に一度コールバック関数が実行されます。
<script setup>
import { ref, watch, watchEffect } from 'vue';
let name = ref('Evan');
let count = ref(0);
// count変更時に発火
watch(count, (newVal, oldVal) => {
console.log(`"count"が${oldVal}から${newVal}に変更されました!`);
});
// 初期レンダリング時 & count変更時 & name変更時に発火
watchEffect(() => {
console.log(`count: ${count.value}, name: ${name.value}`);
});
</script>
props
を監視対象にする場合は、toRefs
を使用してref
に変更してあげる必要があります。
<script setup>
import { watch, toRefs } from 'vue';
interface Props {
someProps: boolean;
}
const props = defineProps<Props>();
const { someProps } = toRefs(props);
// count変更時に発火
watch(someProps, (newVal, oldVal) => {
console.log(`propsの"someProps"が${oldVal}から${newVal}に変更されました!`);
});
</script>
components
OptionsAPIでは外部コンポーネントをインポートして使用する場合、インポート&components
への追加が必要でしたが、CompositionAPIではインポートのみで外部コンポーネントが使用可能です。
個人的に「インポート&components
への追加」という手順はなかなかの二度手間だと思っていたので、嬉しいアップデートでした。
<template>
<Button/>
</template>
<script setup>
import Button from './components/Button.vue';
</script>
ライフサイクルフック
Vue2(OptionsAPI) | Vue3(CompositionAPI) | 実行タイミング |
---|---|---|
beforeCreate |
setup内で記述 | |
created |
setup内で記述 | |
beforeMount |
onBeforeMount(()=> {}) |
コンポーネントがマウントされる直前 |
mounted |
onMounted(()=> {}) |
コンポーネントがマウントされた後 |
beforeUpdate |
onBeforeUpdate(()=> {}) |
コンポーネントがリアクティブな状態変更によりDOMツリーを更新しようとする直前 |
updated |
onUpdated(()=> {}) |
コンポーネントがリアクティブな状態変更によりDOMツリーを更新した後 |
beforeUnmount |
onBeforeUnmount(()=> {}) |
コンポーネントインスタンスがアンマウントされる直前 |
unmounted |
onUnmounted(()=> {}) |
コンポーネントがアンマウントされた後 |
必要に応じてインポートして使用します。実行タイミング
に記載された状態をフックし、それぞれのメソッドのコールバック関数が実行されます。上記は代表的なものになりますので、その他のライフサイクルフックは公式ドキュメントを参照してください。
beforeCreated
、created
に関しては特別にフック関数を定義する必要がなくなりました。setup内のプログラムは画面遷移時、最初に上から順に実行されるので、今までbeforeCreated
、created
で行っていた処理はsetupの一番上に記述することで表現可能になりました。
コンポーザブル関数による再利用性の向上
Vue2では、再利用可能なデータやメソッドを定義する際にmixin
がよく使用されていました。しかしmixin
には名前空間の共有によるプロパティ名のコンフリクトや、どのプロパティがmixinのものなのかが分かりにくいなどの問題がありました。(他にもmixin
が良くないとされる理由がこちらの記事で分かりやすく説明されていました)
このmixin
問題がCompositionAPIにより解決されています。
CompositionAPIでは、以下のように再利用可能な処理を定義します。変数や関数を色々定義したものをひとまとめにして返す関数を定義、エクスポートします。そして使用する側からは、この関数から必要なものを分割代入で取得して使用するといった感じです。関数名は先頭にuse~
を付与するのが一般的です。この関数はコンポーザブル関数と呼びます。(公式ではComposable functionと紹介されています。いい感じの訳が見つからない...)
export const useFormatHelper = () => {
const fullName = (name) => {
return `${name.firstName} ${name.lastName}`;
};
return { fullName }; // 公開する変数,関数をリターン
}
定義したコンポーザブル関数をコンポーネントから使用します。今回定義した関数useFormatHelper
をインポートして、その実行結果からコンポーネント内で使用するものを分割代入で取り込み、使用します。
<template>
<div>
<p>{{ fullName(name) }}</p>
<button @click="sayHello()">Say hello</button>
</div>
</template>
<script setup>
import { useFormatHelper } from '../utils/formatHelper.js';
const name = {
firstName: 'Evan',
lastName: 'You'
};
const { fullName } = useFormatHelper(); // ヘルパーから何を使用しているか分かり易い / 名前のコンフリクトが起こらない(起こる場合はエラーになる)
const sayHello = () => {
console.log(`Hello, ${fullName(name)}!`);
};
</script>
分割代入により、どの関数/変数が外部から取り込んだものなのかが分かり易くなっています。
そして関数名や変数名が既にコンポーネント内で存在する場合はIdentifier 'fullName' has already been declared.
というエラーが出力されます。
mixinが抱えていた問題は、コンポーザブル関数を使用することで解決することができます。
CompositionAPIの良点
柔軟性&可読性の向上
Vue2(OptionsAPI)では、データやメソッドを記述する場所が決まっていた為、それぞれ機能毎のロジック/プロパティの責務が散らばるという問題がありました。
しかしsetup
オプションの登場でメソッドやデータを記述する場所の制限が無くなったことにより、ロジック/プロパティの責務をそれぞれに分けて固めることができるようになりました。これは可読性を向上させ、保守性にも繋がります。
Vue3アプリケーションの作成方法
Vue2までは、vue create {PROJECT_NAME}
というVueCLIを使用したアプリケーションの作成が一般的でしたが、Vue3からは、アプリケーションの作成にはViteというビルドツールの使用が標準となりました。
Viteは従来のWebpackなどのビルドツールと比べ、pre-bundleという「必要な時に必要な分だけプログラムを読み込む」という方法により、プログラムの規模に関わらず常に開発サーバーの起動&更新が高速であることが特徴です。(Viteに関する詳しい解説はこちらの記事)
以下コマンドを実行することで、Viteでアプリケーションを作成することができます。
npm create vite@latest
コマンドを実行すると、以下のように対話形式のCLIが起動します。Vueのアプリケーションを作成する場合は、アプリケーションの名前
、使用するフレームワーク
、使用する言語
が問われるのでそれぞれ入力/選択をしてください。選択が完了すると、{プロジェクト名}/
ディレクトリが生成され、ディレクトリ配下にアプリケーションのプログラムが生成されます。
> ? Project name: › {プロジェクト名}
> ? Select a framework: › Vue
> ? Select a variant: › JavaScript または TypeScript
作成されたディレクトリに移動し、npm install
を実行してください。アプリケーションを動かす為に必要なパッケージが一括インストールされます。
cd {プロジェクト名}
npm install
インストールが完了したら、開発サーバーを起動するコマンドnpm run dev
を実行してください。npmスクリプトにより、Viteで開発サーバーを起動するViteCLIコマンド(vite
)が実行されます。
npm run dev
正常に開発サーバーの起動が完了すると、以下のようなログが出力されます。Local:
に表示されたURLにブラウザでアクセスすると、作成したアプリケーションを確認することができます。アプリケーションの作成ができました!👏
> vite-test-app@0.0.0 dev
> vite
VITE v4.1.4 ready in 194 ms
➜ Local: http://127.0.0.1:5173/
➜ Network: use --host to expose
➜ press h to show help
最後までご覧いただき、ありがとうございました!