Vue.js の基本 (10)

前回、プロパティを使用することでコンポーネントへ値を渡せることを確認しました。

しかし場合によっては、コンポーネントから親側へデータを渡したい場合もあるはずです。
その場合は、カスタムイベントを使用します。

普通の input タグの場合、値が変化すると change イベントが発生します。change イベントをハンドリングするには、addEventListener や jQuery であれば on などを使用してそのイベントが発生したときにコールバックされる関数を登録します。

コンポーネントでは、このようなイベントを任意に発生させることができます。これがカスタムイベントです。

シンプルに input タグをコンポーネントにしています。このコンポーネントは、input タグの値を data の value とバインドしています。
さらに、その value をウォッチし、value の長さが 5 を超えた場合に、this.$emit('my-event', this.value); を行っています。これがカスタムイベントの発行になります。
ここでは my-event というイベントを発行し、その引数として value を渡しています。

これをハンドリングする側が、<my-input v-on:my-event="onMyEvent"/> の v-on:my-event です。これにより、my-input コンポーネントで my-event が発生したら、onMyEvent が呼び出されるというわけです。

このように、コンポーネントではプロパティとカスタムイベントを使用して親とデータのやり取りを行います。


Vue.js の基本 (9)

前回に引き続きコンポーネントを見ていきたいと思います。

以下のような、タイトルとメッセージを表示するというシンプルなHTML部品があったとします。


これをコンポーネント化してみたいと思います。コンポーネントにすると、こうなります。


一番はじめのHTMLがコンポーネントのテンプレートになり、そこに埋め込まれた {{ title }} と {{ message }} によって、コンポーネントのデータである title と message を表示しています。

ただ、実際にはこのコンポーネントの外から、表示するものを渡したいというケースがあるはずです。
その場合は props を設けることで、外から値を渡すことができるようになります。props はコンポーネントのプロパティと呼ばれ、data で定義するデータと同じようにテンプレートで使用することができます。


props: ['title', 'message'] と定義することによって、<my-panel title="foo" message="bar" /> のようにタグの属性として値を指定することができるようになります。

このプロパティに、ただの文字列でなく、JavaScript の変数を渡したい場合にはどうすればよいでしょうか?

v-bind を使います。省略記法は : です。こうなります。


メインの Vue オブジェクトが持っている data の title と message をコンポーネントに渡し、コンポーネントでは受け取った値を表示しています。




Vue.js の基本 (8)

プログラミング言語が変わっても変わらないことがいくつかあると考えていますが、そのうちの1つが「大きいものは分割する」ということです。

部品、共通化、関数、クラス、モジュール、ライブラリ、コンポーネント、などなど昔から呼び方や粒度は様々ですが、その本質は機能を切り出して再利用可能にすることです。

Vue もご多分に漏れず、コンポーネントという仕組みを提供しています。

Vue.component で、my-input というコンポーネントを登録しています。'my-input' という文字列を指定していますが、ここに指定した名前で、カスタムの HTML タグとして使用することができます。

コンポーネントは、これまで見てきた Vue オブジェクトと同じように、データとメソッド、そして HTML テンプレートコードを持ちます。まさに、オブジェクト指向のオブジェクトです。

コンポーネントとはつまるところ、画面上のパーツです。画面上で複数回登場するもの、あるいは大きすぎて分割したいものを、コンポーネントというかたちで切り出し、共通化します。

上記の例 my-input は、これだけではただの input タグです。しかし、これまで見てきたリアクティブな機能をすべて享受することができます。

次回はさらにコンポーネントを深掘りしたいと思います。


Vue.js の基本 (7)

テキストボックスに数値を入れると、それを加算するという機能を実装するとします。
JavaScript(+JQuery) で普通に実装すると、こんな感じになるでしょうか。

テキストボックスになにか入力されたら、すべてのテキストボックスの値を足して、labelにその内容を反映させるというものです。

設計や開発を行っていると、ある値をベースとしてそこから算出される値というものはよくあります。 データベースの正規化でいうところの導出項目というやつです。

Vue では、こういったケースに対応する 算出プロパティ という機能があります。

この computed がそれです。HTMLテンプレート中では、data の項目のように扱えますが、その正体は何らかの値を返す関数です。
上記例では value1~3 までの値を足し算して返しています。

jQuery 版とは、以下が異なります。

  • value1~3 が変化したことを検知するコードを書く必要はない
  • sum の結果はキャッシュされ value1~3 が変化したときだけ再計算される




Vue.js の基本 (6)

これまで何度も Vue はリアクティブだリアクティブだと書いてきました。

上記は ON/OFF のラジオボタンを選択することで、テキストボックスの有効/無効が切り替わります。
これは isOn というデータにもとづいてリアクティブに行われています。

ところで、データが変更されたときに何らかの処理を行いたい場合はどうなるでしょうか? 普通は、データが変更されたということを現在の値と新しい値を持って調べなければならないかと思いますが、これはなかなか辛い処理です。

リアクティブ的でない処理の場合は、以下のようになるかと思います。

きっかけ発生(何らかの操作など) → データ変更有無の確認 → 処理実行  

これが自然な場合は問題ありません。ボタンをクリックしたときにデータを送信する、などは当然そうなるでしょう。
では以下のケースはどうでしょうか。

データαが更新されたら何らかの処理を行いたい
イベントA → データαを更新する(しないこともある)
イベントB → データαを更新する(しないこともある)
イベントC → データαを更新する(しないこともある)

イベントA、B、C それぞれで処理の呼び出しを行うか、あるいは データαが何らかのHTML要素にひもづいたデータであれば、change イベントをハンドリングするという手があります。しかしそうでない場合はその手は使えません。
そしていずれの方法でも、データαの値が更新されたかどうかを調べる必要があります。データαがスカラ値でなかった場合は、さらに面倒なことになるでしょう。

Vue では、データ変更を検出するにはウォッチャを使用することができます。

この例では、ボタンが2つあり、どちらのボタンもデータの値をインクリメントしますが、データの値が10以上になると alert を表示するというものです。

JavaScript のコードを見ると、data や methods の並びに watch というキーがあります。これがウォッチャの定義です。
ウォッチャには、ウォッチするデータの名前をキーに指定して、それが変更されたときに実行する処理を関数として与えます。
ウォッチャの処理には、変更後の値、変更前の値が引数として渡ってきます。
上記の例では、その関数の中で値が10以上になったかをチェックして alert を出しています。

ここでウォッチャは、何によって値が変更されたのかについて関知していないというところがポイントです。

普通は、共通化したとしてもこうなるでしょう。

inc1: function() {
  this.value += 1;
  alertByValue(); // value を見て alert を表示する処理
},

この場合、inc1 は値によって alert を出すということと結びついてしまっています。
もし3つ目のボタンが増えたり、他の処理で value が変更するようになった場合、それらでも共通の関数を呼び出すようにしなければなりません。

しかし Vue であれば、データによってそれらが切り離されます。

inc1 ----> value <---- value のウォッチャ
// inc1 は value を変更するだけ その先で何が起こるかは知らない
// value のウォッチャは value を見て処理を行うだけ 誰が value を変更したのかは知らない




Vue.js の基本 (5)

ループは、どのようなプログラミング言語でもテンプレートエンジンでも備えられていますが、もう1つ備えられているものがあります。分岐です。

Vue にも分岐を行うための仕組みがあります。それが v-if 属性です。

非常にシンプルです。v-if 属性に指定した値が true であれば、その要素がレンダリングされます。false であればレンダリングされません。

ここで注目したいのは、表示/非表示を切り替える処理を実装しているわけではなく、データによってリアクティブにそれが行われているということです。

上の例ではチェックボックスにチェックすることによってその下に div 要素が現れますが、チェックボックスの値を取得する処理を書くこともなく、 チェックボックスの値によって表示/非表示を切り替える処理を書くこともなく、この動きを実現しています。


if があるなら else も else-if もあります。使い方は... イメージ通りだと思います。


v-if に似たものとして、v-show という属性もあります。

動きは最初の例と同じように見えますが、見えないところに違いがあります。
v-if はその要素の存在そのもののありなしを制御するのに対し、v-show は、その要素を CSS で見えなくしているだけです。




Vue.js の基本 (4)

どのようなプログラミング言語でも、テンプレートエンジンでも、ループを行うための仕組みがあります。

Vue でそれを行うのは、v-for 属性です。なお Vue 独自の属性には、すべて v- というプレフィクスがついています。

データは list という配列で、要素は 'a' 'b' 'c' という3つの文字列です。
HTML では、v-for タグを使用して、ループを行っています。v-for では、それを指定したタグが、指定した内容に基づいて複数回レンダリングされます。

for (...) {
  <span>...</span>
}

的な書き方ではありません。

v-for に指定するのは、配列*1の指定と、その要素にアクセスする変数名の指定です。
{要素の変数名} in {配列} となります。 指定した list は 'a' 'b' 'c' という3つの文字列だったので、item in list の item には、 'a' 'b' 'c' が順番に入ることになります。

<span v-for="(item, index) in list">{{ index }} {{ item }}</span>

このようにすることで、インデックスを取得することもできます。foreach 系言語構文でインデックスを扱うには自前でインクリメントしないといけない場合もありますので、これは便利です。

そして、v-for に指定する配列の要素は、オブジェクトでも大丈夫です。

実際のシステムでは、オブジェクト構造、リスト構造がネストし、それらをループしながらデータを入力させたり表示させたりすることは常です。
PHP であれば、どんな name 属性にするか考慮が必要になります。name 属性自体はフラットなため、name="list[0]['name']" のような書き方をしなければなりません。 これではネストが深くなったときに苦しくなってくるのは明らかです。

これに対し Vue では、上記のコードのように v-model="item.name" というとても直感的な書き方ができています。




*1:オブジェクトも指定できます。その場合はオブジェクトのプロパティをループすることになります。