【Vue.js】基礎

Vue.jsの学習記録。

CodePen

HTML、CSSJavaScriptの学習を手軽に始めることができる。

https://codepen.io/

mount

マウントとは、既存のDOM要素をVue.jsが生成するDOM要素で置き換えること。
id="app"を指定した要素内が、vue.jsが構築したDOMに置き換えられる。

html

<div id="app">
</div>

js

const app = Vue.createApp({
  // options
})
app.mount('#app')

以下の記法でもよい。

js

const app = Vue.createApp({
  // options
}).mount('#app')

データバインディング

データと描画を同期する仕組み。

{{ }} この記法をマスタッシュ構文と呼ぶ。

html

<div id="app">
  <p>{{ message }}</p>
  <p>{{ count }}</p>
  <p>{{ user.prefecture }}</p>
  <p>{{ colors[1] }}</p>
</div>

js

const app = Vue.createApp({
  data: () => ({
    message: 'Hello Vue.js!',
    count: 99,
    user: {
      lastName: 'Yamazaki',
      firstName: 'Kento',
      prefecture: 'Tokyo'
    },
    colors: ['Red', 'Green', 'Blue']
  })
})
app.maunt('#app')

コンポーネント

  • 名前付きの再利用可能なインスタンス
  • ページを構成するUI部品
  • テンプレートとそのロジックから構成される

以下はHelloコンポーネントを使って、Helloと出力する処理を再利用している。

ディレクティブ

  • v-で始まる特別な属性のこと
  • directove(指令)という名前の通り、VUe.jsに何らかの支持を行う仕組み

ディレクティブの例

  • v-bind
  • v-if
  • v-show
  • v-for
  • v-on
  • v-model

使用例

<input type="text" v-bind:value="message" />

v-bindディレクティブ

属性へデータバインディングする記法。
マスタッシュ構文ではv-bindを使用する。

html

<div id="app">
  <input type="text" v-bind:value="message">
</div>

js

const app = Vue.createApp({
  data: () => ({
    message: 'Hello Vue.js'
  })
})
app.mount('#app')

省略式

どちらを使っても良いが、プロジェクト内で統一されていれば良い。
慣れるまでは省略しない記法で書けば良い。

<a v-bind:href="url">Link</a><a :href="url">Link</a>

v-ifディレクティブ

  • 要素の表示/非表示を切り替えることのできるディレクティブ

toggleがtrueの場合はHelloと表示する例。

html

<div id="app">
<p v-if="toggle">Hello</p>
</div>

js

const app = Vue.createApp({
  data: () => ({
  toggle: true
  })
})
app.mount('#app')

v-forディレクティブ

配列やオブジェクトを繰り返し処理で描画できる。

配列の例:

オブジェクトの例:

v-onディレクティブ

イベント処理を行うディレクティブ。

ボタンをクリックすると現在時刻を表示する例。

v-modelディレクティブ

双方向データバインディング:とは、
dataオブジェクトの値変更⇒テンプレートの値変更
に加えて、
テンプレートの値変更⇒dataオブジェクトの値変更
ができる。

v-onceディレクティブ

  • 初回だけテンプレートを評価し、それ以降は静的なコンテンツとして扱う。
  • つまり初回だけテキストバインディングを行いたいときに利用する。
  • API呼び出しを画面がロードされたときだけ行いたい時など。
  • ページ更新のパフォーマンスの向上が図れる。

v-onceディレクティブを使っていないときは、ボタンをクリックすると文字が毎回反転するが、
v-onceディレクティブを使うと初回以降は静的なコンテンツとして扱われ、文字が反転しなくなる。

v-preディレクティブ

  • 要素と全ての子要素のコンパイルをスキップしたいときに利用する。

ユースケース

  • 生のMustache(マスタッシュ)タグを表示したいとき。
  • ディレクティブの無い大量のノードをスキップして、コンパイルのスピードを上げたいとき。

v-htmlディレクティブ

  • プレーンなHTMLを挿入したいときに利用する。

jsに書いたタグをテキストではなくHTMLとして扱う例。

注意事項

v-cloakディレクティブ

覆い隠す、の意味。

  • ページを表示開始してから、インスタンスの作成が終わるまでの間に、Mustache タグなど、コンパイル前のテンプレートが表示されてしまうことを防ぐ際に利用する。

v-textディレクティブ

  • Mustacheの代わりにディレクティブを使いたい時に利用する。
  • どちらを使ってもよく、統一すればよい。
  • どちらでもよければMustache構文を利用する。
  • v-textディレクティブは配下のテキストを丸ごと置き換えてしまう。テキストの一部を置き換えたい場合はMustache構文を利用する必要があるため。

JavaScript

データバインディング内部ではJavaScript式を利用できる。

{{ number + 1 }}

算出プロパティ computed

  • 関数によって算出したデータを返すことができるプロパティ。
  • ロジックを再利用したいときに利用する。

算出プロパティとメソッドの違い

computed methods
呼び出し方 ()が不要 ()が必要
getter/setter getter/setterを定義できる getterのみ定義できる
キャッシュ あり(前回の計算結果を利用する) なし(呼び出される度に実行される)

getter/setterの例

キャッシュの例

computedで実行したランダム関数はキャッシュされているが、methodsのランダム関数はキャッシュされず毎回実行されている。

監視プロパティ(ウォッチャ)

  • 特定データ、または算出プロパティの状態を監視して、変化があったときに登録した処理を自動的に実行できるもの。
  • 検索フォームの値が変わったタイミングで自動的にAjaxを行って結果を一覧表示するなど。

dataオプションのmessageプロパティが変化したときに、監視プロパティのmessageがフックされ、コンソールにログを出力する例。

監視プロパティを使った単位変換アプリの例

算出プロパティと監視プロパティ

  • どちらでも実装できる場合は、算出プロパティを利用する。
  • シンプルに記述できるため。

deepオプション

  • 監視プロパティでネストされたオブジェクトを監視する場合に利用する。
  • deepオプションはデフォルトfalse。

クラスのデータバインディング

プロパティの値によって動的にスタイルを変更する例。
複数のクラスを適用できる。
クラス名にハイフンを含む場合はシングルクォートで囲む必要がある。

html

<p>Hello <span v-bind:class="{ large: isLarge, 'text-danger': hasError }">Vue.js!</span></p>

v-bindによるクラスのバインディングとプレーンなクラス属性の共存

以下のようにv-bind:classとclass=を共存して記述できる。
スタイルが競合する際は、より後に記述したスタイルが優先される。

html

    <p>Hello <span class="bg-gray text-blue"
                   v-bind:class="{ large: isLarge, 'text-danger': hasError }">Vue.js!</span></p>

配列構文による複数クラスのバインディング

次のように書くことで複数クラスをバインディングすることができる。

html

    <p>Hello! <span v-bind:class="[largeClass, dangerClass]">Vue.js!</span></p>

js

const app = Vue.createApp({
    data: () => ({
        largeClass: 'large',
        dangerClass: 'text-danger'
    })
})
app.mount('#app9')

データオプションにクラスを定義してv-bindへ渡す

html

    <p>Hello! <span v-bind:class="classObject">Vue.js!</span></p>

js

const app = Vue.createApp({
    data: () => ({
        classObject: {
            large: true,
            'text-danger': true
        }
    })
})
app.mount('#app9')

インラインスタイルのデータバインディング

一般的にcssをhtmlへ直書きすることは推奨されないが、デバッグ時など一時的に利用する場合はある。

インラインスタイルとは以下のような記述のこと。
html

    <p style="color: red;">Hello</p>

インラインスタイルを使ったデータバインディングの例。 html

    <p>Hello! <span v-bind:style="{ color: color, fontSize: fontSize + `px` }">Vue.js!</span></p>

js

const app = Vue.createApp({
    data: () => ({
        color: 'blue',
        fontSize: 40
    })
})
app.mount('#app9')

インラインスタイルのデータバインディングにオブジェクトデータを使う

前述の例よりも見通しが良くなる。

html

    <p>Hello! <span v-bind:style="styleObject">Vue.js!</span></p>

js

const app = Vue.createApp({
    data: () => ({
        styleObject: {
            color: 'blue',
            fontSize: '40px',
        }
    })
})
app.mount('#app9')

v-ifとv-elseディレクティブ

html

    <p v-if="toggle">Yes</p>
    <p v-else>No</p>

js

const app = Vue.createApp({
    data: () => ({
        toggle: true
    })
})
app.mount('#app10')

v-else-ifディレクティブ

  • v-if の “else if block” として機能する。

colorに応じて表示を切り替える信号のサンプルプログラム。

html

    <p v-if="color === 'red'">Stop</p>
    <p v-else-if="color === 'yellow'">Caution</p>
    <p v-else-if="color === 'blue'">Go</p>
    <p v-else=>Not red/yellow/blue</p>

js

const app = Vue.createApp({
    data: () => ({
        color: 'red'
    })
})
app.mount('#app10')

v-showディレクティブ

  • 要素のdisplay CSSプロパティを切り替えることで表示、非表示を切り替える。
  • v-showディレクティブの後にv-elseを記述しても連動しない。

toggleがfalseのとき非表示にするサンプル。

html

    <p v-show="toggle">Hello Vue.js!</p>

js

const app = Vue.createApp({
    data: () => ({
        toggle: false,
    })
})
app.mount('#app10')

display: none; が追加されているため非表示となる。

v-ifとv-showディレクティブ

  • v-if
    • 要素をDOMから削除、追加する
    • 高い切り替えコストが発生する
    • v-else, v-else-ifが使える
    • 実行時に条件を切り替えることがほとんどない場合に利用すれば良い
  • v-show
    • CSS display プロパティを切り替えることで表示、非表示を切り替える
    • 高い初期描画コストがかかる
    • v-else, v-else-ifが使えない
    • 実行時に表示、非表示を多く繰り返す場合に利用すれば良い

インラインメソッドハンドラ

ボタンクリック時にv-on:clickに記述した式が実行されるサンプル。

メソッドイベントハンドラ

ボタンを押すとカウントを増やすサンプル。

html

    <p>{{ counter }}</p>
    <button v-on:click="clickHandler">Click</button>

js

const app = Vue.createApp({
    data: () => ({
        counter: 0
    }),
    methods: {
        clickHandler: function() {
            this.counter++
        }
    }
})
app.mount('#app11')

イベントオブジェクトの参照

イベントハンドラに引数を指定すると、イベントオブジェクトを取得することができる。
引数名は自由に付けられるが、慣習として event や e がつけられることが多い。

html

    <p>{{ counter }}</p>
    <button v-on:click="clickHandler" id="btn">Click</button>

js

const app = Vue.createApp({
    data: () => ({
        counter: 0
    }),
    methods: {
        clickHandler: function (event) {
            this.counter++
            console.log(event.target)
            console.log(event.target.tagName)
            console.log(event.target.innerHTML)
            console.log(event.target.id)
        }
    }
})
app.mount('#app11')

イベントハンドラに引数を渡す

  • ディレクティブに指定したメソッド名に括弧を付けて値を渡す。

htmlからメソッドに値を渡し、プロパティにセット、そのプロパティを画面へ表示するサンプル。

html

    <button v-on:click="clickHandler('Vue.js')">Click</button>
    <p>{{ message }}</p>

js

const app = Vue.createApp({
    data: () => ({
        message: ''
    }),
    methods: {
        clickHandler: function (message) {
            this.message = message
        }
    }
})
app.mount('#app11')

$event

  • 前述のようにメソッドに引数を定義した場合は、eventオブジェクトを参照できなくなる。
  • その場合は、$eventを引数に追加することで参照できるようになる。
  • $event という記法はVue.jsが定義しているため変更できない。

html

    <button v-on:click="clickHandler($event, 'Vue.js')">Click</button>
    <p>{{ message }}</p>

js

const app = Vue.createApp({
    data: () => ({
        message: ''
    }),
    methods: {
        clickHandler: function ($event, message) {
            this.message = message
            console.log($event)
        }
    }
})
app.mount('#app11')

イベント修飾子 .once

ボタンを押したとき一回だけ現在時刻を表示する例。 html

    <button v-on:click.once="clickHandler3">Now</button>
    <p>{{ now }}</p>

js

const app = Vue.createApp({
    data: () => ({
        message: ''
    }),
    methods: {
        clickHandler3: function() {
            this.now = new Date().toLocaleTimeString()
        }
    }
})
app.mount('#app11')

v-onの省略記法

  • v-onディレクティブはよく利用するため省略記法が用意されている。
  • どちらを使ってもよいが、プロジェクト内で統一されていた方が良い。

html

    <button v-on:click.once="clickHandler3">Now</button>
    <button @click.once="clickHandler3">Now</button>

textarea要素のデータバインディング

textarea要素はMustache構文を使ったデータバインディングはできない。

html

    <textarea>{{ message }}</textarea>

v-modelディレクティブを使えばデータバインディングできる。

html

    <textarea v-model="message"></textarea>

js

const app = Vue.createApp({
    data: () => ({
        message: 'Hello Vue.js'
    })
})
app.mount('#app12')

チェックボックス

単体

html

    <label for="checkbox">{{ checked }}</label>
    <input type="checkbox" id="checkbox" v-model="checked">

js

const app = Vue.createApp({
    data: () => ({
        checked: 'false',
    })
})
app.mount('#app12')

複数

html

    <label for="red">Red</label>
    <input type="checkbox" id="red" value="Red" v-model="colors">
    <label for="green">Green</label>
    <input type="checkbox" id="green" value="Green" v-model="colors">
    <label for="blue">Blue</label>
    <input type="checkbox" id="blue" value="Blue" v-model="colors">
    <p>{{ colors }}</p>

js

const app = Vue.createApp({
    data: () => ({
        colors: [],
    })
})
app.mount('#app12')

ラジオボタン

html

    <label for="red">Red</label>
    <input type="radio" id="red" value="Red" v-model="color">
    <label for="green">Green</label>
    <input type="radio" id="green" value="Green" v-model="color">
    <label for="blue">Blue</label>
    <input type="radio" id="blue" value="Blue" v-model="color">
    <p>{{ color }}</p>

js

const app = Vue.createApp({
    data: () => ({
        color: '',
    })
})
app.mount('#app12')

セレクトボックス

単体

html

    <select v-model="selected">
        <option disabled>Please select one</option>
        <option>Red</option>
        <option>Green</option>
        <option>Blue</option>
    </select>
    <p>{{ selected }}</p>

js

const app = Vue.createApp({
    data: () => ({
        selected: '',
    })
})
app.mount('#app12')

複数

html

    <select v-model="selectedMultiple" multiple>
        <option>Red</option>
        <option>Green</option>
        <option>Blue</option>
    </select>
    <p>{{ selectedMultiple }}</p>

js

const app = Vue.createApp({
    data: () => ({
        selectedMultiple: '',
    })
})
app.mount('#app12')

v-model修飾子 .lazy

  • .lazy: バインドのタイミングを遅延させる

html

<div id="app13">
    <input type="text" v-model.lazy="message">
    <p>{{ message }}</p>
</div>

js

const app = Vue.createApp({
    data: () => ({
        message: ''
    })
})
app.mount('#app13')

入力中はバインドされない。

Tabキーなどを押して入力が確定されるとバインドされる。

v-model修飾子 .trim

  • .trim: 入力値から前後の空白を削除して、データに代入する。
  • 半角、全角ともに削除される。

html

<div id="app13">
    <input type="text" v-model.trim="message">
    <p>{{ message }}</p>
</div>

js

const app = Vue.createApp({
    data: () => ({
        message: ''
    })
})
app.mount('#app13')

v-model修飾子 .number

  • .number: 入力値を数値型に型変換してからデータに代入する。

.numberがない場合は文字列型となっているため、10+10は1010となる。 html

<div id="app13">
    <input type="number" v-model="age">
    <p>{{ age + 10 }}</p>
</div>

js

const app = Vue.createApp({
    data: () => ({
        age: 0
    })
})
app.mount('#app13')

文字列の結合になっている。

.numberを付けると、入力値を数値型に型変換してからデータに代入できるようになる。 html

<div id="app13">
    <input type="number" v-model.number="age">
    <p>{{ age + 10 }}</p>
</div>

コンポーネントの定義 グローバル登録

html

<div id="app14">
    <hello-component></hello-component>
    <hello-component></hello-component>
    <hello-component></hello-component>
</div>

js

const app = Vue.createApp({
    data: () => ({}),
})

app.component('hello-component', {
    template: '<p>Hello!</p>'
})

app.mount('#app14')

コンポーネントの定義 ローカル登録

  • 特定のVueインスタンス配下でしか利用できないようにできる。
  • スコープ制御のために利用する。

ルートのVueインスタンスを定義する前に、ローカルのコンポーネントを定義する。
Vueインスタンスのコンテンツオプションに登録することで、登録したVueインスタンスでのみ利用できるようになる。

html

<div id="app14">
    <hello-global-component></hello-global-component>
    <hello-global-component></hello-global-component>
    <hello-global-component></hello-global-component>
    <hello-local-component></hello-local-component>
    <hello-local-component></hello-local-component>
    <hello-local-component></hello-local-component>
</div>

js

const helloLocalComponent = {
    template: '<p>Hello local!</p>'
}

const app = Vue.createApp({
    data: () => ({
    }),
    components: {
        'hello-local-component': helloLocalComponent
    }
})

app.component('hello-global-component', {
    template: '<p>Hello!</p>'
})

app.mount('#app14')

コンポーネント命名ルール

  • ハイフンを一つ以上含むケバブケースを利用する必要がある
    • OK... local-component
    • NG... localComponent
    • NG... component

全てのHTML要素は一単語となっている背景があり、これらとの衝突を避けるため。

コンポーネントで動的な処理を行う

ボタンを押した回数をカウントするコンポーネントのサンプル。
コンポーネントにすることで再利用できる。

トランジション

ボタンを押すとフェードイン、フェードアウトしながら表示、非表示が切り替わるサンプル。

html

<div id="app15">
    <button v-on:click="show = !show">Change</button>
    <transition name="fade">
        <p v-show="show">Transition</p>
    </transition>
</div>

js

const app = Vue.createApp({
    data: () => ({
        show: true
    })
})

app.mount('#app15')

css

.fade-enter-active, .fade-leave-active {
    transition: opacity 1s;
}

.fade-enter-from, .fade-leave-to {
    opacity: 0;
}