Lesson 3

Vue プログラミングの基本

Lesson 3 Chapter 1
Vue の仕組み

このレッスンでは、Vue プログラミングの基本について学んでいきます。
まずは、Chapter 1 として「Vue の仕組み」について大枠を確認していきましょう。

1. 使用するサンプル

サンプルとして、次のような Vue アプリケーションを見ていきます。
手元で動作確認をしたい場合は、以下の「コードを表示」からコピーしてください。

コードを表示

Lesson3 というフォルダを追加して、次のような chapter1.html というファイルを作成します。

lesson3/chapter1.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>Counter: {{ counter }}</div>
    <button v-on:click="increment">increment</button>
  </div>

  <script>
    const { createApp, ref } = Vue

    const RootComponent = {
      setup() {
        const counter = ref(0)
        function increment() {
          counter.value++
        }
        return {
          counter,
          increment
        }
      }
    }

    createApp(RootComponent).mount('#app')
  </script>
</body>
</html>

vue_3-1-1.png

サンプルの赤枠を上から見ていくと「Vue インストール」「テンプレート」「アプリケーションインスタンス」の 3 つに区分できます。
処理の順番としては、まず、Vue インストールして、⑴ アプリケーションインスタンスを生成し、⑵ テンプレートから HTML を生成する、という流れになります。

2. アプリケーションインスタンス

Vue アプリケーションは createApp 関数で新しい アプリケーションインスタンスを作成するところから始まります。
具体的には、Vue フレームワークが提供する枠組みに、アプリケーションで必要なデータやメソッド(関数)を格納して、実体(インスタンス)を作成するという流れになります。

① インポート

下記コードにある Vue は、 Vue インストールの「src="https://unpkg.com/vue@3.2.36"」の部分から取得されます。

const { createApp, ref } = Vue

この Vue には、様々な関数が含まれています。
ここでは、createAppref の 2 つの関数をインポートしています(実際はインポートではないですが、便宜上インポートと呼んでいます)。

② ルートコンポーネント

createApp 関数で読み込まれている以下のルートコンポーネントは、アプリケーションをマウントする際に、レンダリングの起点として使われます。

const RootComponent = {
  setup() {
    const counter = ref(0)
    function increment() {
      counter.value++
    }
    return {
      counter,
      increment
    }
  }
}

具体的に言うと、実際のアプリケーションは数多くのコンポーネントで構成されますので、その際に、最初に読み込まれるのがこのルートコンポーネントとなります。
Lesson 2 では、data() から、プロパティ(変数)を return していましたが、ここでは、setup() 関数というものを使用しています。

setup() 関数

Vue 3 からは、標準で setup() 関数を使用してその中に変数やメソッドを定義することになります(data() は、主に Vue 2 で使われていた記述です)。
以下、記載されている内容を簡単に説明します(詳細は、本レッスンの Chapter 4 で学びます)。

① ref() 関数(リアクティブな変数を定義する)

次のところで、counter というプロパティ(変数)を定義しています。

const counter = ref(0)

一般的な const counter = 0 という定義では、counter の値が変化しても、画面(テンプレート)に反映されません。
ref() 関数を使用することで、counter の値の変化を即座に画面(テンプレート)に反映することができるようになります。

② function(関数を定義する)

以下では、increment という関数を定義しています。

function increment() {
  counter.value++
}

なお、ref() 関数で定義した、counterの値を取得したり変更したりする場合は、value プロパティを介して操作する必要があります。

③ return(プロパティを返す)

return に指定した内容は、コンポーネントの戻り値となり、画面(テンプレート)側でそのプロパティ(変数、関数)を受け取ることができるようになります。

return {
  counter,
  increment
}

③ アプリケーションインスタンスの生成

最後に、アプリケーションインスタンスの生成部分を確認しておきましょう。

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

まず、RootComponent を引数にとって、createApp 関数で、アプリケーションインスタンスが生成されています。 それに続く .mount('#app') で、このアプリケーションインスタンスを、テンプレート側の id="app" の DOM 要素にマウントしています。
これにより、アプリケーションインスタンスで定義された変数や関数が、HTML テンプレート側に紐づけられることになります。

3. テンプレート

次に、テンプレートのところを見ていきます。

3-1. テンプレート構文とは

<div id="app">
  <div>Counter: {{ counter }}</div>
  <button v-on:click="increment">increment</button>
</div>

Vue で HTML ファイルを生成(描画)するためには「テンプレート構文」というものを使用します。
上のように、基本的には、HTML と同じ形式で書くことができ、かつ、{{ counter }}v-on のような Vue 特有の構文を使用して、JavaScript のデータを HTML 側と紐づける(バインドする※)ことができます。
なお、変数の値を表示する {{}}(二重中括弧)による記法をマスタッシュ構文と呼んでいます。

バインド/バインディングとは

バインド(bind)は、日本語に訳すと、結びつける、関連付けるなどの意。
Vue においては、データなどが相互に関連付けられている状態を指す。

・少し複雑なテンプレート構文の例

テンプレート構文のイメージを掴むために、もう少し、具体的な例を見てみましょう。
以下は概要を見るための例ですので、特にご自身で入力をする必要はありません。

<div id="app">
  <div>{{ message }}</div>
  <div><input v-bind:value="message" v-on:input="inputMessage"/></div>
  <p>
    <button v-on:click="switchMessageDisplay">表示・非表示</button>
    <span v-if="isShowMessage"> こんにちは</span>
  </p>
  <ul>
    <li v-for="user in users" v-bind:key="user.id">{{ user.name }} {{ user.age }} 歳</li>
  </ul>
</div>
全てのコードを表示
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>{{ message }}</div>
    <div><input v-bind:value="message" v-on:input="inputMessage"/></div>
    <p>
      <button v-on:click="switchMessageDisplay">表示・非表示</button>
      <span v-if="isShowMessage"> こんにちは</span>
    </p>
    <ul>
      <li v-for="user in users" v-bind:key="user.id">{{ user.name }} {{ user.age }} 歳</li>
    </ul>
  </div>

  <script>
    const { createApp, ref } = Vue
    // setup 関数で記述
    createApp({
      setup() {
        const message = ref('Hello World!')
        const isShowMessage = ref(true)
        const users = [
          {
            id: 1,
            name: '山田太郎',
            age: 27
          },
          {
            id: 2,
            name: '田中花子',
            age: 32
          },
          {
            id: 3,
            name: '鈴木三郎',
            age: 21
          }
        ]
        const switchMessageDisplay = () => {
          isShowMessage.value = !isShowMessage.value
        }
        const inputMessage = (event) => {
          message.value = event.target.value
        }
        return {
          message,
          isShowMessage,
          users,
          switchMessageDisplay,
          inputMessage
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

上の例では、<div><input><p><button><span> などを用いて、一般の HTML 形式(HTML5)で記述をしています。
これに加えて、{{}} や、v-bindv-onv-ifv-for などのテンプレート構文を使用して、HTML と JavaScript との間で、指定する値やメソッド(関数)をバインディング(紐づけ)しています。

なお、v- ではじまる Vue 特有の属性は「ディレクティブ」と呼ばれ、値が変化したときにリアクティブにデータを伝達する役割を持っています。
このテンプレート構文をうまく活用することにより、画面上に様々な表示や動きを与えることができるようになります。

3-2. 主なディレクティブの一覧

Vue で使用できるディレクティブには、主に次のようなものがあります。

No ディレクティブ 説明
1 v-bind HTML 属性をバインドする。
2 v-on クリック等のアクションに対するイベント処理を行う。
3 v-if,v-else-if,v-else 条件分岐を行う。
4 v-for ループ処理を行う。
5 v-model 双方向バインディングを行う。
6 v-text 要素の textContent を更新する(指定したテキストが表示される)。
7 v-html 要素の innerHTML を更新する(指定した HTML が表示される)。
8 v-show 要素の表示・非表示を制御する。
9 v-pre 要素内のテキストをそのまま表示する。
10 v-cloak コンパイル完了まで要素に残る。
11 v-once 要素を 1 度だけ描画する。

ディレクティブについては、Lesson 4 で詳細を学習する予定です。

4. Vue のバージョンについて

現在の Vue においては Vue 2Vue 3 の 2 つのメジャーバージョンがサポートされています。 この 2 つのバージョンの違いを大雑把に言えば、以下のように、コンポーネント形式が異なるという点にあります。
(※以下、少々難しい話となりますので、何となくの理解で大丈夫です。)

① Options API(Vue 2)

Vue 2 は Options API を使用しており、次のような書き方になります(※)。

const app = new Vue({
  el: '#app',
  data() {
    return {
      unitPrice: 200,
      quantity: 0
    }
  },
  methods: {
    add() {
      this.quantity++
    }
  },
  computed: {
    payment() {
      return this.unitPrice * this.quantity
    }
  }
})

大きな特徴としては、data() で定義した変数を、methods:(メソッド) などで使用する際は this を介して使用する必要がありました。 これにより、メソッド等をView(テンプレート)と切り離して使用することができないという問題がありました。

② Composition API(Vue 3)

Vue 3 は Composition API を使用しており、次のように、setup() の中に処理をまとめて書く形式となっています。

Vue.createApp({
  setup() {
    const unitPrice = 200
    const quantity = ref(0)
    const payment = computed(() => unitPrice * quantity.value)
    const add = () => quantity.value++
    return {
      unitPrice,
      quantity,
      payment,
      add
    }
  }
}).mount('#app')

Composition API においては this を使用しないため、各ロジックをコンポーネントから独立して実行することができます。 これにより、ロジックをカプセル化して再利用しやすくなるとともに、見通しの良いコードを書くことができるようになりました。

なお、Vue 3 ではデフォルトで TypeScript に対応しているなどの改良もされており、処理速度も早くなっています。

Vue 2 の最新バージョン「Vue 2.7」

2022 年 7 月に公開された Vue 2.7 では、Vue 3 の Composition API が使用できるようになっています。 そのため「Vue 2 = Options API」とは単純に言えなくなっています。

このカリキュラムでは、Vue 2 = Options API、Vue 3 = Composition API というように話を単純化して解説を行っていますので、その点をあらかじめご留意ください。

2022 年 2 月以降は Vue 3 がデフォルトバージョンとされていることから、本カリキュラムでは、基本的には Vue 3(Composition API)をベースに解説を行っていきます。

Lesson 2 Chapter 2
テキスト・HTMLの表示

このチャプターでは、Vue でテキストや HTML を表示する方法について学習していきます。

1. テキスト表示(マスタッシュ構文)

最も基本の形式は、「マスタッシュ構文」(二重中括弧) によるテキスト展開です。
「マスタッシュ」とは口ひげを意味します。そのイメージ通りに、髭のような二重中括弧 {{}} をテンプレート内に配置することで利用します。

1-1. マスタッシュ構文の使用例

それでは、lesson3 フォルダに、chapter2-1.html というファイルを作成して、<body> タグ内に、次のように記述してみましょう。

<body>
<script src="https://unpkg.com/vue@3.2.36"></script>

<div id="app">
  {{ message }}
</div>

<script>
  const { createApp } = Vue

  const App = {
    setup() {
      return {
        message: 'りんご'
      }
    }
  }

  createApp(App).mount('#app')
</script>
</body>
全てのコードを表示
chapter2-1.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    {{ message }}
  </div>

  <script>
    const { createApp } = Vue

    const App = {
      setup() {
        return {
          message: 'りんご'
        }
      }
    }

    createApp(App).mount('#app')
  </script>
</body>
</html>

これを、ブラウザで表示すると次のようになります。
setup() で指定した message: 'りんご' という変数を、{{ message }} のところで受け取り、ブラウザ上に「りんご」というように表示されています。

vue_3-2-1.png

マスタッシュ構文とは、この例のように、テキストデータをバインディングするものであり「データバインディング」をする記法のうちのひとつです。

このデータバインディングは HTML 属性や CSS に対しても行うことができますが、のちのチャプターで解説していきます。

1-2. javascript 式の利用

マスタッシュ構文の中では、javascript 式(単一式※)を利用することができます。

lesson3 フォルダに、chapter2-2.html というファイルを作成して、<body> タグ内に、次のように記述してみてください。

<script src="https://unpkg.com/vue@3.2.36"></script>

<div id="app">
  <p>{{ price + 300 }}</p>
  <p>{{ price >= 100 ? 'high' : 'low' }}</p>
</div>

<script>
  const { createApp } = Vue

  const App = {
    setup() {
      return {
        price: 200
      }
    }
  }

  createApp(App).mount('#app')
</script>
全てのコードを表示
chapter2-2.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <p>{{ price + 300 }}</p>
    <p>{{ price >= 100 ? 'high' : 'low' }}</p>
  </div>

  <script>
    const { createApp } = Vue

    const App = {
      setup() {
        return {
          price: 200
        }
      }
    }

    createApp(App).mount('#app')
  </script>
</body>
</html>

{{ price + 300 }}では、200300が足された 500 が表示されます。
また、{{ price >= 100 ? 'high' : 'low' }} では price100 以上であれば 'high' が、 小さければ 'low' が返される三項演算子が使われており、この場合は「high」と表示されます。

ブラウザで表示すると次のようになります。

vue_3-2-2.png

なお、Vue 公式のスタイルガイドでは、マスタッシュ構文内で複雑な式を用いることは、極力避けるべきこととされています。
複雑な JavaScript 式は、アプリケーションインスタンスの setup() 関数内で指定するなどして、見通しの良いコードとするように心がけることが必要です(Chapter 5 の「算出プロパティ」参照)。

マスタッシュ構文で使用できる javascript 式

マスタッシュ構文内に記述できる javascript は「単一式」のみです。
単一式とは、変数に代入可能な式となります。よって以下のような書き方はできません。

<div id="app">
  <p>{{ if (price === 200) { return price } }}</p>
</div>

2. テキスト表示(v-text ディレクティブ)

テキスト表示には v-text ディレクティブを使用する方法もあります。
使い方は次のとおりで、テキストを表示したい要素内に v-text="[変数名]" という形で記述します。

<div id="app">
  <span v-text="message"></span>
</div>

<script>
  const { createApp } = Vue

  const App = {
    setup() {
      return {
        message: 'りんご'
      }
    }
  }

  createApp(App).mount('#app')
</script>

lesson3 フォルダに、chapter2-3.html というファイルを作成して、実際に書いてみましょう。

全てのコードを表示
chapter2-3.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <span v-text="message"></span>
  </div>

  <script>
    const { createApp } = Vue

    const App = {
      setup() {
        return {
          message: 'りんご'
        }
      }
    }

    createApp(App).mount('#app')
  </script>
</body>
</html>

これを、ブラウザで表示すると次のようになります。
マスタッシュ構文の時と同じ結果ですね。

vue_3-2-1.png

基本的には、テキスト表示にはマスタッシュ構文を使えば良いのですが、このような書き方も用意されていることは把握しておきましょう。

3. HTML表示(v-html ディレクティブ)

HTML を使用して文字列に色を付けたりリンクを貼ったりしたい場合もあると思います。
そのような場合は、v-html ディレクティブを使用します。
使い方は、次のように、表示をしたい要素内に v-html="[変数名]" という形で記述します。

<div id="app">
  <span v-html="message"></span>
</div>

<script>
  const { createApp } = Vue

  const App = {
    setup() {
      const message = 'Google検索は<a href="https://www.google.com/">こちら</a>'
      return {
        message
      }
    }
  }

  createApp(App).mount('#app')
</script>

表示したい HTML は次の部分です。

const message = 'Google検索は<a href="https://www.google.com/">こちら</a>'

lesson3 フォルダに、chapter2-4.html というファイルを作成して、実際に書いてみましょう。

全てのコードを表示
chapter2-4.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <span v-html="message"></span>
  </div>

  <script>
    const { createApp } = Vue

    const App = {
      setup() {
        const message = 'Google検索は<a href="https://www.google.com/">こちら</a>'
        return {
          message
        }
      }
    }

    createApp(App).mount('#app')
  </script>
</body>
</html>

これを、ブラウザで表示すると次のようになります。
指定した <a href="https://www.google.com/">こちら</a> が HTML として読み取られているのが分かると思います。 リンクをクリックすると、Google の検索ページに移動します。

vue_3-2-3.png

なお、v-html に指定する値は、信頼できるもののみとしてください(※)。

v-html は信頼できる値でのみ使用する

v-html ディレクティブには、内部の信頼できる値のみを使用するようにしてください。
ユーザーから提供された値を Web ページの表示に使用すると、クロスサイトスクリプティング(XSS)攻撃を容易に招くこととなり、非常に危険です。

Lesson 3 Chapter 3
オブジェクト・配列の要素表示

このチャプターでは、オブジェクトや配列の値を画面で表示する方法を学んでいきます。

1. オブジェクトの表示

1-1. オブジェクトの表示方法

まず、オブジェクトを表示する方法から見ていきます。
マスタッシュ構文内でオブジェクトのプロパティ(要素)を表示するには、次の 2 つの方法があります。

  • ドット記法 : user.name という形式でプロパティの値を取得
  • ブラケット記法 : user['name'] という形式でプロパティの値を取得
これは、javascript でオブジェクトのプロパティにアクセスする方法と何ら変わりません。

実際にコードを書くと次のようになります。
冒頭の方にある、<p> タグの 2 行に着目してください。

<div id="app">
  <p>私の名前は{{ user.name }}、{{ user.age }}歳です。</p>
  <p>私の名前は{{ user['name'] }}、{{ user['age'] }}歳です。</p>
</div>

<script>
  const { createApp } = Vue

  const App = {
    setup() {
      const user = {
        name: '太郎',
        age: 27
      }
      return {
        user
      }
    }
  }

  createApp(App).mount('#app')
</script>

なお、オブジェクトの作成は、setup() 関数の中で行っています。これも、一般的な JavaScript と書き方は変わりません。

const user = {
  name: '太郎',
  age: 27
}

1-2. コードを書いて実行する

それでは、lesson3 フォルダに、chapter3-1.html というファイルを作成して実際に自分で書いていきましょう。
全体のコードを参照したい場合は、次のところから確認してください。

全てのコードを表示
chapter3-1.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <p>私の名前は{{ user.name }}、{{ user.age }}歳です。</p>
    <p>私の名前は{{ user['name'] }}、{{ user['age'] }}歳です。</p>
  </div>

  <script>
    const { createApp } = Vue

    const App = {
      setup() {
        const user = {
          name: '太郎',
          age: 27
        }
        return {
          user
        }
      }
    }

    createApp(App).mount('#app')
  </script>
</body>
</html>

これを、ブラウザで表示すると次のようになります。
ドット記法、ブラケット記法のどちらでも、同じ結果を得ることができています。

vue_3-3-1.png

2. 配列の要素の表示

2-1. 配列の要素の表示方法

続いて、配列の要素を表示する方法について見ていきます。
マスタッシュ構文内で配列の要素にアクセスするにはブラケット [] を使用して、 アクセスする要素のインデックス番号を users[0] の振り合いで記載します。
こちらも、javascript でアクセスする方法と何ら変わりません。

コードを書くと次のようになります。

<div id="app">
  <ul>
    <li>{{ users[0] }}</li>
    <li>{{ users[1] }}</li>
    <li>{{ users[2] }}</li>
  </ul>
</div>

<script>
  const { createApp } = Vue

  const App = {
    setup() {
      const users = ['山田太郎', '田中花子', '鈴木三郎']
      return {
        users
      }
    }
  }

  createApp(App).mount('#app')
</script>

配列の作成は、setup() 関数の中で行っています。これも、一般的な JavaScript と書き方は変わりません。

const users = ['山田太郎', '田中花子', '鈴木三郎']

2-2. コードを書いて実行する

それでは、lesson3 フォルダに、chapter3-2.html というファイルを作成して実際に自分で書いていきましょう。
全体のコードを参照したい場合は、次のところから確認してください。

全てのコードを表示
chapter3-2.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <ul>
      <li>{{ users[0] }}</li>
      <li>{{ users[1] }}</li>
      <li>{{ users[2] }}</li>
    </ul>
  </div>

  <script>
    const { createApp } = Vue

    const App = {
      setup() {
        const users = ['山田太郎', '田中花子', '鈴木三郎']
        return {
          users
        }
      }
    }

    createApp(App).mount('#app')
  </script>
</body>
</html>

これを、ブラウザで表示すると、次のように配列の内容を取得することができます。

vue_3-3-2.png

3. v-for ディレクティブを使用した表示方法

なお、v-for ディレクティブというものを使用すると、配列やオブジェクトの要素を 1 つずつ取り出し、並べて表示させることができます。
v-for ディレクティブについては、Lesson 4 で詳細を学習しますが、ここでも少し見ておきましょう。

3-1. v-for ディレクティブの基本構文

最低限の構文は次のようになります。

v-for="[変数名] in [配列またはオブジェクト名]"

このディレクティブを設定した HTML 要素は、配列(またはオブジェクト)の要素数だけ、繰り返し描画されます。

3-2. 実際のコードで確認する

実際のコードで見た方が分かりやすいと思います。
これは、配列に対して v-for ディレクティブを使用した例です。

<div id="app">
  <ul>
    <li v-for="user in users">{{ user }}</li>
  </ul>
</div>

<script>
  const { createApp } = Vue

  const App = {
    setup() {
      const users = ['山田太郎', '田中花子', '鈴木三郎']
      return {
        users
      }
    }
  }

  createApp(App).mount('#app')
</script>

v-for ディレクティブの指定は、以下の 1 行です。

<li v-for="user in users">{{ user }}</li>

users は、JavaScript 側(アプリケーションインスタンス)から渡された配列名です。
この配列の要素数は 3 つなので、<li> タグは 3 回分繰り返し描画されます。
users から取り出した要素は 1 つずつ user という変数で取得されます(変数名は何でも構いません)。
この取り出した要素を、順に {{ user }} で表示するという処理がされることになります。

3-3. コードを書いて実行する

それでは、lesson3 フォルダに、chapter3-3.html というファイルを作成して実際に自分で v-for ディレクトリを使用してみましょう。
全体のコードを参照したい場合は、次のところから確認してください。

全てのコードを表示
chapter3-3.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <ul>
      <li v-for="user in users">{{ user }}</li>
    </ul>
  </div>

  <script>
    const { createApp } = Vue

    const App = {
      setup() {
        const users = ['山田太郎', '田中花子', '鈴木三郎']
        return {
          users
        }
      }
    }

    createApp(App).mount('#app')
  </script>
</body>
</html>

ブラウザで表示すると、次のように配列の内容を取得することができます。
これは「配列の要素の表示」で実行した結果と同じものとなります。

vue_3-3-2.png

このチャプターでは、オブジェクトと配列の要素を表示させる方法について確認しました。
v-for ディレクトリの詳しい使い方については、また Lesson 4 で学習します。

Lesson 3 Chapter 4
データ定義とメソッド

このチャプターでは、setup() 関数内で行うデータの定義方法とメソッド(関数)の定義方法について詳しく見ていきます。
データの定義は、これまでの例でも行っていますので、先に、メソッドの定義方法から説明していきます。

1. メソッド

「メソッド」とはオブジェクト操作を定義するもので、一般に言う「関数」と考えて差し支えありません(※)。

メソッドと関数の違い

関数とは「入力に対して処理を行い出力を返すもの」となり、かなり意味が広いです。
メソッドも広義の関数に含まれますが「オブジェクト指向プログラミングにおいて、オブジェクトの操作(手続)を定義するもの」という意味合いで用いられます。

C言語のようにオブジェクト指向でない言語(手続型言語)では「メソッド」という言葉は使われず、一般に「関数」という言葉が使用されています。

1-1. メソッドの定義例

メソッドの定義方法について一例を挙げると次のとおりです。

<div id="app">
  <button v-on:click="increment">+ ボタン</button>
</div>

<script>
  const { createApp } = Vue

  createApp({
    setup() {
      let counter = 0
      function increment() {
        counter++
        console.log(counter)
      }
      return {
        increment
      }
    }
  }).mount('#app')
</script>

以下、内容を 1 つずつ確認していきます。

① メソッドの定義内容

メソッドの指定部分は、以下のところです。
JavaScript で関数を指定する場合と何ら変わりません。

function increment() {
  counter++
  console.log(counter)
}

最初の function でメソッド(関数)の定義を宣言し、メソッド名(関数名)は increment() で引数は持たない形です。
counter++ の記述で、実行時に counter の値が 1 加算されるようになっています。
そして、console.log(counter) で、counter の値を出力するようにしています。

② メソッドをテンプレート側に渡す

定義した increment メソッドは、次のように return 内に記述することで、HTML のテンプレート内で使用することが可能となります。

return {
  increment
}

③ テンプレート内でメソッドを実行する

HTML のテンプレートには <button> タグを使用してボタンを 1 つ作っています。

<div id="app">
  <button v-on:click="increment">+ ボタン</button>
</div>

<button> タグ内の v-on:click="increment" の記述(※)により、ボタンをクリックしたら increment 関数が実行されるようになっています。

v-on:click の構文

v-on:click の構文は次のとおりです。

v-on:click="[メソッド名]"

要素がクリックされた際に、指定したメソッド(関数)が実行されます。
ここで使用されている v-on ディレクティブについては、Lesson 4 で改めて学習します。

1-2. コードを書いて実行する

それでは、lesson4 フォルダを作成し、chapter4-1.html というファイルを追加して実際にコードを書いてみましょう。
全体のコードを参照したい場合は、次のところから確認してください。

全てのコードを表示
chapter4-1.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <button v-on:click="increment">+ ボタン</button>
  </div>

  <script>
    const { createApp } = Vue

    createApp({
      setup() {
        let counter = 0
        function increment() {
          counter++
          console.log(counter)
        }
        return {
          increment
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

① ブラウザで開く

ブラウザで表示すると、画面左上に「+ ボタン」というボタンが 1 つだけあります。
これだけでは、実行結果が分かりませんので、この画面を右クリックして「検証」を選択します。

vue_3-4-1.png

② Google Chrome デベロッパーツールの表示

すると、次のように開発者用の画面が表示されます(「Google Chrome デベロッパーツール」と呼ばれるものです)。
画面にある「Console」(赤枠部分)をクリックしてください。

vue_3-4-2.png

③ メソッドの実行

Console 画面が開いたら「+ ボタン」を押してみましょう。

vue_3-4-3.png

④ 実行結果の確認

コード上で console.log(counter) と指定した内容が、Console 上に出力されます。
左側の 1 というのは出力値です。
右側の chapter4-1.html:24 というのは「chapter4-1.html」ファイルの「24」行目の実行結果であることを示しています。

vue_3-4-4.png

「+ ボタン」を押すたびに counter が 1 ずつ加算されて表示されます。

vue_3-4-5.png

以上、メソッドの実行方法について確認をしました。

アロー関数式でメソッドを記述する

Vue におけるメソッドの書き方は、JavaScript の関数の書き方と同じです。
よって、JavaScript で使用できる「アロー関数式」の書き方でメソッドを記述することもできます。 今回の例をアロー関数式で書くと次のようになります。

const increment = () => {
  counter++
  console.log(counter)
}

実際のシステム開発においては、このアロー関数式の書き方をする場合も多いですので、このような書き方があることを把握しておくようにしましょう。

2. データ定義

次に、setup() 関数内でのデータ定義について見ていきましょう。
ここでのキーワードは「リアクティブ」です。定義したデータに変動があった場合に、画面上に反映されるかどうかという点に着目して確認をしていきます。

2-1. リアクティブでないデータ定義

次のコードは、先ほどの「メソッド」の解説で使用したソースコードの counter の値を、単純にマスタッシュ構文 {{ counter }} で画面に表示するようにしたものです。

<div id="app">
  <div>Counter: {{ counter }}</div>
  <button v-on:click="increment">+ ボタン</button>
</div>

<script>
  const { createApp } = Vue

  createApp({
    setup() {
      let counter = 0
      function increment() {
        counter++
        console.log(counter)
      }
      return {
        counter,
        increment
      }
    }
  }).mount('#app')
</script>

chapter4-2.html というファイルを追加してコードを記載してみてください。

全てのコードを表示
chapter4-2.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <button v-on:click="increment">+ ボタン</button>
  </div>

  <script>
    const { createApp } = Vue

    createApp({
      setup() {
        let counter = 0
        function increment() {
          counter++
          console.log(counter)
        }
        return {
          increment
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

この例におけるデータ定義は次の部分です。JavaScript における一般的な書き方ですね。

let counter = 0

それでは、ブラウザで開いて「+ ボタン」をクリックしてみましょう。
Console 画面には、1, 2, 3 ... と値が表示されます。
しかし、マスタッシュ構文 {{ counter }} で指定した、画面左上の「Counter: 0」の値は変動しません。

vue_3-4-6.png

つまり、let counter = 0 という書き方では、データの値が変動しても画面の表示は変わらない(リアクティブではない)ということになります。
これを「リアクティブ」にするには、vue 特有の指定が必要になります。

2-2. リアクティブなデータ定義(ref 関数)

JavaScript(アプリケーションインスタンス)側の値の変動を、HTML(テンプレート)側にリアクティブに伝達するには「ref 関数」というものを使用します。

早速、ref 関数を使用してみましょう。
chapter4-2.html の JavaScript 部分を次のように修正します。

  const { createApp, ref } = Vue

  createApp({
    setup() {
      const counter = ref(0)
      function increment() {
        counter.value++
        console.log(counter.value)
      }
      return {
        counter,
        increment
      }
    }
  }).mount('#app')
全てのコードを表示
chapter4-2.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>Counter: {{ counter }}</div>
    <button v-on:click="increment">+ ボタン</button>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const counter = ref(0)
        function increment() {
          counter.value++
          console.log(counter.value)
        }
        return {
          counter,
          increment
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

修正箇所を確認していきます。

① データ定義の修正

まず、データ定義 let counter = 0 のところを次のように修正しています。

const counter = ref(0)

ref 関数を使用することで、counter をリアクティブな変数(プロパティ)として定義しています。 このように定義することで、counter の値の変動があると即座に HTML(テンプレート)側にも反映されるようになります。

基本的な構文は次のようになります。

const [プロパティ名] = ref([初期値])

ref 関数では、数値や文字列のみでなく、オブジェクトや配列もリアクティブ化できます。

ref 関数でリアクティブ化できる値の範囲
・ プリミティブな値(数値、文字列、真偽値、Null など)
・ オブジェクト
・ 配列

ref 関数で定義される変数はオブジェクトとなる

例えば、ref 関数を使用して文字列をリアクティブ化します。

const message = ref('Hello World!')

ここで定義された message は、.value という単一のプロパティを持つオブジェクトとなります。 文字列 'Hello World!' は、この .value プロパティにセットされ、リアクティブな値として動作します。

② ref 関数のインポート

ref 関数を使用する場合は、Vue の定義ファイルからインポートする必要があります。

const { createApp, ref } = Vue

インストール方法として CDN を使用しているため、上記の形で ref 関数を取得します。

③ ref 関数で定義した値の取得と更新

ref 関数を使用して定義した変数の値は value プロパティから取得・更新をすることができます(いわゆるセッターとゲッターです)。
よって、counter++ および console.log(counter) としていた部分は、次のように .value を加えて記述します(※)。

counter.value++
console.log(counter.value)

④ テンプレート側は修正不要

テンプレート側は、特に修正する必要はなく、次のままで OK です。

<div id="app">
  <div>Counter: {{ counter }}</div>
  <button v-on:click="increment">+ ボタン</button>
</div>

上記 の setup() 関数内では、counter の値を取り出すために、counter.value とする必要がありましたが、テンプレート内では .value は不要で、{{ counter }} の記載で値を表示することができます(※)。

テンプレート側では .value は不要

ref 関数によりリアクティブ化した変数は、テンプレート側に渡される際に自動的に値が取り出される(アンラップされる)ため、テンプレート側で使用する場合は .value を付ける必要はありません。

修正をしたら、実際に実行をして確認してみましょう。

vue_3-4-7.png

ボタンをクリックするごとに、上記の赤枠部分の数値が更新されれば成功です。

2-3. リアクティブなデータ定義(reactive 関数)

先に ref 関数について見てきましたが、「reactive 関数」を使用してもリアクティブな値を定義することができます。
なお、reactive 関数では、オブジェクトまたは配列のみリアクティブ化することができます。 プリミティブな値(数値、文字列、真偽値、Null など)は、そのままでは変数として指定できません。

reactive 関数でリアクティブ化できる値の範囲
・ オブジェクト
・ 配列

これまでと同じ内容を reactive 関数を使用して記述すると次のようになります。

<div id="app">
  <div>Counter: {{ counter.value }}</div>
  <button v-on:click="increment">+ ボタン</button>
</div>

<script>
  const { createApp, reactive } = Vue

  createApp({
    setup() {
      const counter = reactive({ value: 0 })
      function increment() {
        counter.value++
        console.log(counter.value)
      }
      return {
        counter,
        increment
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter4-3.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>Counter: {{ counter.value }}</div>
    <button v-on:click="increment">+ ボタン</button>
  </div>

  <script>
    const { createApp, reactive } = Vue

    createApp({
      setup() {
        const counter = reactive({ value: 0 })
        function increment() {
          counter.value++
          console.log(counter.value)
        }
        return {
          counter,
          increment
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

以下、reactive 関数に関わる箇所を見ていきます。

① データ定義

まず、データ定義は、次のように指定しています。

const counter = reactive({ value: 0 })

reactive 関数では、プリミティブな値(数値、文字列など)はそのままリアクティブ化できないため、オブジェクト形式にする必要があります(配列形式でも OK です)。
基本的な構文は次のとおりです。

const [プロパティ名] = reactive([オブジェクト または 配列])

② reactive 関数のインポート

reactive 関数を使用する場合は、Vue の定義ファイルからインポートする必要があります。

const { createApp, reactive } = Vue

インストール方法として CDN を使用しているため、上記の形で reactive 関数を取得します。

以上のコードを、chapter4-3.html ファイルを追加して書き込み、実行してみましょう。

vue_3-4-8.png

ボタンをクリックするごとに、上記の赤枠部分の数値が更新されるはずです。

2-4. オブジェクトをリアクティブ化する

次にオブジェクトをリアクティブ化してみましょう。

① ref 関数でオブジェクトをリアクティブ化

まず、ref 関数から実行してみます。コードは次のように記載します。

<div id="app">
  <ul>
    <li>id: {{ member.id }}</li>
    <li>name: {{ member.name }}</li>
    <li>age: {{ member.age }}</li>
    <li>prefectures: {{ member.prefectures }}</li>
  </ul>
  <button v-on:click="incrementAge">+ ボタン</button>
</div>

<script>
  const { createApp, ref } = Vue

  createApp({
    setup() {
      const member = ref({
        id: 1,
        name: '山田太郎',
        age: 30,
        prefectures: '東京'
      })
      function incrementAge() {
        member.value.age++
      }
      return {
        member,
        incrementAge
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter4-4.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <ul>
      <li>id: {{ member.id }}</li>
      <li>name: {{ member.name }}</li>
      <li>age: {{ member.age }}</li>
      <li>prefectures: {{ member.prefectures }}</li>
    </ul>
    <button v-on:click="incrementAge">+ ボタン</button>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const member = ref({
          id: 1,
          name: '山田太郎',
          age: 30,
          prefectures: '東京'
        })
        function incrementAge() {
          member.value.age++
        }
        return {
          member,
          incrementAge
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

オブジェクトをリアクティブ化して定義しているのは次のところです。

const member = ref({
  id: 1,
  name: '山田太郎',
  age: 30,
  prefectures: '東京'
})

メソッドは次のように定義しています。
age プロパティにアクセスするには、member.value.age というように、間に .value を入れる必要があることに注意してください。

function incrementAge() {
  member.value.age++
}

chapter4-4.html というファイルを追加しコードを記載して、実行してみましょう。

vue_3-4-9.png

「+ ボタン」をクリックすると、age の値が 1 ずつ加算されるはずです。

② reactive 関数でオブジェクトをリアクティブ化

次に、reactive 関数でオブジェクトをリアクティブ化してみます。
コードは次のように記載します。

<div id="app">
  <ul>
    <li>id: {{ member.id }}</li>
    <li>name: {{ member.name }}</li>
    <li>age: {{ member.age }}</li>
    <li>prefectures: {{ member.prefectures }}</li>
  </ul>
  <button v-on:click="incrementAge">+ ボタン</button>
</div>

<script>
  const { createApp, reactive } = Vue

  createApp({
    setup() {
      const member = reactive({
        id: 1,
        name: '山田太郎',
        age: 30,
        prefectures: '東京'
      })
      function incrementAge() {
        member.age++
      }
      return {
        member,
        incrementAge
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter4-5.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <ul>
      <li>id: {{ member.id }}</li>
      <li>name: {{ member.name }}</li>
      <li>age: {{ member.age }}</li>
      <li>prefectures: {{ member.prefectures }}</li>
    </ul>
    <button v-on:click="incrementAge">+ ボタン</button>
  </div>

  <script>
    const { createApp, reactive } = Vue

    createApp({
      setup() {
        const member = reactive({
          id: 1,
          name: '山田太郎',
          age: 30,
          prefectures: '東京'
        })
        function incrementAge() {
          member.age++
        }
        return {
          member,
          incrementAge
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

オブジェクトをリアクティブ化して定義しているのは次のところです。

const member = reactive({
  id: 1,
  name: '山田太郎',
  age: 30,
  prefectures: '東京'
})

メソッドは次のように定義しています。
reactive 関数では、ref 関数とは異なり member.age というシンプルな形でデータにアクセスできます。

function incrementAge() {
  member.age++
}

それでは、chapter4-5.html というファイルを追加しコードを記載して、実行してみましょう。

vue_3-4-9.png

結果は ref 関数と変わらないことが確認できたと思います。

reactive 関数と ref 関数の違いについて

データをリアクティブ化するには、reactive 関数と ref 関数のどちらを使用しても同様の結果を得ることができました。 この 2 つの違いは何でしょうか。

① reactive 関数

reactive 関数ではプリミティブな値(オブジェクトでない値)をそのままリアクティブ化することができません。これは「Vue ではプリミティブな値の変更が検知できない」という性質と一致した仕様です。
Vue3 の当初の公式では reactive 関数の方が標準とされていました。

② ref 関数

ref 関数では、渡された値をもとに「value というプロパティを 1 つだけ持つオブジェクト」を生成します。 値は全てオブジェクト化されるため、プリミティブな値(数値や文字列など)でも、リアクティブ化することが可能となっています。
なお、ref 関数にオブジェクトが渡された場合は、内部的に reactive 関数に渡される仕組みになっています。

③ どちらを使用すべきか

どちらを使用すべきか、ということは現時点では公式でも触れられていません。
以上のような違いを踏まえて(チームなどで)判断していくということになります。

2-5. 配列をリアクティブ化する

最後に配列のリアクティブ化について確認していきます。

① ref 関数で配列をリアクティブ化

ここでも、ref 関数から実行してみましょう。コードは次のように記載します。

<div id="app">
  <ul>
    <li>{{ prefectures[0] }}</li>
    <li>{{ prefectures[1] }}</li>
    <li>{{ prefectures[2] }}</li>
  </ul>
  <button v-on:click="changePrefecture">変更</button>
</div>

<script>
  const { createApp, ref } = Vue

  createApp({
    setup() {
      const prefectures = ref(['東京', '神奈川', '埼玉'])
      const changePrefecture = () => {
        prefectures.value[2] = '千葉'
      }
      return {
        prefectures,
        changePrefecture
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter4-6.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <ul>
      <li>{{ prefectures[0] }}</li>
      <li>{{ prefectures[1] }}</li>
      <li>{{ prefectures[2] }}</li>
    </ul>
    <button v-on:click="changePrefecture">変更</button>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const prefectures = ref(['東京', '神奈川', '埼玉'])
        const changePrefecture = () => {
          prefectures.value[2] = '千葉'
        }
        return {
          prefectures,
          changePrefecture
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

配列をリアクティブ化して定義しているのは次のところです。

const prefectures = ref(['東京', '神奈川', '埼玉'])

メソッドは次のように定義しています。
prefectures.value[2]'埼玉''千葉' に変更するだけのメソッドです。

const changePrefecture = () => {
  prefectures.value[2] = '千葉'
}

上記はアロー関数式で記載していますが、function を使用した場合は次の記載となります。

function changePrefecture() {
  prefectures.value[2] = '千葉'
}

それでは、chapter4-6.html というファイルを追加しコードを記載して、実行してみましょう。

vue_3-4-10.png

「変更」ボタンをクリックすると、埼玉 の表示が 千葉 に変更されます。

② reactive 関数で配列をリアクティブ化

次に、reactive 関数で配列をリアクティブ化してみます。
コードは次のように記載します。

<div id="app">
  <ul>
    <li>{{ prefectures[0] }}</li>
    <li>{{ prefectures[1] }}</li>
    <li>{{ prefectures[2] }}</li>
  </ul>
  <button v-on:click="changePrefecture">変更</button>
</div>

<script>
  const { createApp, reactive } = Vue

  createApp({
    setup() {
      const prefectures = reactive(['東京', '神奈川', '埼玉'])
      const changePrefecture = () => {
        prefectures[2] = '千葉'
      }
      return {
        prefectures,
        changePrefecture
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter4-7.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <ul>
      <li>{{ prefectures[0] }}</li>
      <li>{{ prefectures[1] }}</li>
      <li>{{ prefectures[2] }}</li>
    </ul>
    <button v-on:click="changePrefecture">変更</button>
  </div>

  <script>
    const { createApp, reactive } = Vue

    createApp({
      setup() {
        const prefectures = reactive(['東京', '神奈川', '埼玉'])
        const changePrefecture = () => {
          prefectures[2] = '千葉'
        }
        return {
          prefectures,
          changePrefecture
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

配列をリアクティブ化して定義しているのは次のところです。

const prefectures = reactive(['東京', '神奈川', '埼玉'])

メソッドでは prefectures[2] の値を変更しています(ref 関数のような .value は不要)。

const changePrefecture = () => {
  prefectures[2] = '千葉'
}

実行結果は、ref 関数の場合と同じになります。 chapter4-7.html というファイルにコードを記載して、ご自身で確認してみてください。

Lesson 3 Chapter 5
算出プロパティとウォッチャ

このチャプターでは、算出プロパティ(Computed Properties)とウォッチャ(Watchers)について学んでいきます。

1. 算出プロパティ

算出プロパティを用いることで「リアクティブな値を用いた演算結果」をプロパティ(データ)として取得することが可能になります。

基本的な構文は次のようになります。

const [算出プロパティ名] = computed([演算を行うコールバック関数])

算出プロパティでは、コールバック関数内に含まれる全てのリアクティブ値を監視し、それらに変動があると演算を再実行する仕組みになっています。
なお、算出プロパティの演算内容にリアクティブな値が含まれていない場合は、再実行されませんのでご注意ください。

コールバック関数とは

JavaScript の関数は、引数に関数を指定することができます。
この引数に指定される関数のことを「コールバック関数」と呼びます。

構文を見ただけではピンと来ないと思いますので、以下、具体的な例を見ながら確認していきましょう。

1-1. 具体例で算出プロパティを確認する

ここでは、次のような、簡単なアプリケーションを作成してみます。

vue_3-5-1.png

動作内容は簡単で、次のとおりとなります。

  • 追加ボタンを押すたびに、購入数が 1 つずつ増えていく
  • 購入数が増えるごとに、お支払金額を計算して表示する

ソースコードは次のとおりです。以下で、1 つずつ解説していきます。

<div id="app">
  <p>
    <div>商品単価 {{ unitPrice }} 円</div>
    <div>購入数 {{ quantity }} 個</div>
    <button v-on:click="add">+ 追加</button>
  </p>
  <p>お支払金額 {{ payment }} 円</p>
</div>

<script>
  const { createApp, ref, computed } = Vue

  createApp({
    setup() {
      const unitPrice = 200 // 商品単価
      const quantity = ref(0) // 購入数

      // お支払金額を算出プロパティで取得
      const payment = computed(function() {
        return unitPrice * quantity.value
      })

      function add() {
        quantity.value++
      }

      return {
        quantity,
        unitPrice,
        payment,
        add
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter5-1.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <p>
      <div>商品単価 {{ unitPrice }} 円</div>
      <div>購入数 {{ quantity }} 個</div>
      <button v-on:click="add">+ 追加</button>
    </p>
    <p>お支払金額 {{ payment }} 円</p>
  </div>

  <script>
    const { createApp, ref, computed } = Vue

    createApp({
      setup() {
        const unitPrice = 200 // 商品単価
        const quantity = ref(0) // 購入数

        // お支払金額を算出プロパティで取得
        const payment = computed(function() {
          return unitPrice * quantity.value
        })

        function add() {
          quantity.value++
        }

        return {
          quantity,
          unitPrice,
          payment,
          add
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

① 算出プロパティ(computed)の定義箇所

算出プロパティを定義しているのは、次のところです。

const payment = computed(function() {
  return unitPrice * quantity.value
})

payment が算出プロパティ名となります。
コールバック関数の戻り値の unitPrice * quantity.value が演算結果として payment に渡されることになります。
ここでは、リアクティブ値 quantity.value に変動があると演算を再実行されます。

なお、コールバック関数の部分をアロー関数式に置き換えると次のようになります。

const payment = computed(() => unitPrice * quantity.value)

実際には、アロー関数式で記載する場合が多いですので、この書き方にも慣れておいてください。

② 他のプロパティの指定箇所

次のところで、unitPrice(商品単価)と (quantity購入数)を指定しています。
quantity は ref 関数によりリアクティブ化されています。

const unitPrice = 200 // 商品単価
const quantity = ref(0) // 購入数

リアクティブな変数である quantity に変動があると、上記 ① の算出プロパティの演算(unitPrice * quantity.value)が再実行されることになります。

③ 算出プロパティの表示

算出プロパティは、他のプロパティと同様に、マスタッシュ構文などを用いて表示することができます。

<p>お支払金額 {{ payment }} 円</p>

④ コードを実行する

以上で見てきた内容を実際に実行してみましょう。
chapter5-1.html というファイルを追加して、コードを記載してください。

ブラウザで開いて、「+ 追加」ボタンを押すと、まず「購入数」が加算され、それと同時に「お支払金額」の再計算が実行されます。

vue_3-5-2.png

このように、算出プロパティを使用することで、リアクティブな計算結果をそのままプロパティの値として指定することができます。

1-2. 他の方法で計算結果を表示する

実際は、算出プロパティを使用しなくとも、リアクティブな形で計算結果を表示することが可能です。
基本的には、算出プロパティを使用することが推奨されますが、比較として算出プロパティ以外の方法として次の 2 つを確認してみます。

  • メソッドを使用して計算結果を表示する
  • マスタッシュ構文内で JavaScript 式を使用する

1-2-1. メソッドを使用して計算結果を表示する

メソッドを使用しても、次のように計算結果をリアクティブに取得することができます。

<div id="app">
  <p>
    <div>商品単価 {{ unitPrice }} 円</div>
    <div>購入数 {{ quantity }} 個</div>
    <button v-on:click="add">+ 追加</button>
  </p>
  <p>お支払金額 {{ payment() }} 円</p>
</div>

<script>
  const { createApp, ref } = Vue

  createApp({
    setup() {
      const unitPrice = 200 // 商品単価
      const quantity = ref(0) // 購入数

      // お支払金額をメソッドで取得
      function payment() {
        return unitPrice * quantity.value
      }

      function add() {
        quantity.value++
      }

      return {
        quantity,
        unitPrice,
        payment,
        add
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter5-2.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <p>
      <div>商品単価 {{ unitPrice }} 円</div>
      <div>購入数 {{ quantity }} 個</div>
      <button v-on:click="add">+ 追加</button>
    </p>
    <p>お支払金額 {{ payment() }} 円</p>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const unitPrice = 200 // 商品単価
        const quantity = ref(0) // 購入数

        // お支払金額をメソッドで取得
        function payment() {
          return unitPrice * quantity.value
        }

        function add() {
          quantity.value++
        }

        return {
          quantity,
          unitPrice,
          payment,
          add
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

メソッドの指定箇所は次のところです。

function payment() {
  return unitPrice * quantity.value
}

マスタッシュ構文でメソッドの戻り値を表示する場合は、メソッド名の末尾に () を入れて実行させます。

<p>お支払金額 {{ payment() }} 円</p>

ブラウザで実行すると、メソッドで実行した場合も、算出プロパティで実行した場合も結果は同じになります(ここでは結果の確認は省きます)。

算出プロパティは計算結果をキャッシュする
それでは、メソッドと算出プロパティは何が違うのでしょうか。
算出プロパティの利点は、計算を行うと計算結果をキャッシュ(保存)しておき、リアクティブな値に変動があった場合のみ再計算を行う仕様となっているところです。
一方メソッドの方は、画面が再レンダリング(再描画)されるたびに常に計算を再実行するため、パフォーマンスの観点から好ましくないといえます。

1-2-2. マスタッシュ構文内で JavaScript 式を使用する

また、マスタッシュ構文を使用することでも、リアクティブな計算結果を取得することができます。

<div id="app">
  <p>
    <div>商品単価 {{ unitPrice }} 円</div>
    <div>購入数 {{ quantity }} 個</div>
    <button v-on:click="add">+ 追加</button>
  </p>
  <p>お支払金額 {{ unitPrice * quantity }} 円</p>
</div>

<script>
  const { createApp, ref } = Vue

  createApp({
    setup() {
      const unitPrice = 200 // 商品単価
      const quantity = ref(0) // 購入数

      function add() {
        quantity.value++
      }

      return {
        quantity,
        unitPrice,
        add
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter5-3.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <p>
      <div>商品単価 {{ unitPrice }} 円</div>
      <div>購入数 {{ quantity }} 個</div>
      <button v-on:click="add">+ 追加</button>
    </p>
    <p>お支払金額 {{ unitPrice * quantity }} 円</p>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const unitPrice = 200 // 商品単価
        const quantity = ref(0) // 購入数

        function add() {
          quantity.value++
        }

        return {
          quantity,
          unitPrice,
          add
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

以上のコードをブラウザで実行すると、算出プロパティで実行した場合と同様の結果が得られます(ここでは結果の確認は省きます)。

該当箇所を確認してみましょう。
マスタッシュ構文内で、次のように JavaScript 式を使用しています。

<p>お支払金額 {{ unitPrice * quantity }} 円</p>

この程度の JavaScript 式であれば、マスタッシュ構文内に書いても、コードの見た目が悪くなるというわけではありません(Vue スタイルガイド参照)。

ただ、MVVM パターンの影響を受けた Vue の設計思想(アーキテクチャ)から考えると、テンプレート側に画面表示ロジックを記載するのは好ましくないですし、今後、計算対象の商品数が増えたり消費税の計算などが入るような場合があると考えると、最初から、アプリケーションインスタンス内に、算出プロパティで定義しておくことが理に適っているといえます。

1-3. 算出プロパティの値取得とセッター関数

1-3-1. 算出プロパティの値取得

算出プロパティは、基本的に読み取り専用です。
setup() 関数内(アプリケーションインスタンス内)で、算出プロパティの値を取得するには次のように記載します(console.log(payment.value) の部分です)。

setup() {
  const unitPrice = 200
  const quantity = ref(0)
  const payment = computed(() => unitPrice * quantity.value)

  function print() {
    console.log(payment.value) // value プロパティから値を取得する
  }
  // 省略
}
全てのコードを表示
chapter5-4.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <p>
      <div>商品単価 {{ unitPrice }} 円</div>
      <div>購入数 {{ quantity }} 個</div>
      <button v-on:click="add">+ 追加</button>
    </p>
    <p>お支払金額 {{ payment }} 円</p>
    <button v-on:click="print">コンソール出力</button>
  </div>

  <script>
    const { createApp, ref, computed } = Vue

    createApp({
      setup() {
        const unitPrice = 200
        const quantity = ref(0)
        const payment = computed(() => unitPrice * quantity.value)

        function print() {
          console.log(payment.value) // value プロパティから値を取得する
        }

        function add() {
          quantity.value++
        }

        return {
          quantity,
          unitPrice,
          payment,
          print,
          add
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

setup() 関数内で、payment という算出プロパティの値を取得するには payment.value というように value プロパティを介して値を取得します(ref 関数の場合と同様ですね)。

ただし、次のように、算出プロパティの値を変更することは基本的にはできません。

payment.value = 1000 × できない

算出プロパティの値の変更が必要な場合はセッター関数を使用します。

1-3-2. 算出プロパティのセッター関数

算出プロパティのセッター関数は次のように指定します。

setup() {
  const unitPrice = 200 // 商品単価
  const quantity = ref(0) // 購入数

  // setter 関数を追加した算出プロパティ
  const payment = computed({
    // gettet 関数
    get() {
      return unitPrice * quantity.value
    },
    // setter 関数
    set(newValue) {
      quantity.value = newValue / unitPrice
    }
  })

  // 算出プロパティに値をセットする
  function setPayment(value) {
    payment.value = value
  }
  // 省略
}
全てのコードを表示
chapter5-5.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <p>
      <div>商品単価 {{ unitPrice }} 円</div>
      <div>購入数 {{ quantity }} 個</div>
      <button v-on:click="add">+ 追加</button>
    </p>
    <p>お支払金額 {{ payment }} 円</p>
    <button v-on:click="setPayment(1000)">デフォルト</button>
  </div>

  <script>
    const { createApp, ref, computed } = Vue

    createApp({
      setup() {
        const unitPrice = 200 // 商品単価
        const quantity = ref(0) // 購入数

        // setter 関数を追加した算出プロパティ
        const payment = computed({
          // gettet 関数
          get() {
            return unitPrice * quantity.value
          },
          // setter 関数
          set(newValue) {
            quantity.value = newValue / unitPrice
          }
        })

        // 算出プロパティに値をセットする
        function setPayment(value) {
          payment.value = value
        }

        function add() {
          quantity.value++
        }

        return {
          quantity,
          unitPrice,
          payment,
          setPayment,
          add
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

① セッター関数を含む算出プロパティの定義

セッター関数を定義する算出プロパティの基本構文は次のとおりです。

const 算出プロパティ名 = computed({
  get() {
    計算処理
    return 計算結果
  },
  set(セットする値) {
    セットする計算処理
  }
})

実際の算出プロパティの定義箇所は次のところです

const payment = computed({
  get() {
    return unitPrice * quantity.value
  },
  set(newValue) {
    quantity.value = newValue / unitPrice
  }
})

ゲッター関数 get() では、これまでどおり算出プロパティで取得する値を定義しています。

セッター関数 set() のところで、算出プロパティ値の変更を定義しています。
次のように、算出プロパティ payment(お支払金額)の値が引数で指定する newValue となるように、逆算して quantity(購入数)の値を変更するようにしています。

quantity.value = newValue / unitPrice

② セッター関数を実行するメソッド

次のメソッドの payment.value = value という記載で算出プロパティの値の変更を実行しています。

function setPayment(value) {
  payment.value = value
}

③ 動作確認

それでは、chapter5-5.html というファイルを追加して、コードを記載して動作を確認してみましょう。
テンプレート部分には、次のように記載して、setPayment メソッドの引数に 1000 を渡すようにしています。

<div id="app">
  <p>
    <div>商品単価 {{ unitPrice }} 円</div>
    <div>購入数 {{ quantity }} 個</div>
    <button v-on:click="add">+ 追加</button>
  </p>
  <p>お支払金額 {{ payment }} 円</p>
  <button v-on:click="setPayment(1000)">デフォルト</button>
</div>

全体のコードは、上記の「全てのコードを表示」から確認してください。

ブラウザで開いて「デフォルト」ボタンを押すと、「お支払金額」は常に 1000 に設定されます。

vue_3-5-3.png

実際は、セッター関数を使わなくとも、直接 quantity(購入数)の値を変更することで同様の実装は可能となります。
使用する機会は多くはないかもしれませんが、算出プロパティには「セッター関数」があることも把握しておきましょう。

2. ウォッチャ

ウォッチャ(Watchers)は、算出プロパティと同様に、コールバック関数内に含まれるリアクティブ値を監視し、それらに変動があると演算を再実行する仕組みとなっています。

算出プロパティは「演算結果をリアクティブな値(プロパティ)として使用すること」を目的としていますが、ウォッチャでは、主に「リアクティブな値の変動をキャッチして演算を再実行すること」を目的として使用されます(※)。

非同期処理などの重い処理を実行する場合

Vue2(Options API)時点における解説では、算出プロパティとウォッチャはリアクティブな値を監視する点では同じような機能のため、多くの場合は算出プロパティの方を使用するのが適切とされています。
ただし、算出プロパティは「非同期処理などの重い処理」を行うには不向きな設計となっているため、そのような場合は、ウォッチャを使用することが推奨されていました(Vue 公式 - ウォッチャ)。
なお、Vue3(Composition API)においては、このような記述は見当たりませんが、1 つの知識として押さえておきましょう。

2-1. ウォッチャの種類

ウォッチャには、次の 2 つの種類があります。

No 名称 説明
1 watchEffect コールバック関数内にある全てのリアクティブ値を監視して、演算を再実行する。
2 watch 指定したリアクティブ値のみを監視して、演算を再実行する。

以下、具体的な例を見ながら、ウォッチャの使用方法を確認していきましょう。

2-2. 具体例で watchEffect を確認する

基本的な構文は次のようになります。

watchEffect(コールバック関数)

watchEffect では、コールバック関数内に含まれる全てのリアクティブ値を監視し、それらに変動があると演算を再実行する仕組みになっています。

それでは、簡単な例で動作を確認してみましょう。

<div id="app">
  <div>カウント {{ number }}</div>
  <button v-on:click="add">+ 追加</button>
</div>

<script>
  const { createApp, ref, watchEffect } = Vue

  createApp({
    setup() {
      const number = ref(0)
      const add = () => number.value++

      watchEffect(() => console.log(number.value))

      return {
        number,
        add
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter5-6.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>カウント {{ number }}</div>
    <button v-on:click="add">+ 追加</button>
  </div>

  <script>
    const { createApp, ref, watchEffect } = Vue

    createApp({
      setup() {
        const number = ref(0)
        const add = () => number.value++

        watchEffect(() => console.log(number.value))

        return {
          number,
          add
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

① watchEffect の定義箇所

watchEffect を定義しているのは、次の 1 行です。

watchEffect(() => console.log(number.value))

number.value に変更があるたびに、Console に値を出力するだけのものです。
関数部分 () => console.log(number.value) はアロー関数式で記述しています。次の指定と同じになります。

watchEffect(function() {
  console.log(number.value)
})

② 動作確認を実行する

chapter5-6.html というファイルを追加して、コードを記載してください。
コードを記載したらブラウザで開いて、右クリックから「検証」を選択し、開発者用の画面(Google Chrome デベロッパーツール)を表示します。

ここで、ブラウザの再読み込みを行ってみてください(赤枠部分をクリック)。
画面が読み込まれるたびに、最初の 1 回目の演算を watchEffect が行うため、Console 画面に 0 と表示されます。

vue_3-5-4.png

それでは、「+ 追加」ボタンを押して、watchEffect の動作を確認してみましょう。

vue_3-5-5.png

ボタンを押すたびに、watchEffect の演算が実行され、Console 画面に数値が表示されることが確認できたことと思います。
ここでは、簡単な例で実行しましたが、例えば、値の変更があるたびにサーバにデータを送信するなど、様々な実装を行うことが可能です。

③ watchEffect のオプション

watchEffect にはオプションを指定することができます。

watchEffect(コールバック関数, { オプションを指定 })

ここでは、詳しい説明は行いませんが、興味のある方は、Vue の公式ページをご確認ください。
オプションには次のようなものがあります。

No オプション 説明
1 flush 演算の実行タイミングを指定する。
2 onTrack / onTrigger デバッグを行う。

2-3. 具体例で watch を確認する

watch の基本的な構文は次のようになります。

watch(
  リアクティブ変数名,
  (変更後の値, 変更前の値) => {
    演算内容
  }
)

watch では、指定されたリアクティブ値の状態を監視し、その値に変動があるたびに演算を再実行するようになっています。
なお、コールバック関数の引数から、変更前の値と変更後の値を取得することが可能です(この引数の指定は省略しても構いません)。

こちらも、簡単な例で動作を確認してみましょう。

<div id="app">
  <div>カウント {{ number }}</div>
  <button v-on:click="add">+ 追加</button>
</div>

<script>
  const { createApp, ref, watch } = Vue

  createApp({
    setup() {
      const number = ref(0)
      const add = () => number.value++

      watch(
        number,
        (newNumber, oldNumber) => {
          console.log(newNumber, oldNumber)
        }
      )

      return {
        number,
        add
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter5-7.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>カウント {{ number }}</div>
    <button v-on:click="add">+ 追加</button>
  </div>

  <script>
    const { createApp, ref, watch } = Vue

    createApp({
      setup() {
        const number = ref(0)
        const add = () => number.value++

        watch(
          number,
          (newNumber, oldNumber) => {
            console.log(newNumber, oldNumber)
          }
        )

        return {
          number,
          add
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

① watch の定義箇所

watch を定義しているのは、次のところです。

watch(
  number,
  (newNumber, oldNumber) => {
    console.log(newNumber, oldNumber)
  }
)

number に変更があるたびに、Console に「変更後の値」と「変更前の値」を表示します。
コールバック関数部分はアロー関数式で記述しています。次の指定と同じになります。

watch(
  number,
  function(newNumber, oldNumber) {
    console.log(newNumber, oldNumber)
  }
)

② 動作確認を実行する

chapter5-7.html というファイルを追加して、コードを記載してください。
コードを記載したらブラウザで開いて、開発者用の画面(Google Chrome デベロッパーツール)を表示します。

ここで、ブラウザの再読み込みを行ってみてください(赤枠部分をクリック)。
先ほどの watchEffect とは異なり、画面が読み込まれる際には watch の演算が行われていないことが分かります(読み込み時に演算を行う方法は後述します)。

vue_3-5-6.png

それでは、「+ 追加」ボタンを押して、watch の動作を確認してみましょう。

vue_3-5-7.png

ボタンを押すたびに、watch の演算が実行され、Console 画面に「変更後の値」と「変更前の値」が表示されることが確認できました。
これらの値が取得できることが watch を利用する利点にもなります。

2-4. watch のオプション

watch にはオプションを指定することができます。

watch(
  リアクティブ変数名,
  (変更後の値, 変更前の値) => {
    演算内容
  },
  {
    オプションを指定
  }
)

watch で、指定できるオプションには次のようなものがあります。

No オプション 説明
1 immediate 読み込み時に演算を実行する(変更前の未定義値は undefined となる)。
2 deep 監視対象がオブジェクトである場合に、ネストされた深い階層の値の変更も監視する。
3 flush 演算の実行タイミングを指定する。
4 onTrack / onTrigger デバッグを行う。

2-4-1. 読み込み時に演算を実行する(immediate オプション)

watch で、読み込み時に演算を実行する immediate を使用してみましょう。
具体的には、次のように watch に { immediate: true } の記述を追加するだけで OK です。

watch(
  number,
  (newNumber, oldNumber) => {
    console.log(newNumber, oldNumber)
  },
  { immediate: true }
)
全てのコードを表示
chapter5-7.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>カウント {{ number }}</div>
    <button v-on:click="add">+ 追加</button>
  </div>

  <script>
    const { createApp, ref, watch } = Vue

    createApp({
      setup() {
        const number = ref(0)
        const add = () => number.value++

        watch(
          number,
          (newNumber, oldNumber) => {
            console.log(newNumber, oldNumber)
          },
          {
            immediate: true
          }
        )

        return {
          number,
          add
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

chapter5-8.html というファイルを追加して、コードを記載しましょう。
コードを記載したらブラウザで開いて、開発者用の画面(Google Chrome デベロッパーツール)を表示します。

ここで、ブラウザの再読み込みを行ってみてください(赤枠部分をクリック)。
0 undefined と表示され watch の演算が行われていることが確認できます。

vue_3-5-8.png

変更前の値は未定義であることから undefined となります。

2-4-2. ネストしたオブジェクトの変更を監視する(deep オプション)

次に、watch で、ネストしたオブジェクトの変更を監視する deep を使用してみましょう。
ここでは次のようなオブジェクトの変更を監視します。

const user = ref({
  name: 'yamada',
  profile: {
    age: 28
  }
})

deep オプションを使用して watch を記述すると次のようになります。

<div id="app">
  <div>ユーザー名: {{ user.name }}、年齢: {{ user.profile.age }} 歳</div>
  <button v-on:click="addAge">+ 追加</button>
</div>

<script>
  const { createApp, ref, watch } = Vue

  createApp({
    setup() {
      const user = ref({
        name: 'yamada',
        profile: {
          age: 28
        }
      })
      const addAge = () => user.value.profile.age++

      watch(
        user,
        (newValue) => {
          console.log(newValue.profile.age)
        },
        { deep: true }
      )

      return {
        user,
        addAge
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter5-7.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>ユーザー名: {{ user.name }}、年齢: {{ user.profile.age }} 歳</div>
    <button v-on:click="addAge">+ 追加</button>
  </div>

  <script>
    const { createApp, ref, watch } = Vue

    createApp({
      setup() {
        const user = ref({
          name: 'yamada',
          profile: {
            age: 28
          }
        })
        const addAge = () => user.value.profile.age++

        watch(
          user,
          (newValue) => {
            console.log(newValue.profile.age)
          },
          { deep: true }
        )

        return {
          user,
          addAge
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

次のメソッドで、2 段階にネストした user.profile.age プロパティの変更が行われます。

const addAge = () => user.value.profile.age++

watch では user オブジェクトを監視対象にして、{ deep: true } という形でオプションの指定をしています。

watch(
  user,
  (newValue) => {
    console.log(newValue.profile.age)
  },
  { deep: true }
)

それでは、chapter5-9.html というファイルを追加して、コードを記載してください。
コードを記載したらブラウザで開いて、開発者用の画面(Google Chrome デベロッパーツール)を表示します。

vue_3-5-9.png

追加ボタンを押すと、無事に watch の演算が実行されていることが確認できます。
ここでは行いませんでしたが、deep オプションを付けない場合も、お手元で試してみてください。その場合は、ボタンを押しても何ら反応がないはずです。

オブジェクト全体を監視する際の注意

deep オプションなどでオブジェクト全体を監視することをディープウォッチといいます。
ディープウォッチは監視対象のオブジェクトのネストされた全てのプロパティにアクセスする必要があるため、大きなデータ構造では高コストになってしまいます。
必要な場面で使用するなど、パフォーマンスへの注意が推奨されています。

2-5. watch で複数のリアクティブ値を監視する

watch で複数のリアクティブ値を監視する場合は次のように配列形式で指定します。

watch(
  [リアクティブ変数名1, リアクティブ変数名2, ...],
  (変更後の値, 変更前の値) => {
    演算内容
  },
  { オプションを指定 }
)

監視するリアクティブ値は、[リアクティブ変数名1, リアクティブ変数名2, ...] のように、配列で指定します。
また、コールバック関数の引数から取得される 変更後の値 および 変更前の値 は、指定した順で配列で取得されます。

具体的な例で見てみましょう。

<div id="app">
  <div>カウント {{ counter }}</div>
  <div>文字列 {{ message }}</div>
  <button v-on:click="addCounter">+ カウント追加</button><br />
  <button v-on:click="addMessage">+ 文字列追加</button>
</div>

<script>
  const { createApp, ref, watch } = Vue

  createApp({
    setup() {
      const counter = ref(0)
      const message = ref('')
      const addCounter = () => counter.value++
      const addMessage = () => message.value += 'abc'

      watch(
        [counter, message],
        (newNumber, oldNumber) => {
          console.log(newNumber, oldNumber)
        }
      )

      return {
        counter,
        message,
        addCounter,
        addMessage
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter5-10.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>カウント {{ counter }}</div>
    <div>文字列 {{ message }}</div>
    <button v-on:click="addCounter">+ カウント追加</button><br />
    <button v-on:click="addMessage">+ 文字列追加</button>
  </div>

  <script>
    const { createApp, ref, watch } = Vue

    createApp({
      setup() {
        const counter = ref(0)
        const message = ref('')
        const addCounter = () => counter.value++
        const addMessage = () => message.value += 'abc'

        watch(
          [counter, message],
          (newNumber, oldNumber) => {
            console.log(newNumber, oldNumber)
          }
        )

        return {
          counter,
          message,
          addCounter,
          addMessage
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

① watch の定義箇所

watch を定義しているのは、次のところです。

watch(
  [counter, message],
  (newNumber, oldNumber) => {
    console.log(newNumber, oldNumber)
  }
)

監視する値として、[counter, message] と指定することで countermessage の 2 つのリアクティブ値を指定しています。
また、出力値として「変更後の値」と「変更前の値」を表示するようにしています。

② リアクティブ値とメソッドの定義

countermessageのリアクティブ値の定義と、それぞれを更新するメソッドの定義は次のとおりです。
動作確認用なので簡単なものとしています。

const counter = ref(0)
const message = ref('')
const addCounter = () => counter.value++
const addMessage = () => message.value += 'abc'

③ 動作確認を実行する

それでは、chapter5-10.html というファイルを追加して、コードを記載してください。
コードを記載したらブラウザで開いて、開発者用の画面(Google Chrome デベロッパーツール)を表示します。

「カウント追加」および「文字列追加」のボタンをそれぞれクリックしてみてください。

vue_3-5-11.png

どちらのボタンを押しても、watch の演算が再実行され、変更前と変更後の値が、配列形式で取得できることが確認できます。

2-6. watchEffect と watch の違い

watchEffect と watch について、基本的な利用方法を学習してきました。
この 2 つの違いについて、簡単にまとめておきましょう。

No 名称 特長
1 watchEffect ・コールバック関数内にある全てのリアクティブ値を監視できる
2 watch ・演算の再実行条件を明文化できる(指定したリアクティブ値のみを監視)
・監視対象の値について、変更前および変更後の値の両方にアクセスできる

watchEffect の性質は、算出プロパティとほぼ同じです。
watch の方は、変更前・変更後の値を取得できる点で、固有の利用価値があります。
このあたりの性質を理解した上で、必要に応じて使い分けをしていきましょう。

Vue3(Composition API)の watch は「Vue2 の watch」と同じ

このレッスンで見てきた Vue3(Composition API)の watch は Vue2(Options API)で使用されていた watch と全く同じものとされています。
そのため、プロジェクトを Vue2 から Vue3 に移行する場合は、watch を使えば、移行の負担が少なくて済むということになります。

Lesson 3 Chapter 6
ライフサイクルフック

ライフサイクルとは、Vue のコンポーネントが生成され破棄されるまでの流れとなります。
(以下の図は 公式サイト より引用)

vue_3-6-1.png

おおまかには次の流れとなります。

  • 1. インスタンスの生成
  • 2. インスタンスのマウント(コンポーネントインスタンスの生成)
  • 3. インスタンスのアンマウント(コンポーネントインスタンスの破棄)

6-1. ライフサイクルフックの種類

Vue には、このライフサイクルの中の特定のタイミングに処理を実行できるように「ライフサイクルフック」というものが用意されています。
主なものを挙げると、次のとおりです。

No 状態 setup 内のフック 状態の説明
1 beforeCreate (setup 内に記述) インスタンス生成後・データ初期化前
2 created (setup 内に記述) インスタンス生成後・データ初期化完了
3 beforeMount onBeforeMount() レンダリング開始前(DOM にマウントする前)
4 mounted onMounted() レンダリング後(DOM にマウント完了)
5 beforeUpdate onBeforeUpdate() 再レンダリング開始前(DOM 適用前)
6 updated onUpdated() 再レンダリング完了(DOM 適用後)
7 beforeUnmount onBeforeUnmount() 非表示処理(アンマウント)開始前
8 unmounted onUnmounted() 非表示処理(アンマウント)完了

これをどのようなタイミングで使用するかですが、考え方としては、次のようなところです。

① View(テンプレート)と関係のない処理
例えば、サーバからデータを取得することなどの処理は、画面の描画が完了していなくとも開始することができます。
このような処理は created のタイミングで行うことが一般的であり、すなわち setup 内にそのまま記述することで足ります。

② View(テンプレート)と関係する処理
DOM からデータを取得するなど View(テンプレート)側の操作に関係する処理などは、マウント完了後(レンダリング完了後)に行う必要があります。
このような処理は mounted のタイミングで行うことが一般的ですので、onMounted() を使用して記述します。

③ アンマウント時に行う処理
例えば、Vue 以外の機能で DOM 要素にイベントリスナを登録した場合、アンマウント時に個別に取り外す必要があります。
このような処理は beforeUnmount のタイミングで行うことが一般的ですので、onBeforeUnmount() を使用して記述します。

ライフサイクルフックの使い方はどれも同じですので、このチャプターでは、代表的なものにつき、実際にコードを書いて確認していきます。

6-2. インスタンス生成後(created 時)のフック

「インスタンス生成後」かつ「マウント開始前」の処理は setup 内に記述します。
このタイミングでは、テンプレートからのデータ取得はできないはずですので、その状態を確認してみます。

次のようにコードを記載してください。
テンプレートの DOM 要素の参照を取得するために ref 属性 を使用しています(※)。

<div id="app">
  <div ref="root">こんにちは</div>
</div>

<script>
  const { createApp, ref } = Vue
  const App = {
    setup() {
      const root = ref(null)
      console.log(root.value)
      return {
        root
      }
    }
  }
  createApp(App).mount('#app')
</script>
全てのコードを表示
chapter5-10.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div ref="root">こんにちは</div>
  </div>

  <script>
    const { createApp, ref } = Vue
    const App = {
      setup() {
        const root = ref(null)
        console.log(root.value)
        return {
          root
        }
      }
    }
    createApp(App).mount('#app')
  </script>
</body>
</html>

① created 時の処理

created 時の処理は、setup 内にそのまま記載します。
次のところで、<div ref="root">こんにちは</div> の要素を取得しようとしています。

console.log(root.value)

② 動作確認

lesson3 フォルダ内に chapter6-1.html というファイルを追加して、コードを記載してください。
ブラウザで開いて、開発者用の画面(Google Chrome デベロッパーツール)を表示します。

vue_3-6-2.png

ブラウザの読み込みを行っても、<div ref="root">こんにちは</div> の要素は取得できず、null しか表示されません。
created のタイミングは、まだマウント未了(レンダリング未了)の段階となりますので、結果としては「正常」ということになります。

ref 属性

ref 属性を使用することで、テンプレートの DOM 要素の参照を取得することができます。
基本的な書き方は次のとおりです。

<div id="app">
  <div ref="root">こんにちは</div>
</div>

<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const root = ref(null)
      return {
        root
      }
    }
  }).mount('#app')
</script>

ref 属性は ref="root" という形で、次のように指定します。
root は名称なので何でも構いません。

<div ref="root">こんにちは</div>

setup 内では、次のように定義します。初期値は null としておきます。

const root = ref(null)

変数名 rootreturn で返すことによってテンプレート側の参照とバインドされます。

return {
  root
}

6-3. mounted 時のフック(onMounted)

onMounted はインスタンスがマウントされた後に呼ばれるライフサイクルフックです。
そのため、このライフサイクルフックでは DOM 要素を参照することができます。
実際にコードを書いて確認してみましょう。

<div id="app">
  <div ref="root">こんにちは</div>
</div>

<script>
  const { createApp, ref, onMounted } = Vue
  const App = {
    setup() {
      const root = ref(null)
      onMounted(() => console.log(root.value))
      return {
        root
      }
    }
  }
  createApp(App).mount('#app')
</script>
全てのコードを表示
chapter5-10.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div ref="root">こんにちは</div>
  </div>

  <script>
    const { createApp, ref, onMounted } = Vue
    const App = {
      setup() {
        const root = ref(null)
        onMounted(() => console.log(root.value))
        return {
          root
        }
      }
    }
    createApp(App).mount('#app')
  </script>
</body>
</html>

① mounted 時の処理

mounted 時の処理は、onMounted() 関数を使用して記載します。
onMounted() の基本構文は次のとおりです。mounted のタイミングでコールバック関数が実行されます。

onMounted(コールバック関数)

今回のコードでは、次のところで、<div ref="root">こんにちは</div> の要素を取得しようとしています。

onMounted(() => console.log(root.value))

② 動作確認

lesson3 フォルダ内に chapter6-2.html というファイルを追加して、コードを記載してください。
ブラウザで開いて、開発者用の画面(Google Chrome デベロッパーツール)を表示します。

vue_3-6-3.png

ブラウザの読み込みを行うと、Console 画面に指定した DOM 要素が表示されます。
mounted のタイミングでは、マウント完了(レンダリング完了)の段階となりますので、こちらも、結果として「正常」ということになります。

以上でライフサイクルフックの解説は終了となります。
実際に使用することが多い機能ですので、Vue コンポーネントのライフサイクルの流れを把握しておくようにしましょう。