Nuxt.jsで簡単な記事取得可能なSPAサイトを制作します。
環境構築
Nuxt.jsの開発環境の制作方法は以下の2つがあります。
- パッケージ化されたものを簡単インストール
- 手動でインストール
今回は手動でインストールする方法を紹介します。
簡単インストールの方はコマンド1つで簡単にインストールする事ができますが、パッケージされたものになり、カスタマイズの自由度が手動よりも低くなってしまいます。
何かしら案件でNuxtを使用する場合、カスタマイズを行いたいという事は多々ありますので、手動でインストールする方が何かと便利です。
事前に必要なもの
- nodeのインストール
- yarnのインストール
Nuxt.jsはnode環境で動作しますので予めnode.jsをインストールしてください。
インストールされていないかたは以下を参考にインストールしてください。
node.jsをインストールするとnpmも一緒にインストールできます。
npmを使用してNuxtを動かす事も可能ですが、今回はyarnを使用して構築していきますのでyarnもインストールします。
yarnのインストール方法は以下を参照ください。
プロジェクトディレクトリにpackage.jsonを制作
プロジェクトのディレクトリを制作したらpackage.jsonファイルを制作します。
package.jsonには以下のコードを記載しましょう。
{ "name": "my-app", "scripts": { "dev": "nuxt", "build": "nuxt build", "generate": "nuxt generate", "start": "nuxt start" } }
3行目のscriptに記載されているものが実行するタスクの名前になります。
devというタスクを実行する事で開発環境がlocalhost:3000で立ち上がります。
Nuxt.jsをインストール
以下のコマンドをターミナルへ入力し、nux.jsをインストールします。
yarn add nuxt
インストールするとpackage.jsonに自動的にインストールしたパッケージの情報が記載され、node_modulesにNuxt.jsがインストールされます。
必用なディレクトリの制作
Nuxt.jsでは各ディレクトリが重要な意味を持ちます。
名前と役割が決まっており、例えば、pagesというディレクトリの中に記載したものが必ずwebページに表示されます。
さらにpagesの中のディレクトリ構造はそのままwebサイトの階層構造となります。
このように、Nuxt.sjではディレクトリ構造を自動的に判断しプログラムが構成されますので、各ディレクトリの役割とルールを知る必要があります。
ディレクトリ名 | 説明 |
pages | pages配下のディレクトリ構造がwebサイト自体の構造を表し、その中で.vueファイルを制作し、各ページを構築します。 |
components | vueコンポーネントファイルを定義します。 |
assets | 画像、css、jsライブラリなどを格納します。 |
layout | pagesの親となり、共通で使用するコンテンツを格納します。 |
plugins | vueアプリケーションがインスタンス化する前に実行するプラグインを格納します。 |
store | アプリケーションの状態(データ)を格納します。 |
上記テーブルに記載しているディレクトリはたいてい必要になっていきます。
他にもいくつかNuxtが用意しているディレクトリはあります。詳しく知りたい方は以下から確認する事ができます。
環境の立ち上げと各コマンド
これで開発環境が整いました。
環境を立ち上げ開発を始めましょう。
開発環境の立ち上げは以下のコマンドで可能です。
yarn dev
Nuxtには他にも以下のコマンドが用意されています。
コマンド | 説明 |
yarn dev | 開発環境の立ち上げ |
yarn build | 本番用のファイルの生成 |
yarn start | 本番ファイル生成後、本番サーバーを起動します。 |
記事取得アプリを制作する
ここからは記事取得を行うアプリを制作します。
SPAとルーティングについて触れるため、ページは以下の3ページを制作しましょう。
- index.vue – サイトのトップページ
- Articles – 記事一覧ページ
- Single – 記事詳細ページ
デモ
各ページの制作
Index,Articles,Singleの各ページを制作し、pages配下のディレクトリとルーティングの関係について学びます。
以下のようにpages配下にディレクトリを制作しましょう。
pages articles index.vue single index.vue index.vue
こうする事でルーティングは以下のようになります。
[ { path: '/articles', component: '~/pages/articles/index.vue', name: 'articles', }, { path: '/single', component: '~/pages/single/index.vue', name: 'single', }, ]
これはlocalhost:3000/articles/にアクセスする事でpages/articles/index.vueが表示され、localhost:3000/single/にアクセスする事でpages/single/index.vueが表示される事を表しています。
このように、pages配下に制作したディレクトリ構造がそのままwebサイト全体の構造となり、ディレクトリ構造と同じパスで各ページにアクセスできます。
メタ情報の制作
webサイトにはクローラーにサイト情報を届けるメタがありますが、Nuxt.jsからそのメタ情報を記載する事が可能です。
scriptエリアに以下のように記載します。
export default { head() { return { title: 'my app top page', meta: [ { hid: 'description', name: 'description', content: 'API経由で記事を表示するアプリ' } ] } } }
これでページタイトルとディスクリプションをセットする事が可能です。
このような形式で他にもhtmlに用意されているすべてのメタタグを使用する事が可能です。
headerの制作
Header.vueファイルを制作し、共通パーツを切り出して共通化を行いましょう。
例えばWordPressでテーマを制作する際に共通で使用するグローバルナビゲーションなどを切り出し、共通化して管理しやすくしますがあれと同じです。
今回Header.vueに記載するのはナビゲーションのみです。
このようなパーツはcomponentsディレクトリに格納するルールとなっていますので、componets/Header.vueを制作し、以下を記載しましょう。
<div> <h1>My app</h1> <nav class="l-nav"> <ul class="p-nav__ul"> <li class="p-nav__li"><nuxt-link to="/">Home</nuxt-link></li> <li class="p-nav__li"><nuxt-link to="/articles/">articles</nuxt-link></li> </ul> </nav> <h2 class="p-page__title">Home</h2> </div>
これでarticlesリンクをクリックする事でlocalhost:3000/articles/ページへ移動する事ができます。
ページを移動する際は通常aタグを使用しますが、Nuxt.jsではnuxt-linkを使用し、to属性にパスを記載します。
こちらのHeader.vueをページ内に設置する必要があるのですが、全てのページ共通のものを各ページに記載するのは管理が煩雑になるのでlayoutを使用します。
layoutの制作
layout/default.vueを制作し、以下のコードを記載しましょう。
<template> <div class="l-root-container"> <Header /> <div class="l-main-content"> <nuxt /> </div> </div> </template> <script> import Header from '../components/Header.vue'; export default { components: { Header } } </script>
<nuxt />タグ部分にpagesで制作した各.vueファイルの内容が入ります。
これで共通でページのトップにHeaderの内容が表示され、各ページの中身(コンテンツ)部分が入れ替わって表示されるようになります。
Homeの制作
Homeは今回のアプリの一番トップとなるページになりますが、SPAを見るために制作したページですので、何も記載はなくて良いです。
Homeとだけわかるように『Home』というタイトルだけ表示しておきましょう。
pages/index.vue
<template> <div> <h2 class="p-page__title">Home</h2> </div> </template> <script> export default { } </script>
articlesの制作
articlesでは記事の一覧を表示します。
記事データやAPIを自前で用意する事は大変なので、jsonplaceholderというサービスを利用します。
jsonplaceholderはWebAPI経由で様々なデータをCRUDできます。
APIをたたくためにaxiosを使用しますので以下のコマンドでaxiosをインストールしておきましょう。
yarn add axios
記事一覧を取得
さっそく記事を表示させましょう。
以下のコードをarticles/index.vueに記載します。
<template> <div> <ul class="p-articles"> <li class="p-articles__item" v-for="item in articles" :key="item.id">{{item.title}}</li> </ul> </div> </template> <script> import axios from 'axios'; const END_POINT = 'https://jsonplaceholder.typicode.com/posts'; export default { data() { return { articles: null, } }, methods: { }, async created() { try { let res = await axios.get(END_POINT + '?_limit=20'); console.log(res.data); this.articles = res.data; } catch (error) { console.log(`error: ${error}`); } } } </script> <style scoped> .p-articles { list-style: none; } .p-articles__item { padding: 5px 16px; border: #ccc solid 1px; border-radius: 5px; } .p-articles__item + .p-articles__item { margin-top: 4px; } </style>
createdにget処理を記載し、articlesに取得した記事データを格納し、v-forで表示しています。
console.logもしくはhttps://jsonplaceholder.typicode.com/postsで確認する事が可能ですが、取得したデータは以下のようになっています。
[ { "userId": 1, "id": 1, "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" } ]
.titleで記事のタイトルを取得可能ですので、一覧にはタイトルを表示させます。
絞り込みボックスの制作
絞り込み機能を制作してcomponentの制作をしてみましょう。
components/UserFilterBox.vueを制作します。
そして以下を記載しましょう。
<template> <div> <select style="width: 100px" v-model="userId" @change="filterHandler"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> <option value="5">5</option> <option value="6">6</option> </select> </div> </template> <script> export default { name: 'UserFilterBox', data() { return { userId: '' } }, methods: { filterHandler() { this.$emit('filter-id', this.userId); } } } </script>
これでuserIdを選択するセレクトボックスが制作出来ました。
セレクトボックスがのアイテムが選択されるとfilterHandlerメソッドが実行され、filterHandlerには$emitで親コンポーネントへイベントの通知を行います。
親で UserFilterBox を表示し、イベントをハンドリングする必要がありますのでpages/articles/index.vueを以下のように変更します。
<template> <div> <UserFilterBox @filter-id="filterHandler"/> <ul class="p-articles"> <li class="p-articles__item" v-for="item in articles" :key="item.id">{{item.title}}</li> </ul> </div> </template> <script> import axios from 'axios'; import UserFilterBox from '../../../components/UserFilterBox.vue'; const END_POINT = 'https://jsonplaceholder.typicode.com/posts'; export default { data() { return { articles: null, } }, methods: { filterHandler(text) { console.log(`filterHandler: ${text}`); } }, async created() { try { let res = await axios.get(END_POINT + '?_limit=20'); // console.log(res.data); this.articles = res.data; } catch (error) { console.log(`error: ${error}`); } }, components: { UserFilterBox } } </script> <style scoped> .p-articles { list-style: none; margin-top: 28px; } .p-articles__item { padding: 5px 16px; border: #ccc solid 1px; border-radius: 5px; } .p-articles__item + .p-articles__item { margin-top: 4px; } </style>
3行目でUserFilterBoxを表示し、ボタンクリックがあった際のイベントをfilterHandlerとしてmethodsに記載しています。
選択した値は引数から取得できるのでconsole.logで確認しましょう。
確認できましたら次にその入力値を使用して絞り込み結果を取得します。
filterHandlerの中を以下のように変更します。
async filterHandler(text) { console.log(`filterHandler: ${text}`); try { let res = await axios.get(END_POINT + `?userId=${text}`); console.log(res.data); this.articles = res.data; } catch (error) { console.log(`error: ${error}`); } }
これでセレクトボックスからuserIdを変更する度にその選択したユーザーIDを持つ記事のみが表示されます。
singleの制作
次に各記事の詳細ページを表示するsingleページを制作してきます。
pages/single/index.vueに以下のように記載しましょう。
<template> <div> <h2 class="p-article-title">{{article.title}}</h2> <ul class="p-article-info"> <li>user id: {{article.userId}}</li> <li>post id: {{article.id}}</li> </ul> <div class="p-article-body"> <p>{{article.body}}</p> </div> </div> </template> <script> import axios from 'axios'; const END_POINT = 'https://jsonplaceholder.typicode.com/posts'; export default { data() { return { article: { title: null, userId: null, body: null }, } }, async created() { try { let res = await axios.get(END_POINT + `/${1}`); console.log(res.data); this.article = res.data; } catch (error) { console.log(`error: ${error}`); } } } </script> <style scoped> .p-article-info { margin-top: 28px; margin-left: 30px; } .p-article-body { margin-top: 28px; } </style>
これで記事IDが1の記事を取得することが可能です。
localhost:3000/singleにアクセスして確認するとid:1の記事内容が表示されいているはずです。
jsonplaceholderはpost/numberで記事idを指定してデータを取得する事が可能になっており、その処理を31行目で行っています。
こちらの1を任意のidにする事で好きな記事データを取得できるわけです。
では、次にこの記事idをarticlesページから取得する処理を記載していきます。
記事idはAPIのリクエストと同じようにurlから取得します。
Nuxt.jsではpages配下のディレクトリ名の先頭に『_』アンダースコアをつける事でワイルドカードとして扱う事ができるようになります。
pages/single/index.vueファイルを pages/single/_id.vueと変更します。
そうする事でいままではlocalhost:3000/single/でアクセスしていたurlがlocalhost:3000/single/1というように最後に記事IDをくっつけてアクセスする事が可能になります。
single/の後の部分は以下のコードで取得することが可能です。
this.$route.params.id
params.idのid部分は今回_id.vueとしたのでidとなっています。もし、_article.vueという名前にしたらparams.articleになるという事です。
async created() { console.log(`article id: ${this.$route.params.id}`); }
createdにconsole.logを追加し、取得できるか localhost:3000/single/100などとアクセスして試してみてください。
取得できる事が確認できたら、取得した記事IDでgetリクエストを送り記事データを取得します。
createdを以下のように書き直します。
async created() { try { let res = await axios.get(END_POINT + `/${this.$route.params.id}`); console.log(res.data); this.article = res.data; } catch (error) { console.log(`error: ${error}`); } }
記事一覧にリンクをつける
記事一覧ページから各記事タイトルをクリックして詳細ページへ移動できるようにarticles/index.vueのtemplateを以下のように書き換えます。
<div> <UserFilterBox @filter-id="filterHandler"/> <ul class="p-articles"> <li class="p-articles__item" v-for="item in articles" :key="item.id"> <nuxt-link :to="'/single/' + item.id">{{item.title}}</nuxt-link> </li> </ul> </div>
6行目のnuxt-linkが追加されています。
以上で完成です。
これで各記事タイトルをクリックすると詳細ページへ移動し、記事の詳細情報閲覧する事が可能なはずです。