第二回は「Vue.js」公式チュートリアル「Githubコミット」をやっていきます。
第一回と同様に、コードを追いつつ、わからないところがあったら調べる、という形式でやっていきます。
概要
今回の「Githubコミット」ではAjaxでGitHubのリポジトリ情報を取得し、コミット履歴をHTML上に表示するというサンプルです。
ブランチの切り替えなどにも対応しています。
html
<div id="demo">
<h1>Latest Vue.js Commits</h1>
<template v-for="branch in branches">
<input
type="radio"
:id="branch"
:value="branch"
name="branch"
v-model="currentBranch"
/>
<label :for="branch">{{ branch }}</label>
</template>
<p>vuejs/vue@{{ currentBranch }}</p>
<ul>
<li v-for="record in commits">
<a :href="record.html_url" target="_blank" class="commit">
{{ record.sha.slice(0, 7) }}
</a>
-
<span class="message">{{ record.commit.message | truncate }}</span>
<br />
by
<span class="author">
<a :href="record.author.html_url" target="_blank">{{ record.commit.author.name }}</a>
</span>
at
<span class="date">{{ record.commit.author.date | formatDate }}</span>
</li>
</ul>
</div>
template
要素は、HTML5で策定された要素です。template
要素はDOMに反映されません。
ここでは、v-for
ディレクティブを指定するため だけに存在しています。
v-for=”branch in branches”
これは、JavaScriptでいうfor文です。
Vueインスタンスのdata
内にあるbranches
の値の数だけ、v-for
ディレクティブを記述した要素(ここではtemplate
が当たります)を生成します。
template
要素を複数個生成することになりますが、template
要素自体はDOMに描画されないため、DOM上では
<input type="radio" id="master" name="branch" value="master"> <label for="master">master</label> <input type="radio" id="dev" name="branch" value="dev"> <label for="dev">dev</label>
のように描画されます。
branch
とはbranches
のプロパティ一つ一つを表す引数で、任意につけることができます。branches
のプロパティが一つ一つ、ループごとにbranch
と記述した部分に代入されます。
今回は、branches
は
branches: ['master', 'dev'],
のようになっています。
この場合、master
とdev
のそれぞれについて要素が描画されます。
:id、:value、:for
v-bind:id
、v-bind:value
、v-bind:for
の略記です。
第一回で説明したので割愛します。
name=”branch”
これはただのvalue
属性です。:id="branch"
のbranchは引数ですが、name="branch"
は定数で、v-for
とは何の関係もありませんので違いにご注意ください。
v-model=”currentBranch”
<input v-model="currentBranch">
とは<input v-bind:value="currentBranch" v-on:input="currentBranch = $event.target.value">
の糖衣構文です。
第一回で、「入力があるたびに、$event.target.value
で内容を取得してはdataの値を書き換え、その値とbindされているvalue
が自動で書き換わる」 という一連の動きがありましたが、その流れをv-model
のみで行うことができます。
input
に入力動作が行われるたびにcurrentBranch
の値が書き換えられ、再びinput
のvalue
プロパティに返ってきます。
今回はtype="radio"
なので、$event.target.value
で取得した結果はtrue
またはfalse
。この値がリアルタイムに切り替わります。
{{ branch }}
mustache構文です。
mustacheとは「口髭」という意味。
v-bind
と同様に、data
の値を束縛します。
ここにdata.branch
の値が入って来ます。
{{ currentBranch }}
<p>vuejs/vue@{{ currentBranch }}</p>
上記と同様にmustache構文です。
先ほどと違うのは、v-for
の中にいない点です。
record in commits
<ul> <li v-for="record in commits"> <a :href="record.html_url" target="_blank" class="commit" >{{ record.sha.slice(0, 7) }}</a > - <span class="message">{{ record.commit.message | truncate }}</span ><br /> by <span class="author" ><a :href="record.author.html_url" target="_blank" >{{ record.commit.author.name }}</a ></span > at <span class="date">{{ record.commit.author.date | formatDate }}</span> </li> </ul>
record
プロパティがたくさんbindされています。commits
オブジェクトは多次元配列になっています。
詳しくはJavaScriptのところで解説します。
JavaScript
var apiURL = 'https://api.github.com/repos/vuejs/vue/commits?per_page=3&sha=' /** * Actual demo */ var demo = new Vue({ el: '#demo', data: { branches: ['master', 'dev'], currentBranch: 'master', commits: null }, created: function () { this.fetchData() }, watch: { currentBranch: 'fetchData' }, filters: { truncate: function (v) { var newline = v.indexOf('\n') return newline > 0 ? v.slice(0, newline) : v }, formatDate: function (v) { return v.replace(/T|Z/g, ' ') } }, methods: { fetchData: function () { var xhr = new XMLHttpRequest() var self = this xhr.open('GET', apiURL + self.currentBranch) xhr.onload = function () { self.commits = JSON.parse(xhr.responseText) console.log(self.commits[0].html_url) } xhr.send() } } })
※第一回と重複する部分は割愛します。
API
Github APIを使っています。
GitHub API v3 | GitHub Developer Guide
詳しいことは割愛しますが、Githubではアカウントの持つリポジトリや、そこで行われているコミットなどのデータをオブジェクト化したものが固有のURLを持っています。
このチュートリアルでは、そのURLのデータをdata
オブジェクトに入れ、色々と操作をしてDOMに表示させています。
今回のURL
var apiURL = 'https://api.github.com/repos/vuejs/vue/commits?per_page=3&sha='
これは、「アカウント名vuejs
のvue
リポジトリ」の「3つぶんのコミットデータ」を表すURLです。
dataオプション
data: { branches: ['master', 'dev'], currentBranch: 'master', commits: null },
特筆すべきはcommits:null
です。
これは、後述のmethods
部分でGithubのAPIを介して取得します。
よって、ここでは初期化のためにcommits:null
としています。
null
は「何もない」という状態を定義するのに使います。
createdオプション
created: function () { this.fetchData() },
ここには関数を定義しておきます。
その関数は Vueインスタンスが作成されると同時に 呼び出されます。
呼び出されるタイミング的には 「el
オプションで指定した要素にインスタンスをマウントする直前」 です。
インスタンスは存在するけれどまだDOM要素に紐付けされてない、というタイミングでの呼び出しになります。
Vueインスタンスが作成されたら、fetchData()
メソッドを呼び出します。
内容は後ほど解説します。
watchオプション
watch: { currentBranch: 'fetchData' },
watch
オプションには、オプションを持つオブジェクトを定義します。
そして、そのオプションを監視し続け、変化があるたびに定義した関数を呼び出します。
第一回に出てきたcomputed
のより汎用的なオプションです。
今回は、data
オプションにあるcurrentBranch
を監視し、変化があるたびにmethods
オプションに定義されたfetchData
を呼び出します。
ここまでで、fetchData
は
- インスタンス作成直後
currentBranch
に変化があった時
に呼び出されることになります。
filtersオプション
filters: { truncate: function (v) { var newline = v.indexOf('\n') return newline > 0 ? v.slice(0, newline) : v }, formatDate: function (v) { return v.replace(/T|Z/g, ' ') } },
フィルタリング処理を行う関数を定義します。
ここでは二つの関数が登録されています。
truncateメソッド
truncate: function (v) { var newline = v.indexOf('\n') return newline > 0 ? v.slice(0, newline) : v },
truncate
とは「先端を切り取る」という単語。
結論からいうと「一行目を返す」メソッドです。
この関数は、HTML上で以下のようにbindされています。
- <span class="message">{{ record.commit.message | truncate }}</span>
{{ record.commit.message | truncate }}
record.commit.message | truncate
のようにすることで、record.commit.message
プロパティをtruncate
メソッドに引数として渡すことができます。
そして、truncate
メソッドの返す値がここに入ります。
truncate
メソッドに引数として与えられるのは「revert ssr readme edit」のような文字列です。
indexOfメソッド
indexOf
メソッドは引数で指定した文字列を探します。
一致すれば、「頭から何文字目で見つかったか」を数値で返します。見つからなければ-1
を返します。\n
は「改行コード」と言われるものです。
例えば「revert ssr readme edit」に対してindexOf('\n')
メソッドを適用した場合、改行が含まれていないので-1
を数値で返します。
また、newline > 0 ? v.slice(0, newline) : v
は 「newline > 0
が真ならばv.slice(0, newline)
、偽ならばv
という値をとります。slice(0, newline)
は、文字列に対して「0
からnewline
まで」を返します。
まとめると
改行 | v.indexOf(‘\n’) | truncateが返す値 |
---|---|---|
あり | -1 | 頭から改行コードまでの文字列 |
なし | 1以上の数 | 全文字列 |
となり、truncate
メソッドは「一行目を返す」ものだとわかります。先端を切り取っています。
よって、{{ record.commit.message | truncate }}
にはrecord.commit.message
プロパティの1行目が入ります。
formatDateメソッド
formatDate: function (v) { return v.replace(/T|Z/g, ' ') }
この関数もtruncate
と同様に、HTML上で以下のようにbindされています。
{{ record.commit.author.date | formatDate }}
record.commit.author.date
の文字列を引数としformatDate
メソッドを呼び出します。formatDate
メソッド内では「/T|Z/g
を半角スペースに置き換える」という操作を行い、その結果を返します。
/T|Z/g
は正規表現で 「TまたはZと連続マッチ」 を表します。
record.commit.author.date
プロパティは2017-02-13T18:39:39Z
のようになっているので、T
またはZ
とマッチするたびに半角スペースと置換され、2017-02-13 18:39:39
のようになります。
{{ record.commit.author.date | formatDate }}
にはrecord.commit.author.date
プロパティないの文字列T
とZ
が半角スペースに置換されたものが入ります。
methods
methods: { fetchData: function () { var xhr = new XMLHttpRequest() var self = this xhr.open('GET', apiURL + self.currentBranch) xhr.onload = function () { self.commits = JSON.parse(xhr.responseText) console.log(self.commits[0].html_url) } xhr.send() } }
var xhr = new XMLHttpRequest()
サーバーへリクエストを出して、XML形式ドキュメントを受け取るためのAPIです。
このメソッド内では.open
,.onload
,.send
,.responseText
の4つが使われています。
xhr.open() / XMLHttpRequest.open()
任意のURLに対してリクエストを出すための設定を行うメソッドです。
ここで設定を行なったのち、後述のsend()
メソッドで実際にリクエストを飛ばします。
xhr.open('GET', apiURL + self.currentBranch)
第一引数にHTTPメソッドを指定します。
第二引数にはアクセスするURLを指定します。GET
は、指定したURLに対して「ファイルの中身」を要求します。
apiURL + self.currentBranch
で生成したURLの中身を取得します。
xhr.onload() / XMLHttpRequest().onload
xhr.open()
のロードが完了したら、「onload」イベントが発生します。
そのイベントをハンドラとして関数を呼び出す場合の設定をここで行います。
onreadystatechange
xhr.onload = function () { self.commits = JSON.parse(xhr.responseText) console.log(self.commits[0].html_url) }
xhr.responseText / XMLHttpRequest().responseText
self.commits = JSON.parse(xhr.responseText)
XMLHttpRequest.open()
で取得したデータの結果はXMLHttpRequest.responseText
のプロパティとして格納されます。
JSON.parse()
self.commits = JSON.parse(xhr.responseText)
JSONオブジェクトのメソッドで、文字列をJSONとして解析するものです。
解析結果をJSONオブジェクトとして返します。
ここでは、XMLHttpRequest.open()
で取得したデータ文字列をJSONデータとして解析し、JSONオブジェクトとしてself.commits
に格納しています。self
はthis
なのでthis.commits
と同意、すなわちdata
に格納されることになります。
xhr.send() / XMLHttpRequest().send
サーバーへリクエストを送信します。
諸々のパラメータは先述のonload
やopen
で設定したものを使います。
流れまとめ
1.Vueインスタンスを定義するとともにfetchData()メソッドを呼び出す
XMLHttpRequest()
オブジェクト上で諸々の設定を行い、リクエストを投げ値を取得。
取得されたデータはJSONデータとしてdata.commits
内に格納されます。
2.諸々のデータがHTMLに反映
データ取得後、Vueインスタンスが#demo
にマウントされます。
その時点で、1で取得したデータ諸々がバインド先に反映されます。
なおその際、一部の値はfilters内のメソッドが適用されます。
ラジオボタンのチェックを変えるとfetchData()メソッドが呼ばれる
watch
オプションでcurrentBranch
が監視されています。
そして、変化があるたびfetchData
メソッドを呼び出すようになっています。
ラジオボタンにはv-model
ディレクティブが記述されているので、ラジオボタンのチェックを変えるたびにcurrentBranch
プロパティが変化し、fetchData
メソッドにより新しくデータが取得されます。
表示が更新される
新しいデータを取得した際も、データバインディングされていることにより自動でDOMに反映されます。
おわりに
長かったですが以上です。
流れを見ると、基本的にはfetchDataでデータ取得=>data
を書き換える=>DOMに反映という操作を行なっていて、filters
では取得したデータに対する表示上の処理を行なう、というだけのものです。
また、今回はXMLHttpRequest
を用いていました。
諸々のパラメータを設定したあとにsend()
でリクエストを飛ばすと言うふうに考えましょう。
次回もチュートリアル解説を行います。
フロントエンドエンジニア積極採用中
株式会社トゥーアールでは現在フロントエンドエンジニア積極的に採用中です!
興味がある人は採用ページをチェック!!