Vue.js 3を利用して今後本格的なWebアプリケーションを構築したいという人でこれまで一度もVue.jsを利用した経験がない人に向けて作成した文書です。実際に動かしながら学べるようにシンプルなコードを使いながら説明を行っています。読み進めていくためにはJavaScriptの知識を前提してしていますが難しい関数や構文は利用していません。本文書ではテンプレートリテラル、アロー関数、filter関数しか出てきませんが、Vue.jsを使いこなすためにはスプレッド構文、分割代入、map関数、 find関数、Promise、async/await関数などを学習することをお勧めします。

Vue.jsをこれまで使った経験がある人なら気になるところだと思いますがVue3ではOptions APIとComposition APIという2つの記述方法(API)があります。本文書ではComposition APIの<script setup>を利用してコードを記述していきます。Options APIとCompositon APIの違いやVue2, Vue3の違いなどについては一切触れていません。プロジェクトの作成はVue CLIではなくViteを利用して行っています。

基礎編に続くコンポーネント編については下記の記事で公開済みです。Props, Slot, Emitなど本文書に含まれていないコンポーネントに関する情報が含まれています。

Options APIを利用したい場合は下記の公開済みの記事が参考になります。

Vue.jsとは

Vue.jsはWEBサイト、アプリケーションを利用するユーザに対してインタラクティブなUI(ユーザインターフェイス)を提供したい場合に利用できるJavaScriptフレームワークです。人気のあるフレームワークなので開発者も多く書籍の多数発刊されているのでわからないことがあってもすぐに調べることができます。WEBアプリケーションの技術の移り変わりは早いので数年後にどの技術が利用されているかわかりませんがVue.jsは現在でも活発に活動が行われているので学んで損はないフロントエンドの技術だと思います。たとえVue.jsが利用されなくなったとしてもその技術はVue.jsよりも効率的にアプリケーションが構築できるようになっているはずなのでそれほど心配することはないでしょう。

環境の構築

Vue.jsの動作確認を行うためにはCDN、StackBlitz(https://vite.new/vue)を利用することができますがWindows、Macに限らずLinuxでも手元の環境にNode.jsをインストールを行うだけでVue.jsの開発環境を構築することができます。Node.jsはJavaScriptを動かすために必要なJavaScriptエンジン(プログラム)です。Vue.jsに限らずJavaScriptを動作させるために利用します。

Node.jsのインストールが必要となるのでhttps://nodejs.org/ja/にアクセスを行いNode.jsのインストールを行う必要があります。推奨版でも最新版でも動作します。Node.jsをインストールすると一緒にnpm(Node Package Manager)もインストールすれます。npmはJavaScriptのパッケージを管理するツールです。本文書でもnpmコマンドを利用してパッケージのインストールを行います。

Node.jsのダウンロードページ
Node.jsのダウンロードページ

Vue.jsの開発環境はViteを利用して行います。ViteはVue.jsを開発をする際に元になるプロジェクトを作成することができるツールです。現在Vue.jsでは新規でプロジェクトを作成する場合はVue CLIではなくViteを利用することを推奨しています。今後はプロジェクト作成ツールはViteをベースに開発が行われていくようです。

ViteではNode.jsのバージョンが12.0.0以上なのでnode -vコマンドを実行してNode.jsがインストールされていることを確認しておきます。


 % node -v
v16.13.0

任意のプロジェクト名(first-vue-app)を指定してnpmコマンドを利用して Vue. jsのプロジェクトの作成を行います。viteはnpmコマンドのほかにyarn, pnpmコマンドを利用することができます。


 % npm init vite@latest [プロジェクト名] -- --template vue

// 実行するコマンド
 % npm init vite@latest first-vue-app -- --template vue
ViteではなくVueのビルドツールを利用したい場合はnpm init vue@latestのコマンドを実行してください。公式のプロジェクトの作成ツールのcreate-vueが実行され利用したい機能を選択してインストールを行うことができます。

実行すると指定したプロジェクト名と同じ名前のフォルダfirst-vue-appが作成されるのでcdコマンドで移動してnpmコマンドを実行します。npm installコマンドを実行することでVue.jsを動作させるために必要なJavaScriptのパッケージが手元のパソコンにインストールされます。


 % cd first-vue-app
 % npm install 

npm installが完了したらnpm run devコマンドでローカルの開発サーバを起動することができます。実行すると開発サーバのURL(http://localhost:3000/)が表示されるのでブラウザからアクセスします。


 % npm run dev
//略
  VITE v3.0.4  ready in 431 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose

ブラウザには下記の画面が表示されます。これでVue.jsの動作確認を行うための環境の構築が完了しました。ローカルサーバに表示される内容を確認しながらアプリケーションを構築していくことになります。

Vue+Viteの初期画面
Vue+Viteの初期画面

画面に表示されている内容の確認

http://localhost:5173にアクセスして表示されている内容がどのファイルの内容を元に表示されているか確認することでVueプロジェクトのファイル構成を理解することができます。

index.html

最初にブラウザから開発サーバにアクセスすると表示されるのがプロジェクトフォルダ直下にあるindex.htmlファイルです。中身を見ると見慣れたHTMLのタグが記述されていることがわかります。


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + Vue</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

index.htmlファイルの内容がブラウザ上に表示されていることを確認するためにtitleタグの中身をVite AppからVue 3入門に変更してみましょう。


<title>Vue 3 入門</title>

ファイルを更新して保存するとnpm run devコマンドを実行したターミナルに[vite] page reload index.htmlというメッセージが表示され更新した内容が反映されます。自動で更新が行われますがまれに更新した内容がうまく更新されない場合があります。その場合はリロードボタンを押して再読み込みを行ってください。

タイトルの更新
タイトルの更新

index.htmlファイルの内容が表示されていることはわかりましたがindex.htmlファイルのどこを見てもブラウザ上に表示されているロゴ画像のimgタグもなければ”Vite + Vue”という文字列も見つけることはできません。

main.js

index.htmlの中にscriptタグが設定されています。index.htmlの外部から何かを読み込んでいるのはこのファイルしかないのでscriptタグに指定されているmain.jsファイルを確認してみましょう。

main.jsには以下の3行しか記述されていません。3行に書かれている内容を確認していきます。


import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

createApp(App).mount('#app')

先頭ではvueからcreateApp関数をimortしています。importしたcreateApp関数を利用してVue.jsのインスタンスの作成を行っています。インスタンスを作成する際には引数にApp.vueファイルからimportしたAppを指定しています。App.vueファイルには見慣れないvueという拡張子がついています。拡張子vueはApp.vueファイルがVue.jsのフォーマットでコードが記述されていることを表しておりこの拡張子を見て内部で適切な処理が行われます。vueファイルの中にはVue.js特有のフォーマットで中身を記述する必要があり、中身については後ほど確認します。

createAppでインスタンスを作成した後はmountメソッドを実行しています。mountメソッドの引数に指定しているのがindex.htmlファイルに記述されているdiv要素のid属性のappです。mountメソッドの処理でid属性にappを持つdiv要素の中にApp.vueファイルに記述された内容を挿入しています。mountの引数の文字列を#appから別の文字列に変更することは可能ですが変更した場合はindex.htmlファイルのid属性も同じ名前に変更する必要があります。

div要素への挿入というイメージが湧かない人はJavaScriptで要素を追加したい場合にgetElementByIdで要素を取得しappendChildメソッドで別の要素を追加することができることを思い出してください。

App.vue

createAppに指定されているApp.vueファイルの中身を見てみましょう。


<script setup>
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <div>
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
    </a>
  </div>
  <HelloWorld msg="Vite + Vue" />
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

App.vueファイルを見ると大きく3つのパート(scriptタグ、templateタグ、styleタグ)に分かれていることがわかります。

拡張子vueがついたファイル内ではscriptタグにはJavaScriptのコード、templateタグにはHTML、styleタグにはCSSを記述することができます。

templateタグの中にはimgタグでpublicフォルダのvite.svgとassetsフォルダのvue.svgを指定しています。vite.svgがブラウザに表示されているViteのログファイルでvue.svgファイルがブラウザ上に表示されているVueのロゴファイルです。index.html, main.js, App.vueファイルの3つを確認してようやくブラウザ上に表示されているロゴが設定されているファイルを確認することができました。

HelloWorld.vue

App.vueファイルのtemplateタグの中にはscriptタグでimortしたHelloWorld.vueと同じ名前のカスタムタグが含まれています。templateタグにはHTMLを記述することができると説明しましたが通常のHTMLタグ以外のタグも記述することができます。

HelloWorld.vueファイルはvueの拡張子がついているのでscriptタグ、templateタグ、styleタグで構成されています。HelloWorld.vueファイルに記述されている内容がブラウザ上に表示されていたVueのロゴ以外の内容です。


<script setup>
import { ref } from 'vue'

defineProps({
  msg: String
})

const count = ref(0)
</script>

<template>
  <h1>{{ msg }}</h1>

  <div class="card">
    <button type="button" @click="count++">count is {{ count }}</button>
    <p>
      Edit
      <code>components/HelloWorld.vue</code> to test HMR
    </p>
  </div>

  <p>
    Check out
    <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
      >create-vue</a
    >, the official Vue + Vite starter
  </p>
  <p>
    Install
    <a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
    in your IDE for a better DX
  </p>
  <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>

<style scoped>
.read-the-docs {
  color: #888;
}
</style>

HelloWorld.vueファイルのようにvueの拡張子のついたファイルをシングルファイルコンポーネント(SFC)ファイルと言います。Vue.jsではcreateAppで指定されていたAppコンポーネントをルートコンポーネントとして他のコンポーネントをimportすることでアプリケーションを構築していきます。Appコンポーネントにすべての処理を記述することも可能ですがコードが肥大化していくとメンテナンスも大変になります。コンポーネントを機能・役割毎に分割していくことでコンポーネントが再利用できるようになり、コードの保守も楽になります。

コンポーネントはツリーような構造になる
コンポーネントはツリーような構造になる

コンポーネントファイルを直接ブラウザから表示できるのではないかと思う人がいるかもしれませんが残念ながらvueファイルの内容を直接ブラウザは理解することができません。またJavaScriptではファイルをimportする際はimportされる側のファイルでexportが必要になりますがVue.jsのコンポーネントファイル(拡張子vue)ではexportの処理を行う必要はありません。ブラウザがVueを認識できるようになる内容になるまで変換処理を行ってくれています。

ここまでの動作確認でindex.htmlファイルからファイルをたどっていくことでブラウザ上に表示されている内容がどのファイルから構成されているのか理解することができました。

冒頭でVueはユーザにインタラクティブなUIを提供することができると話をしましたがその例としてブラウザ上に表示されているcountボタンがあります。ボタンをクリックするとカウントが増えていきます。JavaScript、jQueryを利用することでもこのような機能を実装することができますがVueを利用するとこのようなインタラクティブな機能を簡単に実装することができます。

初めてのHello World

ここからは実際にコンポーネントファイルを更新してVue.jsの機能を確認していきます。

App.vueファイルを以下のように記述します。templateタグにはh1タグを追加しscriptタグの内容は削除しています。


<script setup>
</script>

<template>
<h1>Vue 3 入門</h1>
</template>

ブラウザ上に”Vue 3 入門”の文字のみ表示されます。

App.vueファイルを更新後の画面
App.vueファイルを更新後の画面

scriptタグの中ではJavaScriptのコードを記述できると説明したのでconsole.logを実行します。


<script setup>
console.log('Hello World');
</script>

App.vueファイルを更新すると自動で更新が反映されるのでデベロッパーツールのコンソールには”Hello World”が表示されます。

このことからscriptタグに記述したコードはコンポーネントが読み込まれると自動で実行されるということがわかりました。これは重要なので覚えておいてください。

scriptタグの中にsetupという文字列が入っていますがscriptはscriptタグの中でVue.jsのコード(Composition API)の記述を楽にするために必要な設定なので削除しないように注意してください。削除してもJavaScriptのコードが実行できなくなるわけではありませんがVue.jsを利用したコードが動かなくなります。

次にscriptタグの中に変数messageを定義します。


<script setup>
const message = 'Hello World';
</script>

定義した変数messageの内容をブラウザ上に表示させることができます。その場合はtemplateタグの中で2つのマスタッシュで変数名を囲みます({{ 変数名 }})。


<template>
  <h1>Vue 3 入門</h1>
  <p>{{ message }}</p>
</template>

ブラウザ上に変数messageに設定した文字列が表示されました。Vue.jsではscriptタグに定義した変数をマスタッシュを利用してブラウザ上に表示することができます。

定義した変数をブラウザ上に表示
定義した変数をブラウザ上に表示

さらにマスタッシュは変数に保存された内容を表示するだけではなくJavaScriptのコードを実行することもできます。変数messageの文字列をlengthプロパティで取得し10をかけることでブラウザ上には110と表示されます。


<template>
  <h1>Vue 3 入門</h1>
  <div>{{ message.length * 10 }}</div>
</template>

マスタッシュの中では三項演算子を実行することもできます。messageの文字列が10よりも長い場合はLongと表示され、10以下だとShortと表示されます。messageの値を変更することで表示される内容を変更してみてください。


<template>
  <h1>Vue 3 入門</h1>
  <div>{{ message.length > 10 ? 'Long' : 'Short' }}</div>
</template>

v-textディレクティブ

Vue.jsではtemplateタグ内の要素(div, p, button, span,….)に対してVue.jsが持つ特別な属性v-XXX(XXXには名前)を設定することで設定した要素に対して特別な機能を追加することができます。2つのマスタッシュで変数を囲むことでブラウザ上に表示させることができましたがv-textディレクティブを利用することでv-textディレクティブに設定した変数の内容をブラウザ上に表示させることができます。


<template>
  <h1>Vue 3 入門</h1>
  <p>{{ message }}</p>
  <p v-text="message"></p>
</template>

一つはマスタッシュ、もう一つはv-textを利用してmessageの内容を表示させています。

v-textディレクティブを利用
v-textディレクティブを利用

v-textで設定したタグの中に文字を入力するとエラーになるので注意してください。

v-htmlディレクティブ

v-textと似たディレクティブにv-htmlというディレクティブがあります。これは変数にhtmlタグを含めるとそのタグをブラウザ上でタグとして認識させることができます。

変数messageにh2タグを含んだ文字列を設定します。v-text, v-htmlをdivタグに設定して表示の確認を行います。


<script setup>
const message = '<h2>Hello World</h2>';
</script>

<template>
  <h1>Vue 3 入門</h1>
  <div v-text="message"></div>
  <div v-html="message"></div>
</template>

v-textはh2タグを文字列として認識するのでタグを含んだ形でブラウザ上に表示されます。v-htmlではh2タグとして認識することができるためブラウザ上にはh2タグが設定された文字列が表示されます。

v-textとv-htmlの違い
v-textとv-htmlの違い

関数の実行

scriptタグの中では直接関数を実行することもできます。動作確認のためupperCase関数を追加します。upperCase関数の中では変数のmessageを大文字にする処理を行います。


<script> setup>
let message = 'Hello World';

const upperCase = () => {
  message = message.toUpperCase();
};

upperCase();
</script>

<template>
  <h1>Vue 3 入門</h1>
  <div> v-text="message"></div>
</template>

scriptタグの中身は自動で実行されるということは確認済みなのでページを開くとブラウザ上には大文字になった”HELLO WORLD”を確認することができます。scriptタグ内では関数の設定、実行が行えることが確認できました。

関数を使って大文字になった文字列
関数を使って大文字になった文字列

Binding(バインディング)

v-bindディレクティブを利用することでhtmlタグの属性の設定を行うことができます。属性の設定を行うという意味もわかりにくいと思うのでaタグで頻繁に利用されるhref属性を使ってどういうものか確認していきます。

v-bindディレクティブを利用してhref属性を設定する場合は下記のように記述することができます。v-bindの後ろの:(コロン)そして属性名が続きます。


<a v-bind:href="変数名">リンク名</a >

実際にhref属性にv-bindを設定する場合は以下のように行うことができます。変数linkを定義してgoogleのURLを設定しています。


<script setup>
const link = 'https://google.com';
</script>

<template>
  <h1>Vue 3 入門</h1>
  <div>
    <a v-bind:href="link">Google</a>
  </div>
</template>

v-bindによってhrefの値がlink変数に設定したgoogleへのリンクになっているためアンダーバーのついた青色の文字列をクリックするとGoogleの検索画面が表示されます。

v-bindによりhref属性を設定
v-bindによりhref属性を設定

この動作を確認してhrefに変数が設定できて何かメリットがあるのと思う人もいると思いますがhrefに変数が設定できるということはユーザの行う行動によって変数の値を変更することでユーザの行動に応じたリンク先を設定できるようになることを意味します。ボタンをクリックするとGoogleのリンクからYahooへのリンクへと変更することもできます。静的なHTMLだけで構成されたページではできないことです。Vue.jsを利用しなくてもJavaScriptで動的に変更できますがVue.jsを使うことで簡単にできるようになります。

v-bindディレクティブは省略形で記述することが可能でv-bindを削除して:変数名(変数名の前にコロンのみつける)でも設定が可能です。v-bindに限らず一般的に省略形がある場合は省略形をみんな利用するためv-bind:よりも:(コロン)のみの形をよく見かけます。

href属性に設定を行いましたが他の属性でも利用することができます。v-bindを頻繁に利用する例として以下のようなものがあります。動的に表示させる画像を変更したい場合、条件によってボタンを押させないといったことがv-bindディレクティブを利用することで実装できます。

  • imgタグのsrc属性
  • buttonタグのdisabled属性

class属性

v-bindを利用してclass属性の設定を確認していきます。ここからはすべてv-bind:の省略型の:(コロン)を利用します。

Vue.jsではコンポーネントファイルの中にstyleタグがあるのでその中にclassを設定することができます。styleタグの中でactiveクラスを設定します。activeクラスでは適用すると文字が太文字で赤に設定されます。


<script setup></script>

<template>
  <h1>Vue 3 入門</h1>
  <p class="active">v-bindの設定方法の確認</p>
</template>

<style>
.active {
  color: red;
  font-weight: 900;
}
</style>

styleタグの中に追加したactiveクラスをpタグに設定します。ブラウザで確認するとactiveクラスが反映されていることがわかります。styleタグにclassを追加することでtemplateのhtmlタグにstyleタグで定義したclassが適用できることがわかりました。

styleタグに設定したclassを適用
styleタグに設定したclassを適用

v-bindを使ってclassを設定する方法を確認していきます。classを設定するのであればstyleタグにclassを追加し、追加したclassをclass属性に追加すればいいだけです。v-bindを利用してclassを設定する理由は動的に適用するclassを変更できるようにするためです。その部分に注目して読み進めてみてください。

v-bindを利用したclassの設定には複数の方法があるので状況に応じて使い分けてください。

最初の方法はオブジェクトを利用します。オブジェクトのプロパティにclass名を設定し、値に変数名またはtrue, falseを利用することでclassを適用するかどうかを設定することができます。変数名をtrueに設定した場合はclassが適用されfalseに設定した場合は適用されません。ここでなんでtrue, falseを設定しなければならないと思った人は思い出してください。v-bindを利用してclassを設定する理由は動的に適用するclassを変更できるようにするためだからです。


v-bind:class="{class名: 変数名 or (true or false)}"    

isActiveという名前の変数を定義してfalseに設定します。


<script setup>
const isActive = false;
</script>

<template>
  <h1>Vue 3 入門</h1>
  <p> :class="{ active: isActive }">v-bindの設定方法の確認</p>
</template>   

isActiveのfalseの場合はactiveクラスが適用されないのでclassが適用されていない状態で表示されます。

classが適用されない場合
classが適用されない場合

isActiveをtrueに設定することでactiveクラスが適用されます。


<script setup>
const isActive = true;
</script>
styleタグに設定したclassを適用
classが適用された場合

v-bindで設定するclass以外のclassを設定したい場合(動的に変更する必要がなく常時設定させておきたいclass)は通常のclass属性を利用することができます。class属性にunderLineクラスを設定します。pタグにはclass属性とv-bindを設定したclassが含まれることになります。


<template>
  <h1>Vue 3 入門</h1>
  <p class="underLine" :class="{ active: isActive }">v-bindの設定方法の確認</p>
</template>

<style>
.active {
  color: red;
  font-weight: 900;
}
.underLine {
  text-decoration: underline;
}
</style>

変数isActiveの値がtrueの場合はactiveクラスとunderlineクラスが適用されることになります。

通常のclassとバインディングclassを設定
通常のclassとバインディングclassを設定

underLineとactiveクラスをisActiveがtrueの場合に適用したい場合には下記のように記述することができます。


<p :class="{ 'underLine active': isActive }">v-bindの設定方法の確認</p>

v-bindディレクティブを設定したclassの中には複数のclassを設定することができます。backクラスをstyleタグの中に追加し、v-bindに利用する変数isBlackを定義します。isBlackの値はclassが適用できるようにtrueにしています。2つの変数を使ってclassを制御できることになったことで4つのパターンでpタグを表示できるようになりました。


<script setup>
const isActive = true;
const isBlack = true;
</script>

<template>
  <h1>Vue 3 入門</h1>
  <p class="underLine" :class="{ active: isActive, back: isBlack }">
    v-bindの設定方法の確認
  </p>
</template>

<style>
.active {
  color: red;
  font-weight: 900;
}
.underLine {
  text-decoration: underline;
}
.back {
  background-color: black;
}
</style>

どちらの変数もtrueの場合は3つのclassが適用されているのでブラウザの画面は下記のように表示されます。

v-bindによる複数のclassの適用
v-bindによる複数のclassの適用

下記のように複数のclassを設定することもできます。


<p :class="{ 'underLine active': isActive, back: isBlack }">

v-bindディレクティブを設定したclassの中では論理演算子(&&)を利用して設定することもできます。


v-bind:class="{変数名 && 'class名'}"    

isActiveがtrueの時にactiveクラスが適用されます。


<p :class="isActive && 'active'">v-bindの設定方法の確認</p>

利用頻度は低いとは思いますが論理演算子(||)も利用できます。変数がfalseの時にclassが適用されます。


v-bind:class="{変数名 || 'class名'}"    

三項演算子を利用することもできます。isActiveがtrueの場合はactive, falseの場合はunderLineが適用されます。


<p :class="isActive ? 'active' : 'underLine'">v-bindの設定方法の確認</p>

:classに変数名を設定して変数名に設定しているclassを適用することもできます。


<script setup>
const isActive = 'active';
</script>

<template>
  <h1>Vue 3 入門</h1>
  <p> :class="isActive">v-bindの設定方法の確認</p>
</template>

複数のclassを適用したい場合には配列を利用します。


<script setup>
const isActive = 'active';
const isBlack = 'back';
</script>

<template>
  <h1>Vue 3 入門</h1>
  <p :class="[isActive, isBlack]">v-bindの設定方法の確認</p>
</template>

配列の中でオブジェクトを利用することもできます。


<p :class="[{ active: 'isActive' }, isBlack]">v-bindの設定方法の確認</p>

配列の中で演算子を利用することもできます。


<p :class="[isActive && 'active', isBlack]">v-bindの設定方法の確認</p>

<p :class="[isActive ? 'active' : 'underLine', isBlack]">
  v-bindの設定方法の確認
</p>

このようにclassを動的に変更するための方法がいくつかあります。すべてを覚える必要はありませんが動的にclassを変更する際にどの記述方法がアプリケーションに適しているかを考えて利用してみてください。

style属性の設定

style属性の場合もclass同様に複数の記述方法があります。inlineでオブジェクトを利用して複数のプロパティを設定することができます。変数activeColor, fontStressを定義してv-bindを設定したstyle属性の中で利用しています。文字の色を赤から青に変更したい場合はactiveColorの値をredからblueに変更します。


<script setup>
const activeColor = 'red';
const fontStress = '900';
</script>

<template>
  <h1>Vue 3 入門</h1>
  <p :style="{ color: activeColor, fontWeight: fontStress }">
    v-bindの設定方法の確認
  </p>
</template>

上記ではfontWeightをキャメルケースで記述していますが通常のstyleのプロパティを利用した場合はシングルクォーテーションを使います。


<p :style="{ color: activeColor, 'font-weight': fontStress }">
  v-bindの設定方法の確認
</p>

変数側でstyleを設定することもできます。変数側でスタイルを設定することでtemplateタグがすっきりと見通しが良くなります。


<script setup>
const styleObjcet = {
  color: 'red',
  fontWeight: 900,
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <p :style="styleObjcet">v-bindの設定方法の確認</p>
</template>

通常のstyle属性のように記述することもできます。


<script setup>
const styleObjcet = 'color:red;font-weight:900';
</script>

<template>
  <h1>Vue 3 入門</h1>
  <p :style="styleObjcet">v-bindの設定方法の確認</p>
</template>

条件分岐

アプリケーションを構築していくと例えばページを閲覧するユーザの認証が完了している場合としていない場合に表示内容を変えたり、入力フォームでエラーが発生している場合のみエラメッセージを表示させたいなどある条件によって表示させたい内容を制御したいという場合にv-ifディレクティブやv-showディレクティブを利用します。

v-ifによる分岐

v-ifディレクティブを利用することで要素単位またはブロック単位で表示・非表示または表示内容を変えることができます。v-ifと一緒にv-else-if, v-elseディレクティブを利用することができますが3つを必ず一緒に利用する必要はありません。v-ifは必須ですがv-else-if, v-elseは必要な場合のみ利用します。

v-if, v-else-if, v-elseディレクティブはtemplateタグ内でのみ利用します。scriptタグの中で条件分岐を行いたい場合は通常のif, else-if, elseを利用します。

v-ifはtemplate内で下記のように記述することができます。


<div v-if="条件式">内容</div>
<div v-else-if="条件式">内容</div>
<div v-else>内容</div>

実際のtemplateタグ内での設定方法について確認していきます。

例えばerror変数にメッセージが入っている場合のみエラーを画面に表示させる場合は下記のようにv-ifのみ利用することができます。この場合は表示・非表示の制御を行っています。


<script setup>
const error = 'エラー発生';
</script>

<template>
  <h1>Vue 3 入門</h1>
  <div> v-if="error">{{ error }}</div>
</template>

条件式にはerrorの値をtrue, falseのbooleanを利用することもできます。


<script setup>
const error = true;
</script>

<template>
  <h1>Vue 3 入門</h1>
  <div> v-if="error">エラーが発生しています。</div>
</template>

場合によってはエラーがない場合もエラーがないことをユーザに伝えたいという場合があるかと思います。その場合はv-elseを利用することができます。v-elseはv-ifの条件に合致しなかった場合に表示される内容を記述します。error変数に何も入っていないので画面にはエラーはありませんと表示されます。この場合は条件によって表示する内容を変えています。


<script setup>
const error = '';
</script>

<template>
  <h1>Vue 3 入門</h1>
  <div v-if="error">{{ error }}</div>
  <div v-else>エラーはありません。</div>
</template>

表示する内容を複数の条件によって変えたい場合はv-else-ifを利用することができます。ECを運用しており商品の在庫数によって表示を変えたい場合などに利用することができます。


<script setup>
const stock = 0;
</script>

<template>
  <h1>Vue 3 入門</h1>
  <div v-if="stock > 5">まだ商品に在庫数に余裕があります</div>
  <div v-else-if="stock === 0">申し訳ございません。現在売り切れです。</div>
  <div v-else>在庫数が少なくなっています。お急ぎください。</div>
</template>

stockの数が0なので画面上には”申し訳ございません。現在売り切れです。”と表示されます。v-ifディレクティブを利用することでstockの値によって表示内容を変更することができます。v-if, v-elseは1つしか利用できませんがv-else-ifは条件の数によって増やすことができます。

条件によって表示内容を変える
条件によって表示内容を変える

ここまでは各条件下で表示させる内容が少なかったですがv-ifディレクティブが設定されている要素だけではなくその中にさらに要素を含むより大きな単位で表示の内容を切り替えることできます。


<div v-if="user">
  <div>...</div>
  <div>
    <p>...</p>
    <p>...</p>
  </div>
</div>

v-showによる制御

v-ifディレクティブによく似たものにv-showディレクティブがあります。v-ifでは条件式を利用することで表示・非表示だけではなく表示する内容を変更することができました。v-showにはv-else-ifなど一緒に利用するディレクティブは存在しません。条件によって表示・非表示のみ制御を行います。


<div v-show="条件式">内容</div>;

error変数の値がtrueの場合は内容が表示され、falseの場合には内容が表示されることはありません。


<script setup>
const error = true;
</script>

<template>
  <h1>Vue 3 入門</h1>
  <div v-show="error">エラーが発生しています。</div>
</template>

v-if, v-showの違い

v-ifとv-showは下記のコードを見る限り実行できることは同じです。errorがtrueの場合は”エラーが発生しています。”が2つ表示され、falseの場合は何も表示されません。これだけ見るとv-else-ifの複数の条件を設定できないv-showディレクティブは必要がないように思われます。しかしv-showが存在するということは何か意味があるはずです。


<script setup>
const error = true;
</script>

<template>
  <h1>Vue 3 入門</h1>
  <div v-if="error">エラーが発生しています。(v-if)</div>
  <div v-show="error">エラーが発生しています。(v-show)</div>
</template>

tueの場合とfalseの場合にブラウザのデベロッパーツールで要素の情報を確認します。

trueの場合はブラウザ上に表示されているのどちらの要素も確認することができます。

条件がtrueの場合
条件がtrueの場合

falseの場合にはv-if, v-showで大きな違いを確認することができます。v-ifの場合は要素が存在しませんがv-showの場合は要素自体は存在し、style属性でdiplayプロパティがnoneに設定されています。

条件がfalseの場合
条件がfalseの場合

ページの要素を確認することでv-showでは表示・非表示の制御をdisplayプロパティを利用して制御していることがわかりました。

非表示の方法がv-if, v-showで異なることがわかりましたがv-showを利用するメリットは何なのでしょうか。v-showのdisplayのnone, blockの切り替えはv-ifによる要素の追加、削除より処理の負荷低いためです。ユーザとインタラクティブによってスピードは重要なので可能な限り負荷が低い処理を実行することには意味があります。

例えばドロップダウンメニューのように非表示の時には何も表示されず表示・非表示を繰り返すような機能にv-showを利用することが適しています。

リスト表示

アプリケーションを構築していくと複数のデータで構成されたユーザ情報、顧客情報、商品情報を画面上にリスト表示(テーブル表示)させたいという場合が必ずあります。そのようなデータをリスト表示させたい場合に利用できるのがv-forディレクティブです。

配列のリスト表示

例えば現在学習したいプログラム言語/技術を変数languatesに配列で保存して画面上に表示させたいといった場合には配列の要素を指定して表示させることができます。


<script setup>
const languages = ['JavaScript', 'TypeScript', 'Vue.js'];
</script>

<template>
  <h1>Vue 3 入門</h1>
  <p>{{ languages[0] }}</p>
  <p>{{ languages[1] }}</p>
  <p>{{ languages[2] }}</p>
</template>

ブラウザで確認すると配列に保存した情報が下記のように表示されます。

配列を画面上に表示
配列を画面上に表示

さらに学習したい言語にReact, Rust, Goなど追加していくとtemplate側では配列の数だけpタグを追加していくことになります。配列がたくさんの要素で構成されている場合v-forディレクティブで1行のコードで処理を行うことができるようになります。


<script> setup>
const languages = ['JavaScript', 'TypeScript', 'Vue.js', 'React', 'Rust', 'Go'];
</script>

<template>
  <h1>Vue 3 入門</h1>
  <p> v-for="language in languages" :key="language">{{ language }}</p>
</template>

v-forを利用することで配列から要素を1つずつ順番に取り出し、取り出し要素をマスタッシュで囲むことでリストとして表示することができます。

v-forディレクティブを利用した場合はv-bindでkey属性に一意の値を設定する必要があります。

配列から順番に取り出された要素が下記のように表示されます。

v-forディレクティブを利用して表示
v-forディレクティブを利用して表示

v-forディレクティブを利用することで今後学習した言語が配列にさらに追加された場合もtemplateタグの中の変更を行う必要がなくなります。このようにv-forディレクティブは複数の情報が入ったデータから繰り返しの処理を使って個別に取り出したい時に利用できる便利な機能です。

オブジェクト配列のリスト表示

先ほどの動作確認で配列の要素に入った値をv-forディレクティブでリスト表示できることがわかりました。Vue.jsでは配列の要素に入るのは値ではなくほとんどの場合はオブジェクトで、配列に入ったオブジェクトデータをリスト表示させることが大半です。配列に入ったユーザオブジェクトをリスト表示したい場合のv-forディレクティブの使い方を確認します。

変数usersを定義します。配列の要素の中にオブジェクトでユーザ情報が保存されています。


<script setup>
const users = [
  { id: 1, name: 'John Doe', email: 'john@test.com', admin: true },
  { id: 2, name: 'Jane Doe', email: 'jane@example.com', admin: false },
  { id: 3, name: 'Kevin MacDonald', email: 'kevin@test.com', admin: false },
];
</script>

配列と同じようにv-forディレクティブを利用します。


<template>
  <h1>Vue 3 入門</h1>
  <p v-for="user in users" :key="user">{{ user }}</p>
</template>

配列と同様にv-forにより配列から要素(オブジェクト)が順番に取り出され、ブラウザ上にはそれぞれのオブジェクトの内容がそのまま表示されます。

オブジェクトをv-forでリスト化
オブジェクトをv-forでリスト化

オブジェクト全体ではなくオブジェクトのプロパティの値を個別に表示できるようにコードを書き換えます。keyも一意の値を持つidに変更しています。オブジェクトのプロパティを表示するためにはuser.プロパティ名でアクセスすることができます。


<template>
  <h1>Vue 3 入門</h1>
  <div v-for="user in users" :key="user.id">
    {{ user.id }}:{{ user.name }}({{ user.email }})
  </div>
</template>

プロパティを利用することでブラウザ上には設定した通りに表示されます。

オブジェクトのプロパティを使って表示
オブジェクトのプロパティを使って表示

divではなくul, liタグを利用したい場合には下記のように書き換えることができます。


<template>
  <h1>Vue 3 入門</h1>
  <ul>
    <li v-for="user in users" :key="user.id">
      {{ user.id }}:{{ user.name }}({{ user.email }})
    </li>
  </ul>
</template>

tableで表示させたい場合には以下のように記述することができます。


<template>
  <h1>Vue 3 入門</h1>
  <table>
    <thead>
      <tr>
        <td>ID</td>
        <td>ユーザ名</td>
        <td>Email</td>
      </tr>
    </thead>
    <tbody>
      <tr v-for="user in users" :key="user.id">
        <td>{{ user.id }}</td>
        <td>{{ user.name }}</td>
        <td>{{ user.email }}</td>
      </tr>
    </tbody>
  </table>
</template>

v-forでは配列の要素の番号であるindexを取得することで現在何番目の要素なのか確認することができます。


<template>
  <h1>Vue 3 入門</h1>
  <div v-for="(user, index) in users" :key="user">
    {{ index }}-{{ user.id }}{{ user.name }}{{ user.email }}
  </div>
</template>

indexは0から始まり以下のように表示されます。

v-forでindexを表示
v-forでindexを表示

v-forにdiv要素を利用していましたがtemplateタグでも利用することができます。


<template>
  <h1>Vue 3 入門</h1>
  <template v-for="user in users" :key="user.id">
    {{ user.id }}{{ user.name }}{{ user.email }}
  </template>
</template>

templateタグを使った場合は要素がなくなるため上記のようにtemplateタグの中でhtmlタグを利用していない場合はそのまま文字列がつながって表示されます。

templateタグでv-for
templateタグでv-for

v-forでは”in”を使っていましたが”of”も利用することができます。inでもofでも表示内容は変わりません。


<div v-for="user of users" :key="user.id">

オブジェクトのリスト化

v-forディレクティブを利用してここまでは配列をリスト化しました。ここではオブジェクトにv-forディレクティブを利用してリスト化する方法を確認します。


<script setup>
const user = {
  id: 1,
  name: 'John Doe',
  email: 'john@test.com',
  admin: true,
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <div v-for="value in user" :key="value">{{ value }}</div>
</template>

オブジェクトのプロパティの値が順番に表示されます。

オブジェクトの値をリスト化
オブジェクトの値をリスト化

プロパティの値だけではなくプロパティの名前も表示させることができます。


<template>
  <h1>Vue 3 入門</h1>
  <div v-for="(value, name) in user" :key="value">{{ name }}:{{ value }}</div>
</template>
プロパティ名と値を表示
プロパティ名と値を表示

v-forを利用することでオブジェクトのプロパティ名も表示できることが確認できました。

配列のオブジェクトのv-forとオブジェクトのv-forを組み合わせることで下記のようなコードも記述することができます。


<script setup>
const users = [
  { id: 1, name: 'John Doe', email: 'john@test.com', admin: true },
  { id: 2, name: 'Jane Doe', email: 'jane@example.com', admin: false },
  { id: 3, name: 'Kevin MacDonald', email: 'kevin@test.com', admin: false },
];
</script>

<template>
  <h1>Vue 3 入門</h1>
  <div v-for="user in users" :key="user.id">
    <div v-for="(value, name) in user" :key="value">{{ name }}:{{ value }}</div>
  </div>
</template>

値と一緒にプロパティ名を表示することができます。

複数のv-forを実行
複数のv-forを実行

リストと分岐

v-forディレクティブとv-ifディレクティブを利用することで条件が一致した情報のみ表示させるといったことが可能になります。ユーザオブジェクトのadminプロパティを利用して表示・非表示を決定します。


<script setup>
const users = [
  { id: 1, name: 'John Doe', email: 'john@test.com', admin: true },
  { id: 2, name: 'Jane Doe', email: 'jane@example.com', admin: false },
  { id: 3, name: 'Kevin MacDonald', email: 'kevin@test.com', admin: false },
];
</script>

<template>
  <h1>Vue 3 入門</h1>
  <div v-for="user in users" :key="user.id">
    <div v-if="!user.admin">
      {{ user.name }}
    </div>
  </div>
</template>

ブラウザにはadminの値がfalseのユーザのみ表示されます。

イベントの設定

ユーザとのインタラクティブなユーザインターフェイスを提供するためにはイベントを利用する必要があります。

イベントはボタンをクリックする、マウスを動かす、キーボードを押すなどユーザが実行する処理によって引き起こされます。Vue.jsではユーザによって引き起こされたイベントをv-onディレクティブを使って受け取ることができます。v-onディレクティブでイベントを受け取ることでそれをトリガーとして別の処理を行うことができます。v-onディレクティブを設定してもイベントが発生しなければ何も処理は行われません。

ボタンのクリックというユーザの行動によって引き起こされるイベントを例にv-onディレクティブでイベントを受け取りコンソールに文字列を表示してみましょう。

clickイベント

ユーザがボタンをクリックするとクリックイベントが引き起こされます。クリックイベントを受け取るためにはv-onディレクティブの後には:(コロン)とイベント名であるclickを設定します。イベントを受けとった後はイコールの中に記述されたコードを実行します。ここではclickButton関数を実行します。clickButton関数はJavaScriptの関数なのでscriptタグの中に記述します。


<script setup>
const clickButton = () => {
  console.log('click button');
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <button v-on:click="clickButton">クリック</button>
</template>

ボタンをクリックするとイベントが発生してv-on:clickでイベントを受け取り、デベロッパーツールのコンソールに”click button”の文字列が表示されます。これがイベントの一連の流れです。イベントの流れのパターンは全て同じでユーザが何か行動を行うその行動によってイベントが発生し、そのイベントを受け取り、別の処理を実行します。

v-onディレクティブには省略形があり、v-on:clickは@clickと記述することができます。ここからは省略した@を利用してイベントを記述していきます。


<button @click="clickButton">クリック</button>

@の後ろにはイベント名を設定することができます。click以外のイベントを設定して動作確認をしてみましょう。同じクリックイベントにdbclickというものがあるのでclickからdbclickに変更します。dbclickはダブルクリックした時に引き起こされるイベントです。


<button @dbclick="clickButton">クリック</button>

クリックをしただけではダブルクリックイベントは発生しないためclickButton関数は実行されませんがダブルクリックするとイベントが発生し@dbclickでイベントを受け取ることができるためclickButton関数が実行されます。

そのほかにmouseover, mouseenterなどのイベントもあります。それぞれ設定して動作するか確認してみてください。

イベントを受け取った後に引数を渡すことも可能です。ボタンをクリックすると引数で渡した文字列がコンソールに表示されます。


<script setup>
const clickButton = (msg) => {
  console.log(msg);
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <button @click="clickButton('クリック')">クリック</button>
</template>

クリックイベントを受け取って複数の関数を実行することも可能です。クリックを押すとclickButton関数とanother関数が実行されます。


<script setup>
const clickButton = (msg) => {
  console.log(msg);
};
const another = (msg) => {
  console.log(msg);
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <button @click="clickButton('クリック'), another('click')">クリック</button>
</template>

eventオブジェクト

イベントを受け取る際にeventオブジェクトを受け取ることができます。Vueではeventオブジェクトを受け取りたい場合に$eventを利用します。受け取ったeventオブジェクトのtargetプロパティを確認することでクリックした要素の情報を取得することもできます。


<script setup>
const clickButton = (event) => {
  console.log(event.target);
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <button @click="clickButton($event)">クリック</button>
</template>

クリックするとデベロッパーツールのコンソールにはクリックしたbutton要素が表示されます。


<button">クリック</button>

eventオブジェクトから要素にアクセスすることができるので要素のスタイルを変更することができます。下記の設定を追加することでクリックするとボタンの色が赤に変わります。


const clickButton = (event) => {
  event.target.style.backgroundColor = 'red';
};

イベント修飾子

イベント名の後に.修飾子をつけることで機能を追加することができます。

例えばformタグのsubmitが実行されるとページのリロードが必ず行われます。ページのリロードを防ぐためにはeventオブジェクトを利用してevent.preventDefaultを実行する必要があります。Vue.jsでは簡単にpreventDefaultが実行できるようにイベントに修飾子のpreventを追加することでだけで実装することができます。preventを利用しない場合の設定方法を確認し、その後prevetnを設定して動作確認を行います。

下記ではformタグにsubmitイベントを設定しています。submitイベントを設定するとformタグ内でボタンをクリックするとイベントを受け取りsend関数が実行されます。送信ボタンをクリックするとsend関数が実行されコンソールには”send”が表示されますがページのリロードにより”send”の文字は消えます。


<script> setup>
const send = () => {
  console.log('send');
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <form @submit="send">
    <button>送信</button>
  </form>
</template>

ページのリロードを防ぐためeventのオブジェクトのpreventDefaultを利用することができます。preventDefaultメソッドを実行するでsubmitのデフォルトの動作をキャンセルすることができます。


<script setup>
const send = (event) => {
  event.preventDefault();
  console.log('send');
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <form @submit="send($event)">
    <button>送信</button>
  </form>
</template>

上記の方法でもリロードを防ぐことができますがイベント修飾子を利用することでeventオブジェクトを利用することなく簡単に設定を行うことができます。これまでの@submitから@submit.preventに変更しています。


<script setup>
const send = () => {
  console.log('send');
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <form @submit.prevent="send">
    <button>送信</button>
  </form>
</template>

イベント修飾子にはpreventのほかにevent.stopPropagationを実行するために利用することができるstopなどがあります。

キー修飾子

特定のキーボードのキーが押された時にイベントを受け取りたい場合にはキー修飾子を利用することができます。例えばユーザがEnterを押して発生するイベントを@keyupイベントにキー修飾子のenterを設定すること受け取ることができます。下記のコードでは画面上でEnterボタンを押すとenterイベントが発生し、@keyup.enterイベントでイベントを受け取りsubmit関数を実行します。submit関数によるイベントを@submitイベントが受け取りsend関数が実行され、コンソールに”send”が表示されます。


<script setup></script>
const send = () => {
  console.log('send');
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <form @submit.prevent="send">
    <button @keyup.enter="submit">送信</button>
  </form>
</template>

そのほかのキー修飾子には下記のようなものがあります。設定することでそれぞれのキーを押した際に発生するイベントを取得することができます。

  • .enter
  • .tab
  • .delete
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

Reactivity

プロジェクト作成時からデフォルトで存在するHello Worldコンポーネントでは画面にcountボタンが表示されておりボタンをクリックすると画面上のcountの数が増えました。この機能の実装を行うためにはReactivityの理解が必要になります。ここではReactivityを行うとどうなるのかを確認しながら動作確認を行っていきます。

scriptタグでcountを定義しclickイベントを利用してcountの数を増やせるか確認するためこれまでの確認したVue.jsの機能を利用して下記のコードを記述します。


<script setup>
const count = 0;
</script>

<template>
  <h1>Vue 3 入門</h1>
  <button> type="button" @click="count++">count is: {{ count }}</button>
</template>

count変数に設定した数字は表示されますがcountボタンをクリックしてもcount数に変化はありません。この設定ではユーザの行動に対して何も変化が起こらないのでインタラクティブなアプリケーションではありません。

countボタンの表示
countボタンの表示

count数が増えないのは定義したcount変数がreactivityを持っていないためです。ボタンをクリックすると画面上のcount数が増やせるようにするためにはref関数またはreactivity関数を使って変数にreactivityを持たせる必要があります。変数にreactivityを持たせる方法を確認していきます。

refの設定

ref関数を利用することで変数にreactivityを持たせることができます。

vueからref関数をimportしてref関数の引数にcountの初期値である0を設定します。たったこれだけの処理でcountはreactiveな変数となります。ref関数を利用してcountを定義した後countボタンをクリックするとクリックした回数だけcountの数が増えます。


<script setup>
import { ref } from 'vue';
const count = ref(0);
</script>

<template>
  <h1>Vue 3 入門</h1>
  <button type="button" @click="count++">count is: {{ count }}</button>
</template>

3回ボタンをクリックするとcountの数は3になります。

reactiveな変数countを利用してcount数を増やす
reactiveな変数countを利用してcount数を増やす

ref関数で定義したreactiveな変数countはオブジェクトでラップされておりvalueプロパティのみを持っています。そのためscriptタグ内ではcount = 1といったようにcountを直接更新することはできません。scriptタグ内でcountの値にアクセスする場合はcount.valueで行うことができます。


<script setup>
import { ref } from 'vue';
const count = ref(0);
console.log(count.value);
</script>

templateタグ内ではではcount++でカウント数を増やすことができましたがscriptタグの中でcountの数を増やす場合は下記のようにcount.valueを利用する必要があります。countを増やすという処理は同じですがscriptタグとtempolateタグでは変更の方法が異なります。


<script setup>
import { ref } from 'vue';
const count = ref(0);

const addCount = () => {
  count.value++;
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <button type="button" @click="addCount">count is: {{ count }}</button>
</template>

ref関数を利用したreactiveな変数の設定方法がわかりました。

$refの利用

ref関数を利用した場合にvalueのつけ忘れまたはvalueはつけるのが面倒だという人も多いと思います。Experimental Featureですがscriptタグ内でvalueを利用しなくてもreactiveな変数として定義できる$refがあります。

$refを利用するためにはvite.config.jsファイルを設定する必要があります。pluginsのvueの引数にreactivityTransform:trueを追加しています。


import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue({
    reactivityTransform: true
  })]
})

設定を反映させるためにnpm run devコマンドを再実行します。実行するとExperimental Featureなのでメッセージが表示されます。


[@vue/reactivity-transform] Reactivity transform is an experimental feature.
Experimental features may change behavior between patch versions.
It is recommended to pin your vue dependencies to exact versions to avoid breakage.
You can follow the proposal's status at https://github.com/vuejs/rfcs/discussions/369.

メッセージが表示されますがそのまま$refを利用します。$refを利用する場合はrefのようにimportを行う必要はありません。


<script setup>
const count = $ref(0);

const addCount = () => {
  count++;
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <button type="button" @click="addCount">count is: {{ count }}</button>
</template>

reactiveの設定

reactiveな変数を定義する方法にはref関数とreactive関数があります。refでは引数にはboolean, string, オブジェクトを取ることができますがreactiveでは引数にオブジェクトを取ります。refとreactiveでは初期値の設定方法や値を更新する方法が異なります。

ref関数と同様にボタンをクリックするとcountの数が増える機能をreactive関数を利用して行います。

reactiveを利用する際もvueからreactive関数をimportする必要があります。reactive関数では引数にオブジェクトを取るのでcountはオブジェクトのプロパティとして設定します。初期値は0とします。


<script setup>
import { reactive } from 'vue';

const state = reactive({
  count: 0,
});

</script> 

countの値を表示する場合はオブジェクトなのでstate.countとなります。値を増やす場合もstate.count++となります。


<template>
  <h1>Vue 3 入門</h1>
  <button type="button" @click="state.count++">
    count is: {{ state.count }}
  </button>
</template> 

設定後にボタンをクリックするとcountの数がクリックの度に増えてきます。

templateタグの中でのcount数の増やし方はわかったので関数を利用してscriptタグ内でcountの数を増やす方法を確認します。reactive関数ではref関数で作成したreactiveな変数のようにvalueプロパティを利用する必要はありません。通常のオブジェクトの更新のように処理を行うことができます。


<script setup>
import { reactive } from 'vue';

const state = reactive({
  count: 0,
});

const addCount = () => {
  state.count++;
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <button type="button" @click="addCount">count is: {{ state.count }}</button>
</template>

ref関数の引数にオブジェクトを取ることができると先ほど説明しました。ref関数でのオブジェクトを利用した場合の動作も確認しておきましょう。オブジェクトの場合でも関数でcountを更新する場合にはvalueを介して行う必要があります。valueを挟まなければ後の設定方法はreactive関数と同じです。templateタグ内ではstate.countでアクセスすることができます。


<script setup>
import { ref } from 'vue';

const state = ref({
  count: 0,
});

const addCount = () => {
  state.value.count++;
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <button type="button" @click="addCount">count is: {{ state.count }}</button>
</template>

refとreactiveの違い

ref関数とreactive関数の一番の違いはrefの場合はscriptタグ内で値にアクセスする場合にvalueを利用することです。reactiveではオブジェクトのみを扱うのに対してrefでは確認したようにprimitiveな値(stringやboolenなど)もオブジェクトも設定することができました。

入力フォーム

アプリケーションを構築した場合にはユーザからのデータ入力を受け付けるためのinput要素を用いた入力フォームが必要となる場合があります。

Vueではv-modelディレクティブとreactiveな変数を利用することで入力フォームを構成するinput要素、textarea要素に入力した値やselect要素で選択した内容を即座にreactiveな変数に反映させ同期させることができます。入力フォームでどのようにv-modelディレクティブを利用していくかを確認していきます。

input要素

理解を深めるためにreactiveでない変数とreactiveな変数を比較して動作を確認します。

最初にreactiveではない変数messageを定義してv-modelディレクティブに設定します。


<script setup>
const message = 'Hello World';

const clickButton = () => {
  console.log(message);
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <p>{{ message }}</p>
  <input v-model="message" />
  <div><button @click="clickButton">Click</button></div>
</template>

ブラウザにはinput要素が表示され変数messageで設定した”Hello World”がinput要素に表示された状態で表示されます。

input要素による入力フォーム
input要素による入力フォーム

input要素の文字を変更してください。変更しても何も変化はありません。変更後にClickボタンを押すとブラウザのコンソールには変数messageに設定した値”Hello World”がそのまま表示されます。reactiveではない変数では画面アクセス時には変数に設定した文字列を表示することはできますがinput要素に入力した値で変数の値を更新することができないことがわかりました。

reactiveではない変数の動作が理解できたので次は変数messageをref関数を利用してreactiveな変数に変更します。ref関数で定義した場合は変数の値にアクセスする場合は.valueが必要となるためconsole.logの中はmessageではなくmessage.valueになっています。templateタグ内の変更はありません。


<script setup>
import { ref } from 'vue';
const message = ref('Hello World');

const clickButton = () => {
  console.log(message.value);
};
</script>

最初の画面は変わりませんがinput要素のHello Worldを変更してみてください。上部の文字が変更に合わせてリアルタイムで更新されることが確認できます。”Hello World”から”Hello Beginner”に変更すると下記のように表示されます。

input要素の中の文字列を更新
input要素の中の文字列を更新

変更後Clickボタンをクリックしてください。デベロッパーツールのコンソールには”Hello Beginner”が表示されます。v-modelディレクティブを利用することでinput要素で入力した値とreactiveな変数を同期させることができることがわかりました。

v-modelディレクティブは実は元の形は以下のようにvalueにv-bindを設定しinputイベントを利用して文字を入力する度にinput要素に入力した値を$eventオブジェクトから取得して設定しています。下記の形はv-modelディレクティブを利用した場合に比べコードが長く難しく感じされるかもしれませんがどのような動作が行われているか理解する上ではこちらの書き方があることを知っておくことは重要です。


<input :value="message" @input="message = $event.target.value" />

reactive関数を利用しても同じように設定ができることを確認します。reactive関数では引数にオブジェクトで設定を行います。下記の設定でref関数と同様の動作を行います。


<script setup>
import { reactive } from 'vue';

const form = reactive({
  message: 'Hello World',
});

const clickButton = () => {
  console.log(form.message);
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <p>{{ form.message }}</p>
  <input v-model="form.message" />
  <div><button @click="clickButton">Click</button></div>
</template>
v-modelディレクティブを利用している場合にinput要素にvalue属性を設定してエラーになるため利用することはできません。placehoder属性を利用することはできます。

修飾子(Modifiers)

v-modelには修飾子を設定することができ修飾子を利用することでv-modelの動作を変更することができます。

3つの修飾子について説明を行っていきます。

  • lazy
  • trim
  • number

input要素に文字を入力すると即時にreactiveな変数へ反映されていましたが修飾子をlazyをつけることで文字の入力毎ではなくinput要素からカーソルを外した場合に変更が反映されるようになります。v-modelの内部ではinputイベントを利用していますがlazyをつけることでchangeイベントに変更されるためです。


<input v-model.lazy="reactiveな変数名" />

input要素の先頭や最後に空白がある場合取り除きたい場合に修飾子のtrimをつけることで自動で取り除いてくれます。文字と文字の間にある空白は削除されることはありません。JavaScriptのtrim関数と同じ処理を行います。

先頭に空白を入れてカーソルを外すと先頭の空白が自動で削除されます。


<input v-model.trim="reactiveな変数名" />

Vue.jsに限らずJavaScriptではinput要素に入力した値を取得すると文字列として扱うため数字とし取得したい場合は修飾子のnumberを設定することで数値として取得します。typeをnumberに設定している場合はVueが自動でnumberに変換してくれるのでtypeがtextの場合に利用することができます。


<input v-model.number="reactiveな変数名" type="text" />

number修飾子をつけずにinput要素で数字を入れると”string”と表示されますがnumber修飾子をつけると”number”と表示されます。typeがtextになっていることを注意してください。typeの設定値がnumberの場合はnumber修飾子はをつけなくても”number”と表示されます。typeofはJavaScriptの関数でタイプを確認するために利用することができます。TypeScriptでも型のチェックに利用するので初めてみた人は覚えておきましょう。


<script setup>
import { reactive } from 'vue';
const form = reactive({
  message: 0,
});

const clickButton = () => {
  console.log(typeof form.message);
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <p>{{ form.message }}</p>
  <input v-model="form.message" type="text" />
  <div>
    <button @click="clickButton">Click</button>
  </div>
</template>

Computedプロパティ

Computedプロパティは定義済みの変数を利用して計算、加工を行うことで元のデータとは異なる形でユーザに表示することができる機能です。reactiveな変数を利用してComputedプロパティを定義した場合はreactiveな変数が更新されるとその更新に合わせて再計算、再加工が自動で行われます。

Computedプロパティを利用しない場合とした場合を比較してComputedプロパティの理解を深めていきます。

reactive関数を利用してuserを定義してfirstNameとlastNameをtemplateタグに設定してブラウザ上に表示させます。


<script setup>
import { reactive } from 'vue';
const user = reactive({
  firstName: 'John',
  lastName: 'Doe',
});
</script>

<template>
  <h1>Vue 3 入門</h1>
  <h2>fullName: {{ user.firstName }} {{ user.lastName }}</h2>
</template>

ブラウザ上には設定通りにfullNameが表示されます。

Computedプロパティ
Computedプロパティ

これをcomputedプロパティを使って表示させます。computedプロパティを利用するためにはvueからcomputed関数をimportする必要があります。importしたcomputed関数の引数に関数を記述します。関数の戻り値がtemplateタグの中で設定したfullNameに表示されます。表示される内容はcomputedプロパティを使う前と変わりません。


<script setup>
import { reactive, computed } from 'vue';
const user = reactive({
  firstName: 'John',
  lastName: 'Doe',
});

const fullName = computed(() => `${user.firstName} ${user.lastName}`);
</script>

<template>
  <h1>Vue 3 入門</h1>
  <h2>fullName: {{ fullName }}</h2>
</template>

Computedプロパティは定義済みの変数を利用して加工して表示することができると説明した通りの動作になっていることが確認できました。この例はシンプルなのでComputedプロパティを利用することによる恩恵は少ないかもしれませんがfullNameという名前を定義することでどのような処理を行なっているかもすぐにわかりますまた何度もtemplate内で利用したい場合にcomputedプロパティのfullNameと記述したほうがコードも短い上、表示の内容を変更したい場合(すべての文字を大文字にするなど)もfullNameの中身を1箇所変更することで対応することができます。

scriptタグでcomputedプロパティにアクセスしたい場合はref関数と同様にvalueをつける必要があるので注意してください。

リストと分岐の動作確認ではv-forディレクティブとv-ifディレクティブを利用してusers情報からadminがtrueのユーザのみ表示する設定を確認しましたがComputedプロパティを利用することでadminユーザのみ表示させることができます。

ComputedプロパティadminUsersの中でfilter関数を利用してadminがtrueのユーザのみ取得します。v-forディレクティブの中ではComputedプロパティadminUsersを利用して繰り返し処理を行っています。


<script setup>
import { computed } from 'vue';
const users = [
  { id: 1, name: 'John Doe', email: 'john@test.com', admin: true },
  { id: 2, name: 'Jane Doe', email: 'jane@example.com', admin: false },
  { id: 3, name: 'Kevin MacDonald', email: 'kevin@test.com', admin: false },
];

const adminUsers = computed(() => users.filter((user) => user.admin === true));
</script>

<template>
  <h1>Vue 3 入門</h1>
  <div v-for="user in adminUsers" :key="user.id">
    <div>{{ user.id }} {{ user.name }} {{ user.email }}</div>
  </div>
</template>

adminの値がtrueのユーザのみ表示されます。定義済みの変数をComputedプロパティで加工することでユーザに表示しています。adminUsersという名前をつけているので名前からどのような情報を設定していることも理解することができます。

adminユーザのみv-forで表示
adminユーザのみv-forで表示

filter関数内で条件式を変更するだけでadminではないユーザを取得することができます。


const generalUsers = computed(() => users.filter((user) => user.admin === false));

Computed vs Function

Computedプロパティで記述した内容は関数を使っても以下のように記述することができます。マスタッシュの中で関数を利用する場合はComputedプロパティと異なり関数の名前の後ろに()が必要です。Computedプロパティと関数で同じことができるのであればどちらの機能を利用する必要がありません。


<script setup>
import { reactive, computed } from 'vue';
const user = reactive({
  firstName: 'John',
  lastName: 'Doe',
});

const fullName = () => `${user.firstName} ${user.lastName}`;
</script>

<template>
  <h1>Vue 3 入門</h1>
  <h2>fullName: {{ fullName() }}</h2>
</template>

しかしComputedプロパティと関数には大きな違いがあり、Computedプロパティはキャッシュ機能を持っています。キャッシュ機能がどのようなものかComputedプロパティと関数を利用して確認を行っていきます。

関数とComputedプロパティを同時に実行するために新たにComputedプロパティのcFullName関数を追加します。


const fullName = () => `${user.firstName} ${user.lastName}`;
const cFullName = computed(() => `${user.firstName} ${user.lastName}`);

Computedプロパティのキャッシュ機能を確認するためにMath.random()関数は追加します。Math.random()関数は実行するとランダムな数字を戻すという関数です。


const fullName = () => `${Math.random()} ${user.firstName} ${user.lastName}`;
const cFullName = computed(
  () => `${Math.random()} $${user.firstName} ${user.lastName}`
);

キャッシュ機能を確認するためにtempalteタグの中で複数の関数とcomputedプロパティを実行させます。


<template>
  <h1>Vue 3 入門</h1>
  <h2>fullName: {{ fullName() }}</h2>
  <h2>fullName: {{ fullName() }}</h2>
  <h2>fullName: {{ fullName() }}</h2>
  <h2>cFullName: {{ cFullName }}</h2>
  <h2>cFullName: {{ cFullName }}</h2>
  <h2>cFullName: {{ cFullName }}</h2>
</template>

開くと下記のようにfullName関数ではすべてのfullName関数の実行でMath.randam関数が実行されるため異なる値が表示されていますがComputedプロパティのcFullNameはキャッシュ機能を持っているため一度Math.randam関数が実行されるだけで後は同じ値を持っていることがわかります。

Computedプロパティのキャッシュ機能の確認
Computedプロパティのキャッシュ機能の確認

Computedプロパティはreactiveな変数の更新を反映する際に再実行されるのでinput要素のv-modelディレクティブにuser.firstNameを設定してどのような変化が起こるか確認します。


<input v-model="user.firstName" />

文字を入力、削除する度に値が変更になりますがComputedプロパティのMath.randamの値はすべてのcfullNameで一緒であることがわかります。fullNameは文字を変更する度にすべてのfullNameでMath.randomが実行されています。

input要素を利用してreactiveな変数を更新
input要素を利用してreactiveな変数を更新

さらにキャッシュ機能を確認するためにComputedプロパティと関数にconsole.logを設定してどのタイミングで実行されている確認します。実行された場合にはデベロッパーツールに指定した文字列が表示れます。


const fullName = () => {
  console.log('Function');
  return `${user.firstName} ${user.lastName}`;
};
const cFullName = computed(() => {
  console.log('Computed Propety');
  return `${user.firstName} ${user.lastName}`;
});

定義したreactiveな変数のuserのfirstNameを変更するとどちらも再実行されることは確認済みなので別のreactiveな変数countをref関数で定義します。定義したcountの数をclickイベントを利用して更新します。


<script setup>
import { ref, reactive, computed } from 'vue';
const count = ref(0);

const user = reactive({
  firstName: 'John',
  lastName: 'Doe',
});

const fullName = () => {
  console.log('Function');
  return `${user.firstName} ${user.lastName}`;
};
const cFullName = computed(() => {
  console.log('Computed Propety');
  return `${user.firstName} ${user.lastName}`;
});
</script>

ページを開くとコンソールには”FunctionをComputed Property”のメッセージが表示されます。その後ボタンをクリックするとFunctionのみメッセージが表示されます。下記の画像ではClickを6回行った場合にFunctionsの左側に6が表示されていることが確認できます。ComputdプロパティはComputedプロパティに関連のないreactiveの変数の影響をうけないことがわかりました。

countボタンをクリックするとFunctionは再実行
countボタンをクリックするとFunctionは再実行

Computedプロパティと関数で同じ処理ができたとしても違いを理解した上でどちらを利用するか決める必要があります。

Computed セッター

scriptタグの中ではcomputedプロパティの値にアクセスする場合には.valueが必要であることを説明しました。.valueを利用してComputedプロパティを更新することができるのか確認します。

clickイベントを持つボタンを追加してボタンをクリックするとchangeName関数を実行するように設定します。changeName関数では.valueを使って別の名前に更新を行います。


<script setup>
import { reactive, computed } from 'vue';

const user = reactive({
  firstName: 'John',
  lastName: 'Doe',
});

const fullName = computed(() => `${user.firstName} ${user.lastName}`);

const changeName = () => {
  fullName.value = 'Jane Doe';
};
</script>

<template>
  <h1>Vue 3 入門</h1>
  <h2>fullName: {{ fullName }}</h2>
  <button @click="changeName">Change Name</button>
</template>

ボタンをクリックするとデベロッパーツールのコンソールには”Write operation failed: computed value is readonly”が表示され書き込み処理が失敗、ComputedプロパティはReadonlyだというメッセージです。Computedプロパティの値は直接は更新できないことがわかりました。

Computedプロパティを使って更新を行いたい場合は直接ではなくSetterを利用する必要があります。下記がSetterを利用してComputedプロパティを更新するためコードです。


const fullName = computed({
  get() {
    return `${user.firstName} ${user.lastName}`;
  },
  set(newValue) {
    const names = newValue.split(' ');
    user.firstName = names[0];
    user.lastName = names[names.length - 1];
  },
});

getメソッドはComputedプロパティにアクセスして表示する場合に実行されるメソッドなので処理の内容は先ほどのComputedプロパティでの設定と同じです。setメソッドは新しい値newValueを受け取りreactiveな変数userのfirstNameとlastNameに受け取ったnewValue(ここではJane Doe)を空白で分割してそれぞれに設定しています。

changeNameボタンをクリックするとComputedプロパティを経由して更新が行われ”John Doe”からJane Doe”となります。Setterを利用することでComputdプロパティが更新できることがわかりました。

ComputedプロパティのSetterでReactiveな変数を更新
ComputedプロパティのSetterでReactiveな変数を更新

Watcher

watcherを利用することでreactiveな変数、computedプロパティの変更を監視し、変更を検知した場合に別の処理を実行することができます。

watcher + ref

ref関数で作成したreactiveな変数countを監視してcountが更新されたら更新された後の値を表示するように設定を行います。


<script setup>
import { ref, watch } from 'vue';

const count = ref(0);

watch(count, (count) => {
  console.log('count:', count);
});
</script>

<template>
  <h1>Vue 3 入門</h1>
  <button @click="count++">Count:{{ count }}</button>
</template>

Countのボタンを押す度にwatcherで変更を検知し、デベロッパーツールのコンソールにcount数が表示されます。

watcherでは変更後の値だけではなく変更前の値も確認することができます。第一引数に変更後の値、第二引数に変更前の値が渡されます。


watch(count, (count, previousCount) => {
  console.log('count:', count);
  console.log('previousCount:', previousCount);
});

Countボタンをクリックすると変更前の値と変更後の値がコンソールに表示されます。

watcher + reactive

reactive関数を使って定義したcountに対してもwatcherの動作確認を行っておきます。監視対応にstate.countを設定します。


<script setup>
import { reactive, watch } from 'vue';

const state = reactive({
  count: 0,
});

watch(state.count, (count, previousCount) => {
  console.log('count:', count);
  console.log('previousCount:', previousCount);
});
</script>

<template>
  <h1>Vue 3 入門</h1>
  <button @click="state.count++">Count:{{ state.count }}</button>
</template>

ページを開くと下記のメッセージがデベロッパーツールのコンソールに表示されwatchのデータソースとしてstate.countが利用できないことがわかります。


[Vue warn]: Invalid watch source:  0 A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.

reactive関数のオブジェクトのプロパティを監視する場合は下記の関数の形に変更することでstate.countの監視が可能になります。Countボタンをクリックすると変更前の値と変更後の値がコンソールに表示されます。


watch(() => state.count, (count, previousCount) => {
  console.log('count:', count);
  console.log('previousCount:', previousCount);
});

watcherのOptions

reactive関数で定義したstateをwatchに設定してcountを変更した場合の動作も確認しておきます。下記の設定を行ってもコンソールには何も表示されません。


watch(
  () => state,
  (state, previousState) => {
    console.log('state:', state);
    console.log('previousState:', previousState);
  }
);

stateをwatcherに設定して中のオブジェクトの変更を検知するためにoptionsのdeepをtrueに変更する必要があります。deepの設定値をtrueに変更すると変更は検知することができますがcountの変更前の値は取得することはできません。


watch(
  () => state,
  (state, previousState) => {
    console.log('state:', state);
    console.log('previousState:', previousState);
  },
  { deep: true }
);

watchの第一引数にstateをそのまま設定するとオプションのdeepをtrueに設定していなくてもcountの変更を検知してくれます。この場合もcountの変更前の値は取得することはできません。


watch(
  state,
  (state, previousState) => {
    console.log('state:', state);
    console.log('previousState:', previousState);
  },
  { deep: true }
);

watcherの処理をページを開いた直後に実行しておきたい場合はオプションにimmediate:trueを設定することで可能です。デフォルトではfalseが設定されています。


watch(
  () => state.count,
  (count, previousCount) => {
    console.log('count:', count);
    console.log('previousCount:', previousCount);
  },
  { immediate: true }
);

初期値は0でpreviousCountの値はないのでコンソールには下記が表示されます。


count: 0
previousCount: undefined

watchEffect

reactiveな変数とcomputedプロパティの変更の検知はwatcherだけではなくwatchEffectでも行うことができます。

watchのように特定の変数を指定するのではなくwatchEffectの中に記述した関数で記述されている変数の変更を検知して実行されます。

下記のようにref関数でcount, count2を定義します。watchEffect関数の中ではcountのみ利用します。2つボタンを設定して一つはcountの変更、もう一つはcount2の変更を行います。Countのボタンをクリックした場合にはwatchEffectの関数が実行され、Count2をクリックしても何も行われません。


<script setup>
import { ref, watchEffect } from 'vue';

const count = ref(0);
const count2 = ref(100);

watchEffect(() => console.log(count.value));
</script>

<template>
  <h1>Vue 3 入門</h1>
  <button @click="count++">Count:{{ count }}</button>
  <button @click="count2++">Count2:{{ count2 }}</button>
</template>

watchEffectではページを開いた時にも一度処理が実行されます。

両方の変数をwatchEffectに追加するとどちらのボタンをクリックしてもwatchEffectの関数が実行されるようになります。


watchEffect(() => console.log(`${count.value}/${count2.value}`));

続きはコンポーネント編で公開しています。