📅

Vue初心者が勉強し始めて詰まったところの話🤔

2024/12/23に公開

Vueアドベントカレンダー23日目の記事です🎄

はじめに

こんにちは!
ねぎなす(@neginasu_grid)です。

普段のお仕事ではReactを使っていますが、Vueのコミュニティに関わることが増え、もっと知りたい!と思い勉強を始めました🙌

そこで、今回はVueを勉強し始めて詰まったこと・疑問に思ったことをまとめてみました。

Options APIとComposition APIとは?

まず最初に 「どんなところが違うのか?」 「どちらから勉強すれば良いのか」 と言う疑問を持ちました🤔

Options APIとは

https://ja.vuejs.org/guide/introduction#options-api

https://ja.vuejs.org/guide/introduction#which-to-choose

Vue2まで採用されていた方法で、公式には

Options APIでは、data、methods、mounted といった数々のオプションからなる 1 つのオブジェクトを用いてコンポーネントのロジックを定義します。
これらのオプションによって定義されたプロパティには、コンポーネントのインスタンスを指す this を使って、関数内から次のようにアクセスできます:

Options APIの考え方は、「コンポーネントのインスタンス」(サンプルに見られる this) を中心とするもので、OOP (Object Oriented Programming: オブジェクト指向プログラミング)言語の経験のあるユーザーにとってはクラスベースの心理的モデルによく適合します。
同時に、Options API ではリアクティビティーの細かな部分が抽象化され、各オプションのグループによってコードの構成が整理されるため、初心者にとって分かりやすいモデルでもあります。

と記述されています。

Composition APIとは

https://ja.vuejs.org/guide/extras/composition-api-faq.html#what-is-composition-api

https://ja.vuejs.org/guide/introduction#which-to-choose

Composition APIについて公式では

Composition APIはオプションを宣言する代わりに関数をインポートすることで Vue コンポーネントを書くことができる API セットのことです。
以下に記載する API を含む包括的な用語です:

Composition APIは、リアクティブな状態変数を関数のスコープ内で直接宣言し、複数の関数の組み合わせによって状態を組み立てて複雑な処理を扱おう、という考え方が中心にあります。
より自由度が高い形式であるため、効果的な使い方をするには Vue のリアクティビティーがどのような仕組みで動くのかを理解しておく必要があります。
その代わり、柔軟性が高いことから、さまざまなパターンに沿ってロジックの整理や再利用を強力に進めることができます。

と記述されています。

Options APIとComposition APIの比較

https://ja.vuejs.org/guide/extras/composition-api-faq.html#why-composition-api

公式のOptions APIとComposition APIを比較している画像を見るとよくわかりますが、

Options APIでは名前の通り、そのオプションごとにまとまってコードを記述しています。
コードの構造が決まっており、どこに何の記述がされているのか明確になっているという利点があります。

一方でComposition APIはコードの関心ごとにコードを記述しています。
公式にも

Composition APIの最大の利点は コンポーザブル関数の形式で、クリーンかつ効率的にロジックを再利用できることです。
これは Options API の主要なロジック再利用メカニズムであったミックスインの欠点を全て解決しています。

と記述されているように、ロジックの再利用をする事ができる利点があります。

ミックスインとは?ミックスインの欠点って何?

ミックスインとは

公式ではミックスインについて

ミックスイン (mixin) は、Vue コンポーネントに再利用可能で柔軟性のある機能を持たせるための方法です。
ミックスインオブジェクトは任意のコンポーネントオプションを含むことができます。
コンポーネントがミックスインを使用するとき、ミックスインの全てのオプションはコンポーネント自身のオプションに”混ぜられ”ます。

と記述されています。

実際にミックスインを触ってみる

ミックスインに触れた事がなかったので、実際に下記のように実装してみました👩‍💻

mixin.js
export default {
  data() {
    return {
      message: 'hello! mixin!',
    };
  },
};
App.vue
<script>
import mixin from './mixin/mixin.js';

export default {
  mixins: [mixin],
};
</script>

<template>
  <div>{{ this.message }}</div>
</template>

mixin.js ではOptions APIの形で datamessage を定義し、 App.vue では 先ほどの mixin をimportして、 mixins に登録した上で template 内で呼び出しています。

実行すると下記のようになりました。

実行したい処理を切り出すという考え方は理解したものの、ローカルで明示的に定義していないものを使用しているというところに違和感を感じました😵‍💫

次に mixin を複数使った場合も試してみました。

mixin.js
export default {
  data() {
    return {
      message: 'hello! mixin!',
    };
  },
};
mixin2.js
export default {
  data() {
    return {
      message: 'hello! mixin2!!',
    };
  },
};
mixin3.js
export default {
  data() {
    return {
      message: 'hello! mixin3!!!!',
    };
  },
};
App.vue
<script>
import mixin from './mixin/mixin.js';
import mixin2 from './mixin/mixin2.js';
import mixin3 from './mixin/mixin3.js';

export default {
  mixins: [mixin, mixin2, mixin3],
};
</script>

<template>
  <div>{{ this.message }}</div>
</template>

実行すると以下のように mixin3.jsmessage が表示されました。

このように mixins の中で1番最後に定義されたものが優先されています。

配列の最後の要素が適用されるということは何となくわかりますが、どの mixin に何が定義されているのかぱっと見で判別できないと感じました。

ここでさらに App.vue に同じ message を定義すると、以下のような挙動になります。

App.vue
<script>
import mixin from './mixin/mixin.js';
import mixin2 from './mixin/mixin2.js';
import mixin3 from './mixin/mixin3.js';

export default {
  mixins: [mixin, mixin2, mixin3],
  data() {
    return {
      message: 'hello! local!',
    };
  },
};
</script>

<template>
  <div>{{ this.message }}</div>
</template>

このようにローカルで定義されたものが最も優先され、次に mixins が優先されています。

結論

上記のコードはとても簡単な実装ですが、それだけでも、

  1. 複数ある場合、どこから発生しているのかわからない
  2. 名前の衝突によって上書きされてしまうという特性を考慮しながら使用しなければならない

という点で怖さや辛みを感じました。

大きなプロダクトで使用するのであればもっと複雑になることは容易に想像できるので、これらの欠点を解決したComposition APIの使用を推奨していることに納得です😌

Options APIとComposition APIどちらから勉強すればよい?🤔

公式の見解

公式の どちらを選ぶか? という項目では、

学習が目的の場合は、自分で理解しやすいと思うスタイルをお選びください。
繰り返しますが、中核的な概念の多くは、どちらのスタイルでも共通です。
もう一方のスタイルも、好きなときに後から習得することができます。

プロダクション用途の場合は、以下をおすすめします:

ビルドツールを利用しない予定の場合や、プログレッシブエンハンスメントなどの複雑性の低いシナリオで主に Vue を使う予定の場合は、Options API を選択します。

アプリケーション全体を Vue で構築する予定の場合は、Composition API と単一ファイルコンポーネントの組み合わせを使用します。

と記述されています。

結論

上記を踏まえて、アプリケーションを作れるようになりたいと思ったので、まずはComposition APIを学んで、コードを書くことに慣れたいと思います。

その上で、さらにVue自体を深く理解するためにOptions APIを勉強する方針に決めました🫡

Vue2とVue3の <script><template> の順番が違う?

単一ファイルコンポーネントの書き方について、Vue2とVue3の公式ドキュメントを比較すると、<template><script>の順番が異なることに気づきました。

Vue2ドキュメントの例

https://v2.ja.vuejs.org/v2/guide/single-file-components

<template>
  <p>{{ greeting }} World!</p>
</template>

<script>
module.exports = {
  data: function() {
    return {
      greeting: "Hello"
    };
  }
};
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>

Vue3ドキュメントの例

https://ja.vuejs.org/guide/scaling-up/sfc#introduction

<script setup>
import { ref } from 'vue'
const greeting = ref('Hello World!')
</script>

<template>
  <p class="greeting">{{ greeting }}</p>
</template>

<style>
.greeting {
  color: red;
  font-weight: bold;
}
</style>

Vue2では <template><script> の順番、Vue3では <script><template> の順番で書かれています。この違いは何なのかどちらで書くのが良いのか調べてみました👀

公式ドキュメントの見解

公式スタイルガイド (Vue 3)には以下のように記載されています。

https://ja.vuejs.org/style-guide/rules-recommended#single-file-component-top-level-element-order

単一ファイルコンポーネントでは、 <script> ,<template> , <style> タグの順番は常に一定で、<style>は最後にする必要があります。
なぜなら他の二つのうち少なくとも一つは常に必要だからです。

つまり、<script><template> の順番は厳密には決まっていませんが、スタイルガイドに従う場合、<style> が最後であることが推奨されているようです。

結論

  1. <script><template> の順番はどちらでも良い
  2. プロジェクトや会社の方針、個人の選択を優先する
  3. ただし、ESLintの設定で順番が指定されている場合もある

という事がわかりました👀

個人的には<script><template><template><style> を行き来してコードを書くことが多いため、 <script><template><style> の順番で記述されているとファイルのスクロール量が減って便利に感じます😌

Vue3の refreactive の違いって何?

勉強を進めていくと、refreactive が出てきました。
ただどちらもリアクティブな値を宣言する関数で、どちらをどのタイミングで使えば良いのか、その使い分けは何なのか疑問を感じました🤔

ref とは

公式では refについて、以下のように記述しています。

内部値を受け取り、リアクティブでミュータブルな ref オブジェクトを返します。
またそれは、内部値を示した単一プロパティである .value を持っています。

https://ja.vuejs.org/api/reactivity-core.html#ref

reactive とは

公式では reactive について、以下のように記述しています。

リアクティブな状態を宣言する方法として、reactive() という API を使う方法もあります。
内側の値を特別なオブジェクトでラップする ref とは異なり、reactive() はオブジェクト自体をリアクティブにします:

https://ja.vuejs.org/guide/essentials/reactivity-fundamentals#reactive

結局どちらを使えば良いの?

関数 違い
ref 内側の値を特別なオブジェクトでラップする
reactive オブジェクト自体をリアクティブにする

ただ、どちらの関数もリアクティブにするために使うことは分かりましたが、結局どちらをどのタイミングで使用すれば良いのか?という疑問が出ました🤔

公式ドキュメントでは ref の記述で、

https://ja.vuejs.org/guide/essentials/reactivity-fundamentals#declaring-reactive-state-1

Composition API では、リアクティブな状態を宣言する方法として、ref() 関数を使用することを推奨します:

と書かれている一方で、 reactive では以下のように書かれています。

https://ja.vuejs.org/guide/essentials/reactivity-fundamentals#limitations-of-reactive

reactive() API にはいくつかの制限があります:

  1. 限定された値の型: オブジェクト型(オブジェクト、配列、および Map や Set などの コレクション型)に対してのみ機能します。
    文字列、数値、真偽値などの プリミティブ型 を保持できません。
  1. オブジェクト全体を置換できない: Vue のリアクティビティー追跡はプロパティアクセス上で動作するため、リアクティブなオブジェクトへの参照を常に同じに保つ必要があります。
    つまり、最初の参照へのリアクティブな接続が失われるため、リアクティブなオブジェクトを簡単に「置き換える」ことはできません:
let state = reactive({ count: 0 })

// 上記の参照({ count: 0 })は、もはや追跡されていません
// (リアクティブな接続は失われました!)
state = reactive({ count: 1 })
  1. 分割代入できない: また、リアクティブなオブジェクトのプリミティブ型のプロパティをローカル変数に分割代入したり、そのプロパティを関数に渡したりすると、下記に示すようにリアクティブなつながりが失われることとなります:
const state = reactive({ count: 0 })

// count は分割代入すると state.count と切り離されます。
let { count } = state
// 元の状態に戻りません。
count++

// この関数は数値を受け取りますが、これだと
// state.count の変更を追跡することができません。
// リアクティビティーを維持するためには、オブジェクト全体を渡す必要があります
callSomeFunction(state.count)

このような制約があるため、リアクティブな状態を宣言するための主要な API として ref() を使用することを推奨します。

また、VueFesJapan2023で行われたVue.jsクリニックという企画でもこの話題が注目を集めており、Vue.js の作者である、Evan You氏が「 ref をおすすめしたい」との旨を言っていたそうです👀

(こちらの企画はアーカイブがないため、Xの投稿を参照しています)

https://x.com/punksy2/status/1718174713955090610

https://x.com/t0yohei/status/1718173910523580731

https://x.com/ruka70663556/status/1718210334413193247

https://x.com/9pid/status/1718174508325236817

reavtive が存在している理由は?

先ほどの公式引用や、VueFes2023のVue.jsクリニックでEvan氏がお話しされていたことを踏まえて、reavtive が存在している理由は何なのかという疑問を感じました🤔

https://ja.vuejs.org/guide/essentials/reactivity-fundamentals#reactive

非プリミティブ値は、後述する reactive() を介してリアクティブプロキシーに変換されます。

公式では上記のように書かれており、 ref の内部で reavtive が使用されているそうです。

また、他の記事ではOptions APIの data プロパティの定義と書き方が似ているので、従来の書き方で慣れている場合は親しみやすかったり、 .value を書かなくてもよいというメリットがあるとのことです。

ただ、個人的には Vue2からVue3への移行が大変 という意見が多い中で、完全に reactive がなくなってしまうと、破壊的な変更が増え、開発者に負担がかかるという点から残しているのではないかと考えています😌

結論

基本的に refを使えば問題ない! という事がわかりました🙆‍♀️
とはいえ、 reactive を活用した実装例もよく見受けられるので、どちらも対応できるように使ってみたいと思います👀

おわりに

Vueを勉強していく中で度々疑問を持つ箇所があり、その理由や公式の推奨・見解などを調べていくことで、新たな学びを得られました☺️

今後も小さな疑問を深掘っていくことで、楽しいVueライフを送っていこうと思います🥳

Vue・Nuxt 情報が集まる広場 / Plaza for Vue・Nuxt.

Discussion