目次
はじめに
リライトして目次を追加しました。
内容は2019年5月当時のままです。
僕がNuxt.jsの各機能を理解するために、ひとつひとつ確認したことをまとめていこうと考えています。
インストール・環境構築は、以下の手順でできている前提です。
Nuxt.jsを整理する方針
Nuxt.js関連資料やサンプルソースを見ると混乱することが多いのは、Nuxt.jsはVue.jsの機能を包含していて、ひとつのソースの中で、どれがNuxt.js特有の機能で、どれがVue.jsの機能かの区別がつけにくいからだと感じています。
その混在しているイメージを図にしてみると、こんな感じに見えます。
なので、僕はアプローチとして、各技術要素にわけて、一回に少しずつ要素を確認していくことにしました。
分け方としては、以下のようになると考えています。
- Nuxt.js独自の要素ー基本
- Vue.js独自の要素
- BulmaのCSS要素(Burfyのclassは、Bulmaを使っている場合が多い)
- Buefy独自の要素
- Nuxt.js独自の要素ー応用
- テストフレームワーク他モジュールの要素
今回は、Nuxtの機能として重要なプラグインについて整理したいと思ってます。
サンプルコードで確かめる
Nuxt.jsで「プラグイン」を2種類作って、asycData()とmethodsの各ブロックの中で使ってみます。
都道府県情報・市区町村情報をWEB-APIから取得してくる処理を「プラグイン」化して、全体をすっきりさせようというのが、今回の目標になります。
プラグインについて
Nuxt.js では JavaScript プラグインを定義して使うことができます。
これには「axios」など外部プラグインもありますが、自前で作ることも可能です。
自前で作るプラグインは「plugins」フォルダに、拡張子「.js」で作ります。
Nuxt.jsのプラグインは感覚的にはサブルーチンみたいなんですが、他の言語のそれとは大きく違う特徴が2つあります。
- 定義の仕方で使える場所が異なること
- 使うときには「nuxt.config.js」に書いておく必要があること
プラグインは定義の仕方で使える場所が異なる
Nuxt.jsには。
- asyncData()みたいにページコンポーネントがロードされる前に実行されるもの(=thisが使えない)
- methods内の関数のようにロード後に実行されるもの(=thisが使える)
があって、どちらで使用する想定のプラグインかによって、定義の仕方が違います。
プラグインを使うときには「nuxt.config.js」に書いておく
自前で作るプラグインは、pluginsフォルダに.js拡張子でファイルを作成します。
それを、pagesのvueファイル内で使えるようにするには、「nuxt.config.js」の「plugins」ブロックに書いておく必要があります。
ここに書くのを忘れてたり、書き方が間違ってたりすると「not function(ファンクションじゃねーぞ!)」と怒られます。
他の言語(特にpythonとか)みたいに、importとかで参照できるようになりません。
プラグインにする場合としない場合のソースコードの比較
プラグインを使わない場合と使う場合を比較してかいてみます。
まず、プラグインを使わない場合です。
pages/cityapi.vue
<script> import axios from 'axios' export default { data () { return { prefecturesSelected: '', municipalitiesSelected: '', municipalitiesJson: null } }, asyncData () { return axios .get('https://opendata.resas-portal.go.jp/api/v1/prefectures', { headers: { 'X-API-KEY': 'nomgxxxxxxxxxxxxxxxwOjK' }, data: {} }) .then((res) => { return { prefecturesJson: res.data } }) }, methods: { async updateMunicipalitiesJson ({ prefecturesSelected }) { const url = `https://opendata.resas-portal.go.jp/api/v1/cities?prefCode=${prefecturesSelected} ` await axios .get(url, { headers: { 'X-API-KEY': 'nomgxxxxxxxxxxxxxOjK' }, data: {} }) .then((res) => { this.municipalitiesJson = res.data }) return this.municipalitiesJson } }, head: { title: 'CityApi page' } } </script>
それを今回はプラグインに外だしすることで。
<script> export default { async asyncData(context) { return {prefecturesJson: await context.app.$getPrefecturesJson()} }, methods:{ async updateMunicipalitiesJson({prefecturesSelected}){ this.municipalitiesJson = await this.$getmunicipalitiesJson(prefecturesSelected) } }, } </script>
こんな感じにします。
見通しがよくなりますし、何の処理かが明確で、とても良い感じになります。
では、やっていきます。
プラグインを作ります
今回は、以下の2種類のプラグインを作ります。
両方とも、プラグインの関数名に「$」をつけてますが、これは名前の衝突をさけるための「一般的な命名ルール」に従ってます。
別に$を付けない名前にしても問題なく動くのですが、別に無理に$をつけない名称にする理由もありませんので、そうしています。
プラグインを「this」を使って参照するケースのサンプル
今回の場合だと、onChangeイベントをトリガにして呼ばれる「updateMunicipalitiesJson」内で呼び出す「$getmunicipalitiesJson」がそれにあたります。
まず、ソースコードです。
plugins/getMunicipalitiesJson.js
import Vue from 'vue' import axios from 'axios' Vue.prototype.$getmunicipalitiesJson = (prefecturesSelected) => { const url = `https://opendata.resas-portal.go.jp/api/v1/cities?prefCode = ${prefecturesSelected} ` return axios .get(url, { headers: { 'X-API-KEY': 'XXXXXXXXXXXXXXXXXXOjK' }, data: {} }) .then((res) => { return res.data }) }
ポイントを補足します。
関数名の定義の仕方がポイントです。
Vue.prototype.$getmunicipalitiesJson = (prefecturesSelected) => {
ちょっと独特の形ですが、上記のようにします。
ざっくり言えば「Vue.prototype」に関数名を記述すれば、thisで参照できる関数になるよ・・ということです。
axiosを使った処理の中身については、ほぼ、元のコピペなのですが、以下の2点を変更しています。
- async/awaitをとっている。
- this.municipalitiesJson = res.dataではなく、「return res.data」にしている。
理由は、見たらわかるレベルなので補足はしません。
プラグインに外だししたわけですから、当然の処理をしてるだけです。
プラグインを「context.app」を使って参照するケースのサンプル
今度は、thisの使えない「asyncData()」の中でプラグインの関数を参照する方です。
context.app.$getPrefecturesJson()のように、「context.app」を使って参照します。
ソースコードです。
plugins/getPrefecturesJson.js
import axios from 'axios' export default ({ app }, inject) => { app.$getPrefecturesJson = () => { return axios .get('https://opendata.resas-portal.go.jp/api/v1/prefectures', { headers: { 'X-API-KEY': 'XXXXXXXXXXXXXXXXXXXXXOjK' }, data: {} }) .then((res) => { return res.data }) } }
ポイントです。
関数名を定義する部分が以下のようになっています。
export default ({ app }, inject) => {
app.$getPrefecturesJson = () => {
}
}
このappに関数名を記述することで、context.appで参照できるようになります。
とりあえず、この形を覚えておきます。
context.appというのは 「すべてのプラグインを含むルートの Vue インスタンス」なので、そこに加えてやる感じですかね。
こちらのaxiosを使った処理の内容は、ほぼ元のコピペです。
上記と同様に、async/awaitだけはずしてます。
プラグインを登録する
上記で2つのプラグインを作ったわけですが、そのままでは使えません。
プロジェクト内のVueソース内で使うため、nuxt.config.jsに登録する必要があります。
以下のように書きます。
nuxt.config.js
plugins: [ { src: '~/plugins/getMunicipalitiesJson.js' }, { src: '~/plugins/getPrefecturesJson.js' } ],
書き方はもう一種類あります。
こういう書き方です。
plugins: ['~/plugins/getMunicipalitiesJson.js']
実際、サンプルとかでも両方の書き方が混在しています。
でも。
混在しない方が良いと思っているので、自分は「 { src:'~/plugins/getMunicipalitiesJson.js'}」の書き方で統一しています。
上記のプラグインを反映させたpagesのサンプル
プラグインを利用するように変更した「cityapi.vue」です。
pages/cityapi.vue
<template> <div> <NLink to="/"> ルートのページへ戻ります </NLink> <br> <img src="/jamap.JPG" alt="image03"> <div v-if=" prefecturesJson.message == null"> <select v-model="prefecturesSelected" @change="updateMunicipalitiesJson({prefecturesSelected})"> <option value="" /> <option v-for="pOption in prefecturesJson.result" :key="pOption.id" :value="pOption.prefCode"> {{ pOption.prefName }} </option> </select> </div> <div v-if="municipalitiesJson != null"> <div v-if="municipalitiesJson.message == null"> <select v-model="municipalitiesSelected"> <option v-for="mOption in municipalitiesJson.result" :key="mOption.id" :value="mOption.cityCode"> {{ mOption.cityName }} </option> </select> </div> </div> <div v-else> <select v-model="municipalitiesSelected"> <option value="" /> </select> </div> </div> </template> <template> <div> <NLink to="/"> ルートのページへ戻ります </NLink> <br> <img src="/jamap.JPG" alt="image03"> <div v-if=" prefecturesJson.message == null"> <select v-model="prefecturesSelected" @change="updateMunicipalitiesJson({prefecturesSelected})"> <option value="" /> <option v-for="pOption in prefecturesJson.result" :key="pOption.id" :value="pOption.prefCode"> {{ pOption.prefName }} </option> </select> </div> <div v-if="municipalitiesJson != null"> <div v-if="municipalitiesJson.message == null"> <select v-model="municipalitiesSelected"> <option v-for="mOption in municipalitiesJson.result" :key="mOption.id" :value="mOption.cityCode"> {{ mOption.cityName }} </option> </select> </div> </div> <div v-else> <select v-model="municipalitiesSelected"> <option value="" /> </select> </div> </div> </template> <script> export default { data () { return { prefecturesSelected: '', municipalitiesSelected: '', municipalitiesJson: null } }, async asyncData (context) { return { prefecturesJson: await context.app.$getPrefecturesJson() } }, methods: { async updateMunicipalitiesJson ({ prefecturesSelected }) { this.municipalitiesJson = await this.$getmunicipalitiesJson(prefecturesSelected) } }, head: { title: 'CityApi page' } } </script>
特に補足はありません。
実行イメージ
プロジェクトをカレントフォルダにして「npm run dev」して、「http://localhost:3000」で表示します。
もちろん、Originチェックを回避できる「テスト用ショートカット」から起動したものですよ。
動きそのものは、NUXT自己流チュートリアル(4)と同じです。
ここから「市区町村のサンプル」のリンクをクリックします。
都道府県のSELECTを開いたところです。
ここで、東京でも選んでみます。
変化がないことを確認できればOKです。
あと、都道府県・市町村データ取得のWEB-APIを使ってますが、これには、API-KEYが必要ですので、こちらのサイトで登録してAPI-KEYを取得する必要があります。
おまけ:わかりづらいエラーの情報
プラグインを追加して実行するときに、こんなエラーに悩まされることがありました。
There are multiple modules with names that only differ in casing. friendly-errors 22:12:36
This can lead to unexpected behavior when compiling on a filesystem with other case-semantic.
Use equal casing. Compare these module identifiers:
なかなか、原因がわからなくてハマったので、追加情報として書いておきます。
自分の場合の原因は。
import Vue from 'vue'
を
import Vue from 'Vue'
とタイプミスしていたことでした。
わかってしまえば、バカみたいなケアレスミスなんですが、はまっている間はなかなか気づかないです。
ということで。
今回は、こんなところで。
ではでは。