"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

プラグインを作ってみる。thisで参照とコンテキストで参照の2種類ぶん。/NUXT自己流チュートリアル(1)-5

今回は、Nuxt.jsで「プラグイン」を2種類作って、asycData()とmethodsの各ブロックの中で使ってみます。

f:id:arakan_no_boku:20190509225706p:plain

この記事は「NUXT自己流チュートリアル(1)」として続き物で書いている記事の5回目です。

1回目から続けて読んでもらえることを想定して書いていますのでご了承ください。

arakan-pgm-ai.hatenablog.com

 

事前に頭にいれといた方がいいこと

 

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とかで書いてしまいそうになるかもしれないので念のため(自分だけかな?)。

 

チュートリアル実行の前提

 

自己流チュートリアル(1)で「前提」と「チュートリアルの準備」として書いていることは、できている必要があります。

つまり。

インストール・環境設定・テスト専用Chromeショートカットの作成うんぬんですね。

まだの方は、下記記事を先に参照して、環境設定をお願いします。

arakan-pgm-ai.hatenablog.com 

あと、都道府県・市町村データ取得のWEB-APIを使います。

API-KEYが必要になるので、まだ取得していない場合は、こちらのサイトで登録してAPI-KEYを取得しておいてください。

 

今回やってみること

 

NUXT自己流チュートリアル(4)で都道府県情報・市町村情報共、Selectにして、都道府県を選択すると連動して市町村のSelectの内容を変更する・・という、まあ、よくあるパターン・・をやりました。

それでちゃんと動くのですが、ちょっとソースの記述量が増えてガチャガチャします。

画面である関係上、<templete>内の記述は多くなるので、できるだけ<script>内はすっきりさせたい・・ということで、都道府県情報・市区町村情報をWEB-APIから取得してくる処理を「プラグイン」化して、全体をすっきりさせようというのが、今回の目標になります。

具体的には、NUXT自己流チュートリアル(4)だと、こんな感じでした。

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 インスタンス」なので、そこに加えてやる感じですかね。

ja.nuxtjs.org

こちらの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>
 

特に補足はありません。 

<templete>内は、NUXT自己流チュートリアル(4)と同じですし、<script>内は上で説明している通りですので。

cityapi.vueと今回追加したプラグイン関連のファイル以外のソースについても変更はありません。

about.vue以外は「NUXT自己流チュートリアル(2)」。

about.vueは「NUXT自己流チュートリアル(3)」で変更したままです。

動かすのに必要な場合は、各ページを参照ください。 

arakan-pgm-ai.hatenablog.com

arakan-pgm-ai.hatenablog.com

arakan-pgm-ai.hatenablog.com

 

さて実行してみます

 

プロジェクトをカレントフォルダにして「npm run dev」して、「http://localhost:3000」で表示します。

もちろん、Originチェックを回避できる「テスト用ショートカット」から起動したものですよ。

動きそのものは、NUXT自己流チュートリアル(4)と同じです。

f:id:arakan_no_boku:20190516234656p:plain

ここから「市区町村のサンプル」のリンクをクリックします。

f:id:arakan_no_boku:20190516234814p:plain

都道府県のSELECTを開いたところです。

ここで、東京でも選んでみます。

f:id:arakan_no_boku:20190516234944p:plain

変化がないことを確認できればOKです。

 

おまけとして、わかりづらいエラーの情報など

 

プラグインを追加して実行するときに、こんなエラーに悩まされることがありました。

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'

タイプミスしていたことでした。

わかってしまえば、バカみたいなケアレスミスなんですが、はまっている間はなかなか気づかないです。

ということで。

今回は、こんなところで。

ではでは。