JavaScriptのアプリケーションを作っていると、よくリモートソースからデータを取得したり、APIを使いたくなったりすると思います。最近、公開されているAPIを見ていたら、ソースからデータを取得して処理するのに良いものがたくさんあることに気付きました。
Vue.jsを使えば提供される機能を使ってアプリを構築し、数分のうちにコンテンツの配信を始められます。
ニューヨークタイムズのAPIからデータを取得して、その日のトップニュース記事を表示し、ユーザーが興味があるカテゴリのニュースを選びだせる簡単なニュースアプリの作り方を説明します。この記事の全コードはここにあります。
アプリの出来上がりは下の図のようになります。
この記事では、Vue.jsのごく基本的な知識が必要です。すばらしい入門記事『ReactとAngularのいいとこ取り? 2017年こそ学びたいVue.jsの始め方』を参考にしてください。 ES6文法も使いますが、復習にはSitePointのここを参照してください。
プロジェクトの構造
ファイルを2つに絞って話を簡単にします。
./app.js
./index.html
app.jsにアプリのロジックを、index.htmlファイルにアプリのメインビューを組み込みます。
index.htmlの基本的なマークアップから始めます。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>The greatest news app ever</title>
</head>
<body>
<div class="container" id="app">
<h3 class="text-center">VueNews</h3>
</div>
</body>
</html>
次にindex.htmlファイルの一番最後の</body>タグを閉じる直前に、Vue.jsとapp.jsを加えます。
<script src="https://unpkg.com/vue"></script>
<script src="app.js"></script>
あらかじめ作っておいたスタイルを使って、見かけを少し良くするためにFoundationが使えます。<head>タグに加えます。
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.3.1/css/foundation.min.css">
簡単なVueアプリを作る
最初に、div#app要素の新しいVueインスタンスを作り、テストデータを使って、ニュースアプリからの応答をシミュレーションをします。
// ./app.js
const vm = new Vue({
el: '#app',
data: {
results: [
{title: "the very first post", abstract: "lorem ipsum some test dimpsum"},
{title: "and then there was the second", abstract: "lorem ipsum some test dimsum"},
{title: "third time's a charm", abstract: "lorem ipsum some test dimsum"},
{title: "four the last time", abstract: "lorem ipsum some test dimsum"}
]
}
});
elオプションを使ってどの要素をマウントするかをVueに知らせ、dataオプションでアプリがどのデータを使うかを指定します。
アプリのビューでこのテストデータを表示するために、#app要素に次のマークアップを書きます。
<!-- ./index.html -->
<div class="columns medium-3" v-for="result in results">
<div class="card">
<div class="card-divider">
{{ result.title }}
</div>
<div class="card-section">
<p>{{ result.abstract }}.</p>
</div>
</div>
</div>
結果のリストをレンダリングするのにv-for命令を使います。結果を表示するために二重中括弧を使います。
備考:Vueテンプレートの文法の詳細は、ここを参照してください。
基本的なレイアウトができました。
APIからデータを取得する
ニューヨークタイムズAPIを利用するためにはAPIキーが必要です。まだ持っていないならここで登録して、Top Stories APIのAPIキーを取得してください。
Ajaxリクエストを送り応答を処理する
AxiosはAjaxリクエストを送るためのpromiseベースのHTTPクライアントで、簡単で豊富なAPIを提供してくれるので大活躍します。fetchAPIによく似ていますが、古いブラウザーにポリフィルを加えたり、そのほかの細々とした設定をせずに使えます。
備考:以前、Vueプロジェクトにはvue-resourceが良く使われていましたが、現在は使われていません。
Axiosをインクルードします。
<!-- ./index.html -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
いったん、Vueアプリがマウントされれば、homeセクションからトップ記事のリストを取得するリクエストが出せます。
// ./app.js
const vm = new Vue({
el: '#app',
data: {
results: []
},
mounted() {
axios.get("https://api.nytimes.com/svc/topstories/v2/home.json?api-key=your_api_key")
.then(response => {this.results = response.data.results})
}
});
注意:NYT Dev Network areaから得た実際のAPIキーでyour_api_keyを置き換えてください。
これでアプリのホームページでニュースフィードが見られるようになりました。表示が乱れていますが、心配しないでください。すぐに修正します。
VueDevtools経由のニューヨークタイムズAPIからのレスポンスは下の図のようになります。
コツ:Vueアプリケーションのデバッグを簡単にするには、Vue Devtoolsを入手してください。
これまでのコードを再利用できるように整理して、少し修正し、URLを作るヘルパー関数を導入します。また、getPostsをmethods objectに加えて、アプリケーションのメソッドとして登録します。
const NYTBaseUrl = "https://api.nytimes.com/svc/topstories/v2/";
const ApiKey = "your_api_key";
function buildUrl (url) {
return NYTBaseUrl + url + ".json?api-key=" + ApiKey
}
const vm = new Vue({
el: '#app',
data: {
results: []
},
mounted () {
this.getPosts('home');
},
methods: {
getPosts(section) {
let url = buildUrl(section);
axios.get(url).then((response) => {
this.results = response.data.results;
}).catch( error => { console.log(error); });
}
}
});
算出プロパティを導入して、APIから得られるオリジナルの結果に少し修正を加え、ビューの見栄えに多少の変更を加えます。
// ./app.js
const vm = new Vue({
el: '#app',
data: {
results: []
},
mounted () {
this.getPosts('home');
},
methods: {
getPosts(section) {
let url = buildUrl(section);
axios.get(url).then((response) => {
this.results = response.data.results;
}).catch( error => { console.log(error); });
}
},
computed: {
processedPosts() {
let posts = this.results;
// Add image_url attribute
posts.map(post => {
let imgObj = post.multimedia.find(media => media.format === "superJumbo");
post.image_url = imgObj ? imgObj.url : "http://placehold.it/300x200?text=N/A";
});
// Put Array into Chunks
let i, j, chunkedArray = [], chunk = 4;
for (i=0, j=0; i < posts.length; i += chunk, j++) {
chunkedArray[j] = posts.slice(i,i+chunk);
}
return chunkedArray;
}
}
});
上のコードでは、processedPosts算出プロパティで、それぞれのニュース記事オブジェクトにimage_url属性を付加します。APIのresultsをループし、個々のresultのmultimedia配列をサーチして、必要とするフォーマットのメディアタイプを見つけ、そのメディアのURLをimage_url属性にアサインします。メディアが入手できない場合はPlacehold.itの画像をデフォルトのURLとします。
results配列を4つにまとめるためにも、ループを書いて先ほど気になったビューの乱れを修正します。
備考:Lodashのようなライブラリーでも、簡単にまとめられます。
算出プロパティはデータを扱うのに便利です。メソッドを作って投稿の配列をまとめる必要があるたびにメソッドを呼ぶ代わりに、算出プロパティとして定義するだけで、使いたいときに使えるようになります。なぜなら、resultsが変更されると、Vueが自動的にprocessedPosts算出プロパティを更新してくれるからです。
また、算出プロパティは依存オブジェクトに基づいてキャッシュされるので、resultsが変わらない限りprocessedPostsプロパティは自分自身のキャッシュバージョンを返します。特に複雑なデータ処理をしているときはパフォーマンスが向上します。
次に計算結果を表示するためにindex.htmlのマークアップを修正します。
<!-- ./index.html -->
<div class="row" v-for="posts in processedPosts">
<div class="columns large-3 medium-6" v-for="post in posts">
<div class="card">
<div class="card-divider">
{{ post.title }}
</div>
<a :href="post.url" target="_blank"><img :src="post.image_url"></a>
<div class="card-section">
<p>{{ post.abstract }}</p>
</div>
</div>
</div>
</div>
アプリの見た目が少し良くなりました。
ニュースリストコンポーネントを導入する
アプリケーションのモジュール性を保ち、HTMLの基本的な拡張をするためにコンポーネントが使えます。たとえば、「ニュースリスト」はコンポーネントとして再構成できます。もしアプリが成長して、ほかでニュースリストを使う場合でも簡単になります。
// ./app.js
Vue.component('news-list', {
props: ['results'],
template: `
<section>
<div class="row" v-for="posts in processedPosts">
<div class="columns large-3 medium-6" v-for="post in posts">
<div class="card">
<div class="card-divider">
{{ post.title }}
</div>
<a :href="post.url" target="_blank"><img :src="post.image_url"></a>
<div class="card-section">
<p>{{ post.abstract }}</p>
</div>
</div>
</div>
</div>
</section>
`,
computed: {
processedPosts() {
let posts = this.results;
// Add image_url attribute
posts.map(post => {
let imgObj = post.multimedia.find(media => media.format === "superJumbo");
post.image_url = imgObj ? imgObj.url : "http://placehold.it/300x200?text=N/A";
});
// Put Array into Chunks
let i, j, chunkedArray = [], chunk = 4;
for (i=0, j=0; i < posts.length; i += chunk, j++) {
chunkedArray[j] = posts.slice(i,i+chunk);
}
return chunkedArray;
}
}
});
const vm = new Vue({
el: '#app',
data: {
results: []
},
mounted () {
this.getPosts('home');
},
methods: {
getPosts(section) {
let url = buildUrl(section);
axios.get(url).then((response) => {
this.results = response.data.results;
}).catch( error => { console.log(error); });
}
}
});
上のコードでは、Vue.component(tagName, options)文を使ってグローバルコンポーネントを登録します。タグ名の定義にはハイフンを使うのがおすすめです。なぜなら、現在使われているHTMLタグや、将来加えられる標準のHTMLタグと競合しないようにするためです。
追加で導入したオプションを下に説明します。
- Props:親スコープからコンポーネントに渡したいデータの配列。メインアプリのインスタンスからロードしたので、resultsを加えた
- Template:ここにニュースリストのためのマークアップを定義する。<section>タグでリストを囲む。なぜなら、コンポーネントがルートエレメントを1つだけしか持てず、複数持てないからだ(div.rowの繰り返しで、複数生成される可能性がある)
news-listコンポーネントを使うために、マークアップを調整して渡すとresultsデータは次のようになります。
<!-- ./index.html -->
<div class="container" id="app">
<h3 class="text-center">VueNews</h3>
<news-list :results="results"></news-list>
</div>
備考:コンポーネントは単独のファイルコンポーネント(.vueファイル)としても生成でき、webpackのようなビルドツールでパースできます。この記事の範囲外ですが、大規模かつ複雑なアプリケーションでの使用をおすすめします。
さらに進んで、個々の記事をコンポーネントにして、モジュール化をさらに進められます。
カテゴリーフィルターを実装する
ニュースのカテゴリーを絞って表示するカテゴリーフィルターを導入し、アプリを高機能化します。
最初に、セクションのリストおよびアプリで見せるセクションを登録します。
const SECTIONS = "home, arts, automobiles, books, business, fashion, food, health, insider, magazine, movies, national, nyregion, obituaries, opinion, politics, realestate, science, sports, sundayreview, technology, theater, tmagazine, travel, upshot, world"; // From NYTimes
const vm = new Vue({
el: '#app',
data: {
results: [],
sections: SECTIONS.split(', '), // create an array of the sections
section: 'home', // set default section to 'home'
},
mounted () {
this.getPosts(this.section);
},
// ....
});
次に、これをdiv#appコンテナーに追加します。
<!-- ./index.html -->
<section class="callout secondary">
<h5 class="text-center">Filter by Category</h5>
<form>
<div class="row">
<div class="large-6 columns">
<select v-model="section">
<option v-for="section in sections" :value="section">{{ section }}</option>
</select>
</div>
<div class="medium-6 columns">
<a @click="getPosts(section)" class="button expanded">Retrieve</a>
</div>
</div>
</form>
</section>
@click文を使えば「Retrieve」ボタンがクリックされるとclickイベントが起こったことが検知され、選択されたセクションに対してgetPostsメソッドが実行されます。
最後の仕上げとデモ
ロード中を示す画像を加えるなどアプリをほんの少し改良するのに、軽微(オプション)な修正をしました。
下のCodePenでデモが見られます(機能制限あり)。
Web版はここで見られます。
最後に
この記事では、ゼロからVue.jsプロジェクトを始める方法、Axiosを使ってAPIからデータを取ってくる方法、コンポーネントと算出プロパティを使ってレスポンスに応え、データを処理する方法を説明しました。
APIサービスを使って機能的なVue.js 2.0アプリができました。ほかのAPIをプラグインすればできる、下のような改良があります。
- Buffer APIを使って、あるカテゴリーのソーシャルメディアの投稿を自動的に順番に並べる
- Pocket APIを使って、あとで読む投稿をマークする
この記事の全コードがGithubにあるので、 自分でクローンしたり、走らせたり、改良したりできます。
※本記事はJoan Yinが査読を担当しています。最高のコンテンツに仕上げるために尽力してくれたSitePointの査読担当者のみなさんに感謝します。
(原文:Fetching Data from a Third-Party API with Vue.js and Axios)
[翻訳:関 宏也/編集:Livit]