"BOKU"のITな日常

BOKUが勉強したり、考えたことを頭の整理を兼ねてまとめてます。

NuxtでavaScript プラグインを定義して作る/thisで参照とコンテキストで参照の2パターン

f:id:arakan_no_boku:20190509225706p:plain

目次

はじめに

リライトして目次を追加しました。

内容は2019年5月当時のままです。

僕がNuxt.jsの各機能を理解するために、ひとつひとつ確認したことをまとめていこうと考えています。

インストール・環境構築は、以下の手順でできている前提です。

arakan-pgm-ai.hatenablog.com

Nuxt.jsを整理する方針

Nuxt.js関連資料やサンプルソースを見ると混乱することが多いのは、Nuxt.jsはVue.jsの機能を包含していて、ひとつのソースの中で、どれがNuxt.js特有の機能で、どれがVue.jsの機能かの区別がつけにくいからだと感じています。

その混在しているイメージを図にしてみると、こんな感じに見えます。

f:id:arakan_no_boku:20190511100242p:plain

なので、僕はアプローチとして、各技術要素にわけて、一回に少しずつ要素を確認していくことにしました。

分け方としては、以下のようになると考えています。

  1. Nuxt.js独自の要素ー基本
  2. Vue.js独自の要素
  3. BulmaのCSS要素(Burfyのclassは、Bulmaを使っている場合が多い)
  4. Buefy独自の要素
  5. Nuxt.js独自の要素ー応用
  6. テストフレームワーク他モジュールの要素

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

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>
 

特に補足はありません。

 

実行イメージ

プロジェクトをカレントフォルダにして「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です。

あと、都道府県・市町村データ取得の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'

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

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

ということで。

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

ではでは。