asyncDataやfetchを使おう
目次
はじめに
こんにちは。Kennyです。今回はNewsのAPIをasyncDataと@nuxtjs/axiosを使って取得し表示していきます。そしてVuexを使ったfetchについてもやってみます。とても簡単なのでぜひやってみてください。
前提
開発環境
macOS Catalina
MacBook Pro (15inch, 2019)
npm version 6.14.4
nodebrew use v14.0.0(node)
@vue/cli 4.3.1
yarn 1.22.4
Nuxt.js v2.12.2
Nuxt.js のプロジェクトを始める
Nuxt.jsはVue.jsの拡張フレームワークです。Vue.jsアプリケーションを作成したことがある方はすぐに始められると思います。この記事ではNuxt.jsの始め方の解説は省かせていただきます。
News APIを始めるためにアカウント登録してAPI Keyを取得しましょう。
News APIを始めよう
まずはアカウント登録になります。必要な情報を送信し、ユーザー登録を済ませましょう。
アカウントが作成できたらAPI Key を取得して、アプリケーションからアクセスしてみましょう。
Nux t.jsのモジュール@nuxt.js/axiosをインストールしよう
Nuxt.jsにはhttpリクエストを行うaxiosモジュールがあります。インストールしてみましょう。インストール後はnuxt.config.jsのmodules配列にセットします。
npm i @nuxtjs/axios --save
インストールが完了したら、nuxt.config.jsを開いてmodulesに追加していきます。
"@nuxtjs/axios": "^5.12.0",
"nuxt": "^2.0.0",
nuxt.config.jsに変化を加えた後はサーバーを落とし再度サーバーを立ち上げ直しましょう。ではこれで@nuxtjs/axiosを使うことができます。
asyncDataを使ってAPIを実行する
contextを確認しよう
Nuxt.jsではサーバーサイドでレンダリングする機能があります。これはVue.jsやReact.jsにはない機能です。ページをリロードしたり、ページコンポーネント(ルーティングありのコンポーネント)の遷移時に機能します。
APIの実行をサーバーでレンダリングしたい時はasyncDataやfetchを使うことで可能になります。fetchはVuexを使用する前提になりますが、今回はasyncDataを使ってみましょう。
export default {
asyncData(context) {
console.log('context', context)
}
}
asyncDataは引数にcontextをとります。まずはcontextの内容を確認してみましょう。確認が済んだら次にasyncDataの引数を{ $axios }にします。contextの$axiosのみを使用するという意味になります。そして@nuxtjs/axiosも使用してみましょう。
$axiosを使用してみよう
asyncData ({ $axios }) {
console.log('$axios', $axios)
}
@nuxtjs/axiosを使用しよう
async asyncData ({ $axios }) {
try {
const topHeadlines = await $axios.$get('apiUrl')
console.log('headline', topHeadlines.articles)
return {
headlines: topHeadlines.articles
}
} catch (e) {
console.log(e.message)
}
}
今回await async構文をしようしています。@nuxtjs/axiosを使用する際は、$を使ってアクセスし、getに対しても$を使います。topHeadlines変数に格納し、コンソールで確認してみてください。次はNews APIのurlをみていきます。
News APIの取得
取得したAPI KeyをapiKeyにセットします。
'http://newsapi.org/v2/top-headlines?' + 'country=us&' + 'apiKey=API_KEY'
API_KEYの場所に取得したAPI Keyをセットしてください。
このURLをaxiosにセットしてみましょう。
async asyncData ({ $axios }) {
try {
const topHeadlines = await $axios.$get('http://newsapi.org/v2/top-headlines?' + 'country=jp&' + 'apiKey=63ffc2f767ff448a9f32b2a1d13g827b')
console.log('headline', topHeadlines.articles)
return {
headlines: topHeadlines.articles
}
} catch (e) {
console.log(e.message)
}
}
コンソールに取得したデータを出力できましたでしょうか。
戻り値はtopHeadlines.articlesをheadlinesとして返した値です。
v-forでheadlinesを繰り返し出力して、データを表示させましょう。
Templateにv-forでnewsを表示させよう
<div v-for="headline in headlines" :key="headline.id">
{{ headline.title }}
</div>
apiKeyをnuxt.config.jsに設定してみよう
apiKeyをコンポーネントからnuxt.config.jsに移動させてみましょう。
env: {
API_KEY: "63ffc2f767ff448a9f32b2a1d13g827b"
}
axiosの認証ヘッダーを設定しよう
リクエストする全てに認証ヘッダーを設定することができます。nuxt.config.jsに次のように記述しましょう。
axios: {
credentials: true
}
この設定を適用するためにpluginsにaxios.jsを作成して共通のAuthorizationヘッダーを付けましょう。plugins/axios.jsを作成して記述します。
export default function ({ $axios }) {
$axios.onRequest((config) => {
config.headers.common.Authorization =
process.env.API_KEY
})
}
axiosモジュールにアクセスする際は$axiosとします。onRequestメソッドを使って共通化します。
nuxt.config.jsにplugins/axios.jsを登録しよう
plugins: [
{ src:'@/plugins/axios' }
]
Nuxt.jsでプラグインを使う場合はこのように設定しましょう。
apiKeyを環境変数として保存しよう
apiKeyをコンポーネントからnuxt.config.jsに移動出せましたが、このままGitHubなどのリポジトリに保存してしまうと外部から不正アクセスされるかもしれません。.gitignoreファイルにnuxt.config.jsを設定することで回避することが出来ますがそれなら.envファイルに記述して、保存しておくと良いでしょう。他の環境変数が必要になる場合は1ファイルにまとめることが出来ます。
npm i @nuxtjs/dotenv
インストール出来たらnuxt.config.jsファイルのmodulesに追記します。
modules: [
'@nuxtjs/axios',
'@nuxtjs/dotenv'
],
次にルートディレクトリに.envファイルを作成して、API_KEYを設定しましょう。
API_KEY=63ffc2f767ff448a9f32b2a1d13g827b
次に.envをnuxt.config.jsにインポートします。実際にはrequireを使いますが、nuxt.config.jsの一行目に追加してください。
require('dotenv').config()
.envファイルに環境変数としてapiKeyを設定し、そのファイルをnuxt.config.jsに読み取らせることでグローバルにapiKeyにアクセスすることができるようになりました。axios.jsのprocess.env.API_KEYは共通のヘッダー認証となりました。
ではここでこれまでの設定をコンポーネントに適用させてみましょう。次のように書き換えてみましょう。
async asyncData ({ $axios }) {
try {
const topHeadlines = await $axios.$get('http://newsapi.org/v2/top-headlines?country=jp')
console.log('headline', topHeadlines.articles)
return {
headlines: topHeadlines.articles
}
} catch (e) {
console.log(e.message)
}
}
変更点は、apiKeyを削除して、urlの文字列を一つにしただけです。
取得出来たでしょうか。次にurlが長いのでproxy設定をしてurlの”http://newsapi.org/v2/”を’/api/’に変更してみましょう。
@nuxtjs/proxyをインストールしよう
npm i @nuxt/proxy
ではmodulesに追記して、axiosにも追記します。proxy:trueに設定して要求を許可します。
modules: [
'@nuxtjs/axios',
'@nuxtjs/proxy',
'@nuxtjs/dotenv'
],
axios: {
credentials: true,
proxy: true
}
proxy: trueを追加したら、proxy構成オブジェクトを用意します。
proxy: {
'/api/': {
target: 'https://newsapi.org/v2/',
pathRewrite: { '^/api/': '' }
}
},
エントリポイントとパスを設定し、正規表現で書き換えを行います。ではコンポーネントのエントリーポイントをリネームしましょう。
$axios.$get('/api/top-headlines?country=jp')
これでエントリポイントを変更することが出来ました。このようにすることで、すっきりとした記述にすることが出来ました。
fetchとVuexを使ってAPI で取得しよう
fetch関数はNuxt.jsのSSR機能の一つです。ただVuexを使用する前提となります。
Vuexのmodule化
Vuexにはクラシックとモジュールと二つモードがありますが、じきにクラシックモードは廃止されます。
store配下にindex.jsとheadlines.jsの二つのファイルを作成してください。index.jsはloadingやerrrorなどの,どのファイルでも使うようなstateの値を用意すると良いでしょう。headlines.jsにはnewsに関わるデータを格納します。このようにデータごとにファイルを分けることで分割して管理することができるのがmoduleモードの特徴です。
fetch関数からstoreにアクセスし、ニュースデータを格納しよう
まずはIndex.vueからfetch関数を使ってVuexにアクセスします。actionsは非同期関数を扱えるのでdispatchしましょう。
先ほどのasyncData関数はコメントアウトしましょう。
export default {
async fetch ({ store }) {
const apiUrl = '/api/top-headlines?country=jp'
await store.dispatch('headlines/loadHeadlines', apiUrl)
},
computed: {
headlines () {
return this.$store.getters['headlines/headlines']
}
}
}
gettersでstateの値を返すようheadlines.jsで記述することになるのでコンポーネントでheadlinesとして取得します。モジュールモードの場合のgettersの取得方法は配列で取得します。dispatchは通常通りですが/を使います。
store/headlines.jsでhttpリクエスト
export const state = () => ({
headlines: []
})
export const mutations = {
setHeadlines (state, payload) {
state.headlines = payload
}
}
export const actions = {
async loadHeadlines ({ commit }, payload) {
try {
const headlines = await this.$axios.$get(payload)
commit('setHeadlines', headlines)
} catch (e) {
console.log(e)
}
}
}
export const getters = {
headlines (state) {
return state.headlines
}
}
storeからaxiosを使う場合はthis.$axiosとなります。取得したapiUrlはpayloadとして取得しています。もちろん変数はなんでも良いです。
commitではmutationsで定義されたsetHeadlines関数を実行します。setHeadlinesによってstateの値がミューテート(変化)されたのでgettersのheadlins関数のstateの値が変更され、先ほどのcomputedライフサイクルはその変化した値を受け取っているという流れになります。
Index.vueを確認すると表示はされていませんがデータは取得しています。
表示するにはconsoleでデータを確認し、絞り込んでみましょう。
<div v-for="headline in headlines.articles" :key="headline.id">
{{ headline.title }}
</div>
無事表示されたでしょうか。
fetch関数では引数にstoreをとることが出来ます。asyncDataと同じようにサーバーでレンダリングする機能となりますが、storeを経由すると覚えておくと良いでしょう。
headlines.articlesからわかるようにそもそもarticlesはheadlinesのオブジェクトです。storeで絞り込んだものを取得することも考えてみたいと思います。次のようにstoreを変更してください。
export const actions = {
async loadHeadlines ({ commit }, payload) {
try {
const { articles } = await this.$axios.$get(payload)
console.log({ articles })
commit('setHeadlines', { articles })
} catch (e) {
console.log(e)
}
}
}
このようにすることでarticlesのみをAPIで取得することが出来ます。コンソールで確認しながら記述すると良いでしょう。
それぞれのニュースを各ページで表示しよう
これまででニュースの一覧(タイトル)を表示させることが出来ました。通常のニュースサイトであればそれぞれのニュースのページがあるはずです。それぞれのニュースのidを取得することでurlに設定することが可能になります。しかしデータをみてもurlに設定できるようなidが見受けられないので、パッケージを使って自動idを生成し、オブジェクトに追加してみようと思います。
uuidを使ってidを生成しよう
npm i uuidv4
uuidはランダム乱数を自動生成するパッケージです。
Index.vueではfetch関数でstoreを経由する形でデータを取得しています。そして取得したデータのオブジェクト内のarticlesをcommitしています。このデータを展開するタイミングを使ってuuidで自動idを生成し、slugとしてidを追加してみましょう。
ではstore/headlines.jsを変更していきます。
import { uuid } from 'uuidv4'
export const actions = {
async loadHeadlines ({ commit }, payload) {
try {
const { articles } = await this.$axios.$get(payload)
// 追加
const headlines = articles.map((article) => {
const slug = uuid(article.title)
const headline = { ...article, slug }
return headline
})
commit('setHeadlines', headlines)
} catch (e) {
console.log(e)
}
}
}
articlesのそれぞれarticleをmap関数を使って新しい配列にし、headlinesに代入します。その時にuuidの引数にarticle.titleを割当ててslug変数に格納します。それから、article配列を展開して、slugをオブジェクトに追加します。
ダイナミックルートを設定する
pagesフォルダーにheadlinesフォルダーを作成し、_id.vueを作成します。これはurlの設定であり、localhost:3000/headlines/idとなります。idの部分は先ほどのuuidのランダム変数が割当てられるように設計します。
Index.vueを変更していきます。
<template>
<div>
<div v-for="headline in headlines" :key="headline.id">
<nuxt-link :to="`headlines/${headline.slug}`">
<div @click.prevent="submitHeadline(headline)">
{{ headline.title }}
</div>
</nuxt-link>
</div>
</div>
</template>
methods: {
submitHeadline (headline) {
// console.log(headline)
this.$store.dispatch('headlines/submitHeadline', headline)
.then(() => {
this.$router.push('/headlines/' + headline.slug)
})
}
}
Index.vueでは取得したタイトルをクリックすることでそのニュースの固有のページに遷移するようにnuxt-linkを追加します。同時にsubmitHeadline関数の処理が始まります。引数には取得しているニュースデータを渡しています。
次にクリックイベントのsubmitHeadlineのstoreをみていきます。storeでの処理は単にコミットするだけです。渡ってきたデータをstateのheadlineに代入しています。
import { uuid } from 'uuidv4'
export const state = () => ({
headlines: [],
// 追加
headline: null
})
export const mutations = {
setHeadlines (state, payload) {
state.headlines = payload
},
// 追加
setHeadline (state, payload) {
state.headline = payload
}
}
export const actions = {
async loadHeadlines ({ commit }, payload) {
try {
const { articles } = await this.$axios.$get(payload)
// console.log({ articles })
const headlines = articles.map((article) => {
const slug = uuid(article.title)
const headline = { ...article, slug }
return headline
})
commit('setHeadlines', headlines)
} catch (e) {
console.log(e)
}
},
// 追加
submitHeadline ({ commit }, headline) {
console.log('store', headline)
commit('setHeadline', headline)
}
}
export const getters = {
headlines (state) {
return state.headlines
},
// 追加
headline (state) {
return state.headline
}
}
gettersでstateのheadlineをかえすよう記述し、_id.vueで取得しましょう。
<template>
<div>
<div>
<h3>{{ headline.title }}</h3>
<p>{{ headline.description }}</p>
<img :src="headline.urlToImage">
</div>
{{ this.$route.params.id }}
</div>
</template>
<script>
export default {
computed: {
headline () {
return this.$store.getters['headlines/headline']
}
}
}
</script>
computedでheadlineとして取得したデータを展開しています。
uuidで生成されたidをオブジェクトに追加し、それを使ってダイナミックルートに設定することが出来ました。
さいごに
今回はasyncDataやfetchを使ってnews APIに取り組んでみました。ページ遷移するたびに呼び出されます。SPAと組み合わせて使用してみてください。今回は以上です。
sssssss