はじめに
まえがき
フロントエンドは昔少し触ったりあクト!だけ。しかもそれももう数年前、JavaScriptもJS Primerを流し見しただけ・・・。そんなフロントエンド未経験が右往左往しながらVue.jsの理解を3ヵ月深めてきました。その中で学んだ知見は他の人にも役に立つはずと感じ、ここに書き残します。
対象読者
- 最近Vue3.2から触り始めたけれども、イマイチVueがつかめない人
- Vueの学習ソースを探している方
基礎編
OptionsAPIとCompositionAPI
Vue.jsに触れ始めて一番最初に迷ったのは書き出し方法です。
Options API
<script>
export default {
data() {
return {
count: 0
}
}
}
</script>
<template>
<button @click="count++">Count is: {{ count }}</button>
</template>
<style scoped>
button {
font-weight: bold;
}
</style>
Composition API
<script setup>
import { ref, onMounted } from 'vue'
// reactive state
const count = ref(0)
// functions that mutate state and trigger updates
function increment() {
count.value++
}
// lifecycle hooks
onMounted(() => {
console.log(`The initial count is ${count.value}.`)
})
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
ある程度理解してくると「あ、OptionsAPIでメソッド定義していたものをCompositionAPIだとメソッド呼び出しで定義できるんだな」くらいの差に感じますが、最初のときは混乱ばかりしていました。
基本的にブログなどで説明されているVueはOptionsAPIを使われているケースが多く、日本語ドキュメントもOptions APIが主流です。
最近の書籍でやっとComposition APIのsetup文にフォーカスした書籍が発売されています。(自分もコレ最初に読みたかったです・・・)
ぜひこれから入門する方は上のような書籍を参考してComposition APIに触れてみてください。
そして、できる限りドキュメントは英語の方を参照しましょう。
翻訳に時間がかかってしまうという面でやはり日本語ドキュメントに書かれていないことが英語の公式の方だと書かれています。
それに加えて、英語ドキュメントの方はOptions APIとComposition APIの表示を切り替えることができ、どちらも見ることが出来るため「Options APIではこう書くけどComposition APIはどうなんだろう?」といった見方ができます。
見比べることによって理解が深まり「自分はOptions APIを理解できていない」という念から抜け出す事ができ、学習スピードも高まるはずです。
余談ですが、公式ドキュメントの方にはVueの型についての情報も豊富なため非常に参考になります。
RefとReactive
RefとReactiveといったVue.jsの一大コンテンツです。
これらをリアクティブオブジェクトと言い、リアクティブオブジェクトが変更されるとDOMが自動的に更新されます。
refとreactiveにおいて、refはプリミティブ型にしてreactiveはオブジェクトに使おうという話がありますがイマイチ理解が深まりませんでした。
公式ドキュメントを読んでみると、reactive()はオブジェクト型に対してProxyでラップをしリアクティブ性を監視しているものであり、プリミティブ型は保持ができないと書かれています。
おそらくここから上記のような使い分けに繋がったのだと思います。
先にreactiveができ、プリミティブ型をカバーするためにrefが用意された。そんなイメージを自分の中でいだきました。(正しいかはわかりません)
このリアクティブの概念を実際に手を動かし、自分の中で理解をしないとVueではDOM更新タイミングを手足のように操作することが出来ずに苦労してしまいます。(実際めちゃくちゃ苦労しました・・・)
- reactiveはProxyでラップされているので更新するときは中のプロパティを
- refはプリミティブ。ただオブジェクトに対しても使えて、その場合はオブジェクトをそのまま入れ替えることでリアクティブを更新できる
- reactiveなものが更新されたとき、DOMは更新される(描画される)
このような理解を持っておく必要があるでしょう。
Reactive性の消失
Reactiveについて話をしたついでですが、Reactive性が消失するパターンがVueにはいくつかあります。(自分もまだまだ覚えられていないです)
こちらの記事がとても分かりやすく、簡潔にまとまっています。
基本的にpropsを介してしまったりPiniaで直接参照してしまった時にReactive性の消失は発生します。
上記の記事にも書いてあるように、computed
を上手く使うことがpropsから渡されるReactiveの良い扱い方になるかと思います。
現時点で理解ができなくてもReactive性は消失するということだけ頭の片隅に置いておけば、いつか困った時に役立つと思います。
Suspense
実験的機能として扱われているSuspenseですがトップレベルawaitを書けるようになるため非常にコードがスッキリします。
少し分かりにくかった部分としてはトップレベルawaitのコンポーネントが必要ということです。
<Suspense>
<AsyncComponent> // これがトップレベルawaitを書いたコンポーネント
<template #fallback> // トップレベルawaitのあるコンポーネントが解決されない時描画されるコンポーネント
</template>
</Suspense>
最初は気づかずにトップレベルawaitのないコンポーネントで動作を確かめたくて実装し、全然動かずハマってしまいました。
Suspenseを正しく動かすには、コンポーネントは必ず非同期コンポーネントである必要があるため、それも忘れずに頭の片隅においておくと時間が大幅に削減できるかもしれません。
WritableComputed
Computedは非常に強力な機能です。計算されたプロパティがリアクティブな依存関係に基づいてキャッシュされるため、パフォーマンスを大きく向上させてくれます。
そしてこのComputedですがgetterだけではなく、setterも定義することが出来ます。
以下の記事が参考になると思います。
なかなか使用する箇所がパッと思い浮かびにくいのですが、直近で使ったことがある例としては、date-pickerで取った値を別のフォーマットにして使いたい場合に役立ちました。
templateのinput
で取れる形式は決まっています。その形式を元にデータを取得し、WritableComputedでデータを整形して保持して上げれば、いちいち変換用の関数を挟む必要もなくコードがスマートになり保守しやすくなります。
公式のほうにはきちんとベストプラクティスも書かれているため、変な使い方をしてしまって逆につらい状況にならないように気をつけましょう。
setupとonBeforeMount
涙なしでは語れない、ライフサイクルの話になります。
Vueはデータ監視を設定し、テンプレートをコンパイルし、インスタンスをDOMにマウントし、データが変更されたときにDOMを更新する必要があります。
その中でライフサイクルフックというものを使うことで指定したタイミングでコードを実行することが出来ます。
出来ることの幅が広がる反面、時系列が関係してくるコードになってくるため非常に追いにくくなる可能性があります。
用法用量を守りつつ、適切に使っていくことが重要になります。
先程出てきたComposition APIのsetup文ですが、一番最初に実行されます。(厳密に言うとRouter等のが早いですが)
そして次にbeforeMount
が実行されます。これを考えてコードを書いていくと以下のような順が時系列をコードに表すことが出来るため良いかと思います。
<script setup lang='ts'>
// props, emits句
const props = defineProps<{
nyan: number
}>();
const emits = defineEmits<{
(e: 'hoge'): void
}>();
// use句(piniaやcomposableなど)
const xxxStore = useXXXStore();
// reactive句
const count = ref(0);
const hoge = reactive({ data: 'hoge' });
// computed句
const fugafuga = computed(() => count * 2);
// Lifecycle句
onBeforeMount(() => {});
</script>
まずはsetup句にあるもの、次にonBeforeMountということだけ理解しておけばある程度ハマることはないと思います。
自分はなぜだかonBeforeMount
のほうが先にくるものだと思っていて少し難読なコードを書いてしまった思い出があります・・・。
Devtoolを上手く使う
至極当然の話ですが、Devtoolを使いこなすことでデバッグが一段と楽になります。
reactiveな値に入っている数値やPiniaに保存されているstateも確認出来たり、こちら側から特定の数値を入れて動作確認なども出来るため使いこなせば百人力です。
タイムラインが見られるのも大きな利点で、ライブラリごとに(Vue-Router, Pinia, vee-validate)提供されているので変にconsole.debug
を埋め込んで動作順序を検証する必要がなく非常に便利です。
番外編
Pinia
これまででも複数回出ているPiniaですが結構運用に困ります。
自分はVuexのアンチパターンで調べ、Vuexのアンチパターンを避けつつPiniaを使うようにしています。
Piniaにおける細かい話で言うと、getterとactionsの使い分けが最初は理解できていませんでした。
getter
はcomputedRef
で返却されるということを理解してからは使い分けが明確になったなぁという印象なのですが、やはりPiniaをどう扱うといいのかは未だにわかっていません。
ドメインオブジェクトチックに外部から取ってきたオブジェクトを加工なり変換なりするメソッドを実装するPiniaのStoreを作ってみましたが、Actionsとgetterが肥大化してしまい、とても読みにくいコードになってしまいました。(しかもどこから依存されているかが不明瞭になる)
現時点では、Propsのショートカットには使わない・コンポーネント特有のものは持たないという簡素なルールを元に使用しています。
React畑などで経験があればもう少しうまい使い方も思いつくのでしょうが、自分はVueがはじめてしっかりと触れたフロントエンドなのでなかなか苦戦しています。(ぜひ教えて下さい)
コードリーディング
実際にVue3を使って書かれてるそれなりのプロダクトを教えてくれ!と思うかも知れないので、少しだけ自分が参考にしたコードリポジトリを置いておきます。
これらを見る上で一番大切なのは、ベストプラクティスなどないということを念頭に自分のプロジェクトに適した手法・書き方をチームで策定し、失敗したと思ったら「いい記述記事のネタができた」くらいのマインドで次から活かしていくことです。
教材を見るのも手
かなり値段が張るものになるのでオススメはしにくいのですが、推奨のオンラインコースを見るのも1つの手となります。
このへんはよりVueに詳しい方々が作られているため信頼できます。また自分もまだ知らないVueの隠された力や上手な使い方もここから学べると思っています。
Piniaの実践レッスンが現在waiting listに入っているため、こちらが公開されたら1ヵ月だけ会員になってみようかな・・・と考えています。
おわりに
忘れていけないのは、ベストプラクティスなどないということです。
時代によってベストプラクティスは変化します。失敗を恐れずに、「この方法あんまり良くなかったみたい😢」でチームと学びを共有し、チームもまた責めることなく良い方法を探っていく。そんな関係を結ぶのが一番大切だということです。