20200426のvue.jsに関する記事は12件です。

【Vuex, Nuxt.js】子コンポーネントのアクションを実行しても状態が反映されない

最近Nuxt.jsを使ってサービスの開発をしてみてます。

フロントエンドのフレームワークの経験がなく慣れておらず、

状態管理でハマったところがあったので備忘録として投稿。

個人的にはてなブログにかいてましたが
技術系はQiitaに書こうと決めたので内容は下記の書き直しとなります。
https://moritomo7315.hatenablog.com/entry/vuex/state_manage/1

ちなみにログイン認証等は今回の記事の本質とは離れてるので割愛してます。

何にハマったか

userのログイン・ログアウトをVuexで

// login
loginStatus = true
// logout
loginStatus = false

のように状態管理しようとしたわけですが、下記のような現象が起きてしまいました。

  • ログアウトする場合:loginStatusがtrue->falseに変化
  • ログインする場合:loginStatusがfalseのまま

ソースコード

store

index.js
import Vuex from 'vuex'
import user from './modules/user'

export default () => new Vuex.Store({
  modules: {
    user
  }
})
user.js
const state = {
  user: null,
  loginStatus: false
}

const getters = {
  user: (state) => state.user,
  isLogin: (state) => state.loginStatus
}

const mutations = {
  setUser(state, { user }) {
    state.user = user
  },
  login(state) {
    state.loginStatus = true
  },
  logout(state) {
    state.loginStatus = false
    state.user = null
  }
}

const actions = {
  fetchUser({ commit }, user) {
    commit('setUser', {user})
  },
  login({ commit }) {
    commit('login')
  },
  logout({ commit }) {
    commit('logout')
  }
}

export default {
  namespaced: true,
  state,
  getters,
  actions,
  mutations
}

header コンポーネント

header.vue
<template>
  <div>
    <v-app-bar
      color="primary"
    >
      <v-toolbar-title>
        <nuxt-link to='/'>
          <img src="~/static/weblogo.png">
        </nuxt-link>
      </v-toolbar-title>
      <v-spacer></v-spacer>
      <v-menu offset-y>
        <template v-slot:activator="{ on }">
          <v-btn
            icon
            color="transparent"
            v-on="on"
          >
            <v-app-bar-nav-icon></v-app-bar-nav-icon>
          </v-btn>
        </template>
        <v-list v-if="loginStatus">
          <v-list-item
            nuxt
            to='#'
          >
            <v-list-item-title>マイページ</v-list-item-title>
          </v-list-item>
          <v-list-item
            @click="logout"
            nuxt
            to='/'
            inactive
          >
            <v-list-item-title>ログアウト</v-list-item-title>
          </v-list-item>
        </v-list>
        <v-list v-else>
          <v-list-item
            nuxt
            to='/login'
          >
            <v-list-item-title>ログイン</v-list-item-title>
          </v-list-item>
          <v-list-item
            nuxt
            to='/signup'
          >
            <v-list-item-title>会員登録</v-list-item-title>
          </v-list-item>
        </v-list>
      </v-menu>
    </v-app-bar>
  </div>
</template>
<script>
import { mapState, mapGetters, mapActions } from 'vuex';

export default {
  name: "Header",
  computed: {
    ...mapState({
      user: state => state.user.user,
      loginStatus: state => state.user.loginStatus
    })
  },
  methods: {
    ...mapActions('user',[
      "logout"
    ])
  }
}
</script>

login page

login.vue
<!-- EmailSigninコンポーネントを呼び出してるだけ。今後Twitterログイン等も入れたいので、コンポーネントに分けている -->
<template>
  <EmailSignin />
</template>

<script>
import EmailSignin from '~/components/emailSignin'
export default {
  components: {
    EmailSignin
  }
}
</script>
emailSignin.vue
<template>
  <div class="userpage">
    <h1>ログイン</h1>
    <div class="userform">
      <v-form>
        <v-text-field
          v-model="email"
          label="E-mail"
          outlined
          required
        ></v-text-field>

        <v-text-field
          v-model="password"
          label="password"
          outlined
          type="password"
          required
        ></v-text-field>
        <v-btn
          color="secondary"
          class="mr-4"
          @click="login"
          x-large
          nuxt
          to="/"
        >
          ログイン
        </v-btn>
        <nuxt-link
          to="#"
        >
          パスワードを忘れた方はこちら...
        </nuxt-link>
      </v-form>
    </div>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions } from "vuex";

export default {
  name: "EmailSignin",
  data() {
    return {
      email: '',
      password: '',
    }
  },
  computed: {
    ...mapState({
      user: state => state.user.user,
      loginStatus: state => state.user.loginStatus
    })
  },
  methods: {
    ...mapActions('user',[
      "login"
    ]),
    passworLogin () {
      this.login()
    }
  }
}
</script>

何が問題だったか

結論から言うと、

@clickの使い方が原因でした。

ソースコードでいうと、storeの記述に関しては問題なしです。

なぜログアウトはできて、ログインができなかったか

なぜログアウトはできるのに、
ログインはできないといった現象が起きてしまっていたかと言うと、

これは親子コンポーネントとイベントの関係があるみたいです。

僕のソースコードで実装されてるログアウトとログインの違いは

  • headerコンポーネントがコンポーネント内のアクションlogoutを呼びイベント発生
  • loginコンポーネントがemailSigninコンポーネント内のloginを呼びイベント発生

となっております。

どちらも@click="methodNameとしていますが、

ログインだけ動かないのはコンポーネントの親子関係が原因でした。

親コンポーネントに子コンポーネントのアクションイベントを実行させるには、

@click.native="methodName

と指定する必要があるみたいです。

まとめ

コンポーネントの親子関係を意識して、

そのコンポーネントが親か子に応じて

@click@click.nativeを指定するで解決しました。

まだvuexの概念を理解しきっていないので、

説明に使用してる言葉があやしいところもあるかもしれませんが、

無事解決できました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue-cliでsocket.ioを使う方法(socket.io-client)

はじめに

 基本的なことだからなのか書いている人があまりいなくて入れるのに少し困ったので書きました。

1. socket.io-clientをインストール

$ npm install socket.io-client

2. socket.ioを使いたいコンポーネントに以下を記述

~
</template>

<script>
import io from 'socket.io-client';  //追加

export default {
    name: 'name',
    data: function(){
        return {
            socket : io('localhost:4000')  //生成
        }
    },
}

おわりに

思いのほか簡単でした!

参考

https://qiita.com/dq-jiyoung-lee/items/76e368d88750f5a619d7

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ミニマムなVue.jsコンポーネントプログラミングその2(ルータ実装)

前回、"Hello, vue.js"を静的にレンダリングするだけのミニマムなシングルコンポーネントを持ったプロジェクトを作成しましたので、それをベースに拡張していきたいと思います。今回の趣旨はミニマムなルータ制御です。SPA的なクライアントアプリを組むためにはルータ機能が必要です。

やるべきことは、以下です。

  • App.vueは(ほぼ)抽象的なアプリケーションコンポーネント(画面の入れ物)とする。
  • 唯一の画面として、Home画面(コンポーネント)を追加する。
  • vue-routerでHome画面を表示させる。

Home画面作成

h1要素を持つだけのシンプルなコンポーネントです。vue-cliが自動生成してくれるようなフォルダ構成でちまちまちだちま作っていきます。

src/vies/Home.vue

<template>
  <div>
      <h1>これはほーむがめんです</h1>
  </div>
</template>

<script>
export default {
    name: 'Home'
}
</script>

ルータ追加

画面遷移をコントロールするルータを追加します。

(1)ルータライブラリインストール

npm install vue-router

(2)ルータ実装(src/router/index.js)

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue' 

Vue.use(VueRouter)

const routes = [
    {
        path: '/',
        name: 'Home',
        component: Home
    }
]

const router = new VueRouter({
    routes
})

export default router

ルート('/')とHome画面(コンポーネント)を連結させただけの
ミニマムなルータです。

(3) Vueインスタンスにルータ追加(src/main.js)

import Vue from 'vue'
import App from './App.vue'
import router from './router'    <== ココ

Vue.config.productionTip = false

new Vue({
  router,                        <== ココ
  render: h => h(App),
}).$mount('#app')

こうやって、Vueインスタンスにいろんな機能モジュール(コンポーネント)を突っ込んでおくと、Vueインスタンスに対してフレームワークがいろいろよしなにさばいてくれるんですねぇ。

こうしてながめると、router/index.jsで作ったルータも一種のコンポーネントとしてとらえたほうがいいかな。

(4) Appコンポーネントにルータ(によるView)を埋め込む(src/App.vue)

<template>
  <div>
    <router-view/>    <== ココ
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

npm run serveして、localhost:8081すると、
範囲を選択_012.png

補足

今回のやったことはつまり、

  1. 抽象的なアプリケーションコンポーネントを用意する。 (複数の画面を持つアプリケーションとして、Appコンポーネントを定義する)
  2. HOME画面を用意する。
  3. '/'をHOME画面と接続したルータ用意。
  4. アプリケーションにルータをジョイント。

って感じです。

次はサブコンポーネントを扱っていきたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ミニマムなコンポーネントプログラミングその2

前回、Hello, Vue.jsを静的にレンダリングするだけのミニマムなコンポーネントを持ったプロジェクトを作成しましたので、それをベースに拡張していきたいと思います。今回の趣旨はミニマムなルータ制御です。

  • Home画面を用意する。
  • vue-routerでHome画面を表示させる。

以上2点です。

Home画面作成

とりあえず前回のAppコンポーネントのように、h1要素を持つだけのシンプルなコンポーネントです。vue-cliがやってるようなフォルダ構成でちまちまちだちま作っていきます。

src/vies/Home.vue

<template>
  <div>
      <h1>これはほーむがめんです</h1>
  </div>
</template>

<script>
export default {
    name: 'Home'
}
</script>

ルータ追加

(1)ルータライブラリインストール

npm install vue-router

(2)ルータ実装(src/router/index.js)

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue' 

Vue.use(VueRouter)

const routes = [
    {
        path: '/',
        name: 'Home',
        component: Home
    }
]

const router = new VueRouter({
    routes
})

export default router

ルート('/')とHome画面(コンポーネント)を連結させただけの
ミニマムなルータです。

(3) Vueインスタンスにルータ追加(src/main.js)

import Vue from 'vue'
import App from './App.vue'
import router from './router'    <== ココ

Vue.config.productionTip = false

new Vue({
  router,                        <== ココ
  render: h => h(App),
}).$mount('#app')

こうやって、Vueインスタンスにいろんな機能モジュール(コンポーネント)を突っ込んでおくと、Vueインスタンスに対してフレームワークがいろいろよしなにさばいてくれるんですねぇ。

こうしてながめると、router/index.jsで作ったルータも一種のコンポーネントとしてとらえたほうがいいかな。

(4) Appコンポーネントにルータ(によるView)を埋め込む(src/App.vue)

<template>
  <div>
    <router-view/>    <== ココ
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

npm run serveして、localhost:8081すると、
範囲を選択_012.png

補足

今回のやったことはつまり、

  1. 抽象的なアプリケーションコンポーネントを用意する。 (複数の画面を持つアプリケーションとして、Appコンポーネントを定義する)
  2. HOME画面を用意する。
  3. '/'をHOME画面と接続したルータ用意。
  4. アプリケーションにルータをジョイント。

って感じです。

次はサブコンポーネントを扱っていきたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ミニマムなコンポーネントプログラミング

Vue.jsを使うと、コンポーネントプログラムというものの楽しさを体感できると思います。(正確に言うと、vue-cliを使ってコンポーネントプログラムを構成すると、かな?)

Web Componentsという概念も最近知ったところですが、MVCという概念に沿って、

  • データモデル定義(M)
  • UI構造(V)
  • データモデルとUI構造の制御インタフェースであるコントローラ(C)

みたいな感じにフォルダをバラバラに分けて管理するのはもう古い、ということなのかも。(適材適所はあるでしょうが)

環境

vue-cli 4.3.1

プロジェクト作成

vue create vue_sample
? Please pick a preset: 
  VueSample (typescript, router) 
  default (babel, eslint) 
❯ Manually select features 

まずはミニマム構成を用意したいので、マニュアルを指定。

? Check the features needed for your project: 
❯◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◯ Router
 ◯ Vuex
 ◯ CSS Pre-processors
 ◯ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

JavaScriptの新しい記法は使いたいので、Babelを選択し、後はオフ。後の設定はデフォルトとして、プロジェクトを作成。

コンポーネント構造をミニマム化

公開するindex.htmlは、

<div id="app"></div>

となっており、すでにミニマムです。

エントリポイントであるmain.jsは、

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

となっており、vueの基幹ライブラリのみimport、
唯一Appコンポーネントのみマウントしており、
こちらもすでににミニマムです。

App.vueを以下のように書き直します。

<template>
  <div>
    <h1>Hello, Vue.js</h1>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

UIの構造をテンプレート定義し、データモデルとコントローラを内包可能なVueインスタンスを定義/生成します。

  • UIの構造は見出しのみ(h1)
  • コンポーネントも名称のみ定義(name: 'App')

以上により、h1要素のみ備えたAppコンポーネント(Vueインスタンス)をレンダリングするだけのミニマムなプロジェクトが完成します。

npm run serveして、localhost:8081とかにアクセスすると、
範囲を選択_011.png
超シンプルな画面がレンダリングされます。

vue.jsを使った'hello world'と、
vue-cliによるコンポーネントプログラミングとしての'hello world'は別物だと思いますし、今回は後者を体験してみました。

次回はこのミニマムなコンポーネントの上に、ミニマムな画面遷移(vue-router)を乗せていこうと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ミニマムなVue.jsコンポーネントプログラミングその1(シングルコンポーネント)

Vue.jsを使うと、コンポーネントプログラムというものの楽しさを体感できると思います。(正確に言うと、vue-cliを使ってコンポーネントプログラムを構成すると、かな?)

Web Componentsという概念も最近知ったところですが、MVCという概念に沿って、

  • データモデル定義(M)
  • UI構造(V)
  • データモデルとUI構造の制御インタフェースであるコントローラ(C)

みたいな感じにフォルダをバラバラに分けて管理するのはもう古い、ということなのかも。(適材適所はあるでしょうが)

最終的にやりたいのは、SPAライクなクライアントアプリです。

従って、
ルータを内包し、
サーバサイドレンダリングはせず、
データはResutFul APIサーバを立ててアクセスし、
アプリ自体は部品(コンポーネント)を組み合わせてつくる、
そんなテーマで進めたいと思います。

そして応用編まで行けたら、Typescriptと組み合わせたモダンな記法とか、やってみたいところです。まずはVue.jsネイティブの理解が必要ですけれども。

今回はシングルコンポーネントだけで構成されたVue.jsアプリを組んでみます。

環境

vue-cli 4.3.1

プロジェクト作成

vue create vue_sample
? Please pick a preset: 
  VueSample (typescript, router) 
  default (babel, eslint) 
❯ Manually select features 

まずはミニマム構成を用意したいので、マニュアルを指定。

? Check the features needed for your project: 
❯◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◯ Router
 ◯ Vuex
 ◯ CSS Pre-processors
 ◯ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

JavaScriptの新しい記法は使いたいので、Babelを選択し、後はオフ。後の設定はデフォルトとして、プロジェクトを作成。

コンポーネント構造をミニマム化

公開するindex.htmlは、

<div id="app"></div>

となっており、すでにミニマムです。

エントリポイントであるmain.jsは、

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

となっており、vueの基幹ライブラリのみimport、
唯一Appコンポーネントのみマウントしており、
こちらもすでににミニマムです。

App.vueを以下のように書き直します。

<template>
  <div>
    <h1>Hello, Vue.js</h1>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

UIの構造をテンプレート定義し、データモデルとコントローラを内包可能なVueインスタンスを定義/生成します。

  • UIの構造は見出しのみ(h1)
  • コンポーネントも名称のみ定義(name: 'App')

以上により、h1要素のみ備えたAppコンポーネント(Vueインスタンス)をレンダリングするだけのミニマムなプロジェクトが完成します。

npm run serveして、localhost:8081とかにアクセスすると、
範囲を選択_011.png
超シンプルな画面がレンダリングされました。

補足

Vue.jsの長所の一つに、「プログレッシブなフレームワーク」という文言がありますが、つまりこれは、

  • 便利なデータバインディングのみ使う
  • 再利用性の高いコンポーネント設計を導入する

等々、シーンに応じた導入ができるフレキシブルさを指していると思います。SPAにしたければ内部ルータを埋め込んだり。

今回のようなシンプルなケースでは、データバインディングのみ使った
非コンポーネントVue.jsプログラミングで全然間に合いますが、ヘッダ、フッタ等、画面が増えていけばどうしても部品を組み合わせたくなってくるのでコンポーネント設計を取り組んでみます。

次回

次回はこのミニマムなコンポーネントの上に、ミニマムな画面遷移(vue-router)を乗せていこうと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ミニマムなコンポーネントプログラミング1

Vue.jsを使うと、コンポーネントプログラムというものの楽しさを体感できると思います。(正確に言うと、vue-cliを使ってコンポーネントプログラムを構成すると、かな?)

Web Componentsという概念も最近知ったところですが、MVCという概念に沿って、

  • データモデル定義(M)
  • UI構造(V)
  • データモデルとUI構造の制御インタフェースであるコントローラ(C)

みたいな感じにフォルダをバラバラに分けて管理するのはもう古い、ということなのかも。(適材適所はあるでしょうが)

環境

vue-cli 4.3.1

プロジェクト作成

vue create vue_sample
? Please pick a preset: 
  VueSample (typescript, router) 
  default (babel, eslint) 
❯ Manually select features 

まずはミニマム構成を用意したいので、マニュアルを指定。

? Check the features needed for your project: 
❯◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◯ Router
 ◯ Vuex
 ◯ CSS Pre-processors
 ◯ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

JavaScriptの新しい記法は使いたいので、Babelを選択し、後はオフ。後の設定はデフォルトとして、プロジェクトを作成。

コンポーネント構造をミニマム化

公開するindex.htmlは、

<div id="app"></div>

となっており、すでにミニマムです。

エントリポイントであるmain.jsは、

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

となっており、vueの基幹ライブラリのみimport、
唯一Appコンポーネントのみマウントしており、
こちらもすでににミニマムです。

App.vueを以下のように書き直します。

<template>
  <div>
    <h1>Hello, Vue.js</h1>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

UIの構造をテンプレート定義し、データモデルとコントローラを内包可能なVueインスタンスを定義/生成します。

  • UIの構造は見出しのみ(h1)
  • コンポーネントも名称のみ定義(name: 'App')

以上により、h1要素のみ備えたAppコンポーネント(Vueインスタンス)をレンダリングするだけのミニマムなプロジェクトが完成します。

npm run serveして、localhost:8081とかにアクセスすると、
範囲を選択_011.png
超シンプルな画面がレンダリングされました。

補足

Vue.jsの長所の一つに、「プログレッシブなフレームワーク」という文言がありますが、つまりこれは、

  • 便利なデータバインディングのみ使う
  • 再利用性の高いコンポーネント設計を導入する

等々、シーンに応じた導入ができるフレキシブルさを指していると思います。SPAにしたければ内部ルータを埋め込んだり。

今回のようなシンプルなケースでは、データバインディングのみ使った
非コンポーネントVue.jsプログラミングで全然間に合いますが、ヘッダ、フッタ等、画面が増えていけばどうしても部品を組み合わせたくなってくるのでコンポーネント設計を取り組んでみます。

次回

次回はこのミニマムなコンポーネントの上に、ミニマムな画面遷移(vue-router)を乗せていこうと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ミニマムなコンポーネントプログラミングその1

Vue.jsを使うと、コンポーネントプログラムというものの楽しさを体感できると思います。(正確に言うと、vue-cliを使ってコンポーネントプログラムを構成すると、かな?)

Web Componentsという概念も最近知ったところですが、MVCという概念に沿って、

  • データモデル定義(M)
  • UI構造(V)
  • データモデルとUI構造の制御インタフェースであるコントローラ(C)

みたいな感じにフォルダをバラバラに分けて管理するのはもう古い、ということなのかも。(適材適所はあるでしょうが)

環境

vue-cli 4.3.1

プロジェクト作成

vue create vue_sample
? Please pick a preset: 
  VueSample (typescript, router) 
  default (babel, eslint) 
❯ Manually select features 

まずはミニマム構成を用意したいので、マニュアルを指定。

? Check the features needed for your project: 
❯◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◯ Router
 ◯ Vuex
 ◯ CSS Pre-processors
 ◯ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

JavaScriptの新しい記法は使いたいので、Babelを選択し、後はオフ。後の設定はデフォルトとして、プロジェクトを作成。

コンポーネント構造をミニマム化

公開するindex.htmlは、

<div id="app"></div>

となっており、すでにミニマムです。

エントリポイントであるmain.jsは、

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

となっており、vueの基幹ライブラリのみimport、
唯一Appコンポーネントのみマウントしており、
こちらもすでににミニマムです。

App.vueを以下のように書き直します。

<template>
  <div>
    <h1>Hello, Vue.js</h1>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

UIの構造をテンプレート定義し、データモデルとコントローラを内包可能なVueインスタンスを定義/生成します。

  • UIの構造は見出しのみ(h1)
  • コンポーネントも名称のみ定義(name: 'App')

以上により、h1要素のみ備えたAppコンポーネント(Vueインスタンス)をレンダリングするだけのミニマムなプロジェクトが完成します。

npm run serveして、localhost:8081とかにアクセスすると、
範囲を選択_011.png
超シンプルな画面がレンダリングされました。

補足

Vue.jsの長所の一つに、「プログレッシブなフレームワーク」という文言がありますが、つまりこれは、

  • 便利なデータバインディングのみ使う
  • 再利用性の高いコンポーネント設計を導入する

等々、シーンに応じた導入ができるフレキシブルさを指していると思います。SPAにしたければ内部ルータを埋め込んだり。

今回のようなシンプルなケースでは、データバインディングのみ使った
非コンポーネントVue.jsプログラミングで全然間に合いますが、ヘッダ、フッタ等、画面が増えていけばどうしても部品を組み合わせたくなってくるのでコンポーネント設計を取り組んでみます。

次回

次回はこのミニマムなコンポーネントの上に、ミニマムな画面遷移(vue-router)を乗せていこうと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unknown custom element: <TagName> - did you register the component correctly?

上記のエラー、いくつか原因があるそうです。以下の記事で気づくことができました。助かりました。

自分の場合、循環参照で問題が起こっていました。解決策は、以下のように非同期 import (asynchronous import) させることでです。

// 誤
import Component from './Component.vue'
export default {
  components: {
    Component
  },
}
// 正
export default {
  components: {
    Component: () => import('./Component.vue')
  },
}

他にも require を使う方法も記載されていました。以下、公式ドキュメントになります。


自分の場合はさらに Content と言うコンポーネント名を使おうとしていました。これが content と言う非推奨になっているタグとバッティングしていたらしく、さきに非推奨の content が適用されてさらによくわからない状態になっていました... orz

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsとWordPressを共存させる(OGPにも対応)

地元のグルメ情報を紹介するWebアプリをVue.jsで作り、WordPressで運営しているサイトのサブディレクトリー上に公開しました。また。WebアプリのURLがSNSでシェアされた時に写真が展開されるよう、OGPに対応させています。

この記事ではWordPressを使っているサイトにVue.jsのアプリを同居させ、プリレンダリングを使ってOGPに対応させる方法について紹介します。WordPressは https://example.com で運営され、そこにVueアプリを https://example.com/app-name/ で公開するものとします。

前提とする環境

Vue CLIを使ってアプリを開発しています。

$ npm install -g @vue/cli 
$ vue --version
@vue/cli 4.2.3

ルート以外でVueアプリを公開する

開発したアプリはルートディレクトリ https://example.com/ で公開されるように作られています。これを、サブディレクトリ https://example.com/app-name/ で公開されるように変更します。

WordPressをサーバーのディレクトリー/にインストールしている場合、その下に新しく/app-nameディレクトリーを作り、Vueアプリの成果物をアップロードします。

最初はWordPressの動作をよく理解していなかったので、

  • /app-name/ でアクセスできるページが必要?
  • それなら「固定ページ」で app-name を作ればいいのでは?

と誤解していたのですが、そんなことは不要でした。

vue.config.js を変更する

vue.config.js に publicPath [公式]、outputDir [公式] を追記します。

vue.config.js
module.exports = {
  publicPath: '/app-name/',
  outputDir: 'dist/app-name/',
  ...
}

まず、開発用サーバーで正しく動作するかを確認します。

$ npm run serve
...
 DONE  Compiled successfully in XXXXms

  App running at:
  - Local:   http://localhost:8080/app-name/ 
  - Network: http://XXX.XXX.XXX.XXX:8080/app-name/

  Note that the development build is not optimized.
  To create a production build, run npm run build.

Webブラウザーで http://localhost:8080/app-name/ にアクセスして、正しく動作することを確認します。

public ディレクトリーに.htaccessを作成してビルド

WordPressはWebブラウザーが/app-nameにアクセスした時に/app-name/index.phpを読み込むように設定されています。その設定はサーバーの/.htaccessに書いてあります。
Vue.jsでは/app-nameにアクセスした時に(index.phpではなく)/app-name/index.htmlを読み込むことを期待します。そこで、/app-name/.htaccessを準備して/app-nameにアクセスしたときの動作を変更します。ここで作成する.htaccessファイルは開発マシンのpublicディレクトリーに作ります。

public/.htaccess
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /app-name/
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . index.html [L]
</IfModule>

参考:WordPressのサブディレクトリでVueアプリを動かす

次にビルドを実行して、dist/app-name/ に成果物が出力されることを確認します。なお、vue uiでbuildタスクを動かす場合、vue.config.js の outputDir が無視されます。UIからoutputを設定してください。

参考:outputDir config not working #2639

$npm run build
...
 DONE  Build complete. The dist/gourmet directory is ready to be deployed.
 INFO  Check out deployment instructions at https://cli.vuejs.org/guide/deployment.html

WordPressの.htaccessを書き換える

生成された dist/app-name をサーバーの/app-nameとしてアップロードします。ここで https://example.com/app-name にアクセスしても、まだVueアプリは動作しません。サーバー側の/.htaccessを修正して、/app-nameへのアクセスだけは/app-name/.htaccessに書いたルールで動作させる必要があります。

修正を間違えるとWordPressが正しく動作しなくなるため、.htaccessファイルのコピーを別名で作ってから作業してください。

/.htaccess
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !(^/app-name/) # この行を追加
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

参考:WordPressのサブディレクトリでVueアプリを動かす

https://example.com/app-name にアクセスしてVueアプリが正しく動作することを確認してください。

VueアプリをOGPに対応させる

OGPではシェアしたときに表示したい写真のURLなどをHTMLの

タグにあるタグに記述します。そのページのURLがSNSでシェアされた時に、SNSのボットがそのURLにアクセスし、metaタグを解析することでSNSに表示する情報を決めています。

VueアプリのトップページをOGPに対応させる

public/index.html を編集して、OGPに対応させます。このファイルはビルドにより dist/app-name/index.html として出力されるのでそのままサーバーにアップロードして公開します。

public/index.html
<!DOCTYPE html>
<html lang="ja">
  <head prefix="og:http://ogp.me/ns# fb:http://ogp.me/ns/fb# website:http://ogp.me/ns/website#">
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="/favicon.ico">
    <title>[アプリ名]</title>
    <meta name="description" content="[アプリの説明]" />
    <meta property="og:type" content="website" />
    <meta property="og:site_name" content="[サイト名]" />
    <meta property="og:title" content="[アプリ名]" />
    <meta property="og:url" content="https://example.com/app-name/" />
    <meta property="og:image" content="https://example.com/app-name/img/app-name.jpg" />
    <meta property="og:description" content="[アプリの説明]" />
    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:site" content="@[アプリのTwitterアカウント名]" />
    <meta name="twitter:player" content="@[アプリのTwitterアカウント名]" />
  </head>
  <body>
    <noscript>
      <strong>We're sorry but vue_app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

公開後、OGPの情報が正しく設定されているかを確認するため、以下のツールにアプリのURLを入力して動作を確かめます。

Vue Routerで作ったページをOGPに対応させる

OGP対応で問題になるのが、Vue Routerを使ったアプリです。

OGPの情報を収集するFacebookやTwitterのボットはJavaScriptに対応していません。なので、Vue Routerで生成したURLにボットがアクセスしてもOGPの情報を収集することがでず、そのURLをSNSでシェアしても、写真などが展開されることはありません。

そこでビルドの際にヘッドレスブラウザーPuppeteerを使ってプリレンダリングを実施し、Vue RouterのURLごとにindex.htmlを成果物として作成します。出来上がったindex.htmlをそのままサーバーにアップロードして公開すれば、ボットがOGPの情報を収集できるようになります。

vue-meta を使ってURLごとにOGPを設定する

Nuxt.jsに含まれるvue-metaを使えば、URLごとに異なるOGP情報を設定できます。

vue-metaを使う時に気をつける必要があるのは、変更するタグには data-vmid 属性を付与することです。

ここでは、description, og:title, og:url, og:image, og:description の5つを変更するため、public/index.html を次のように修正します。

public/index.html
...
    <meta data-vmid="description" name="description" content="[アプリの説明]" />
    <meta data-vmid="og:title" property="og:title" content="[アプリ名]" />
    <meta data-vmid="og:url" property="og:url" content="https://example.com/app-name/" />
    <meta data-vmid="og:image" property="og:image" content="https://example.com/app-name/img/app-name.jpg" />
    <meta data-vmid="og:description" property="og:description" content="[アプリの説明]" />
...

その上で、vue-meta をインストールして、URLに応じてタグを書き換えるために metaInfo() を記述します。

$ npm install --save vue-meta
Spot.vue
...
<script>
...
  data: {
    return: function() {
      id: '...',
      titie: '...',
      desc: '...'
    }
  },
  metaInfo() {
    return {
      title,
      meta: [
        { vmid: 'description', name: 'description', content: desc },
        { vmid: 'og:title', property: 'og:title', content: title },
        { vmid: 'og:description', property: 'og:description', content: desc },
        { vmid: 'og:url', property: 'og:url', content: `https://example.com/app-name/spots/${this.id}/` },
        { vmid: 'og:image', property: 'og:image',
          content: `https://example.com/app-name/img/${this.id}.jpg`
        }
      ]
    }
  }
}
</script>

参考:メタタグが重複したときは? - NuxtJS

prerender-spa-plugin を使ってビルドの際にプリレンダリングを実施する

prerender-spa-pluginを使ってビルド時に開発用サーバーを起動し、PupeteerでVueアプリにアクセスすることでHTMLを生成していきます。

ここではVueアプリのURLとして /app-name/spots/00001/ から /app-name/spots/00100/ が存在するとします。

$ npm install --save prerender-spa-plugin

staticDir は app-name なし、indexPath は app-name ありで index.html まで指定することに気をつけてください。

なお、アクセスする先の /app-name/spots/:id は、アクセスしてから <div id="spot" /> が表示されるまでに少し時間がかかるので、Puppeteer にそのタグが出るまで待たせるようにしています。皆さんのアプリには不要なので削除してご利用ください。

vue.config.js
const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer

module.exports = {
  publicPath: '/app-name/',
  outputDir: 'dist/app-name/',
  configureWebpack: () => {
    if (process.env.NODE_ENV === 'production') {
      const routes = []
      for (let i = 1; i <= 100; i++) {
        let id = String(i).padStart(5, '0')
        routes.push(`/app-name/spots/${id}/`)
      }
      return {
        plugins: [
          new PrerenderSPAPlugin({
            staticDir: path.join(__dirname, 'dist'),
            indexPath: path.join(__dirname, 'dist/app-name/index.html'),
            routes,
            renderer: new Renderer({
              renderAfterElementExists: '#spot'
            }),
          })
        ]
      }
    }
  }
}

あとはビルドを実行して、dist/app-name/spots の下に 00001/index.html, ..., 00100/index.html がそれぞれ出力されて、タグに期待通りの情報が記述されていることを確認してください。

参考:プリレンダリングを用いてVue.jsのSPAをビルドする導入から設定まで

問題がなければdist/app-nameをサーバーにアップロードし、Facebook デバッガーなどで https://example.com/app-name/spots/00001/ などが期待通りに動作するか確認してください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsとWordPressを共存させる(OGPにも対応)

地元のグルメ情報を紹介するWebアプリをVue.jsで作り、WordPressで運営しているサイトのサブディレクトリー上に公開しました。また。WebアプリのURLがSNSでシェアされた時に写真が展開されるよう、OGPに対応させています。

この記事ではWordPressを使っているサイトにVue.jsのアプリを同居させ、プリレンダリングを使ってOGPに対応させる方法について紹介します。WordPressは https://example.com で運営され、そこにVueアプリを https://example.com/app-name/ で公開するものとします。

前提とする環境

Vue CLIを使ってアプリを開発しています。

$ npm install -g @vue/cli 
$ vue --version
@vue/cli 4.2.3

ルート以外でVueアプリを公開する

開発したアプリはルートディレクトリ https://example.com/ で公開されるように作られています。これを、サブディレクトリ https://example.com/app-name/ で公開されるように変更します。

WordPressをサーバーのディレクトリー/にインストールした場合、/app-nameディレクトリーを作ってその下にVueアプリの成果物を配置します。

vue.config.js を変更する

vue.config.js に publicPath [公式]、outputDir [公式] を追記します。

vue.config.js
module.exports = {
  publicPath: '/app-name/',
  outputDir: 'dist/app-name/',
  ...
}

まず、開発用サーバーで正しく動作するかを確認します。

$ npm run serve
...
 DONE  Compiled successfully in XXXXms

  App running at:
  - Local:   http://localhost:8080/app-name/ 
  - Network: http://XXX.XXX.XXX.XXX:8080/app-name/

  Note that the development build is not optimized.
  To create a production build, run npm run build.

Webブラウザーで http://localhost:8080/app-name/ にアクセスして、正しく動作することを確認します。

public ディレクトリーに.htaccessを作成してビルド

WordPressはWebブラウザーが/app-nameにアクセスした時に/app-name/index.phpを読み込むように設定されています。その設定はサーバーの/.htaccessに書いてあります。
Vue.jsでは/app-nameにアクセスした時に(index.phpではなく)/app-name/index.htmlを読み込むことを期待します。そこで、/app-name/.htaccessを準備して/app-nameにアクセスしたときの動作を変更します。ここで作成する.htaccessファイルは開発マシンのpublicディレクトリーに作ります。

public/.htaccess
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /app-name/
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . index.html [L]
</IfModule>

参考:WordPressのサブディレクトリでVueアプリを動かす

次にビルドを実行して、dist/app-name/ に成果物が出力されることを確認します。なお、vue uiでbuildタスクを動かす場合、vue.config.js の outputDir が無視されます。UIからoutputを設定してください。

参考:outputDir config not working #2639

$npm run build
...
 DONE  Build complete. The dist/gourmet directory is ready to be deployed.
 INFO  Check out deployment instructions at https://cli.vuejs.org/guide/deployment.html

WordPressの.htaccessを書き換える

生成された dist/app-name をサーバーの/app-nameとしてアップロードします。ここで https://example.com/app-name にアクセスしても、まだVueアプリは動作しません。サーバー側の/.htaccessを修正して、/app-nameへのアクセスだけは/app-name/.htaccessに書いたルールで動作させる必要があります。

修正を間違えるとWordPressが正しく動作しなくなるため、.htaccessファイルのコピーを別名で作ってから作業してください。

/.htaccess
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !(^/app-name/) # この行を追加
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

参考:WordPressのサブディレクトリでVueアプリを動かす

https://example.com/app-name にアクセスしてVueアプリが正しく動作することを確認してください。

VueアプリをOGPに対応させる

OGPではシェアしたときに表示したい写真のURLなどをHTMLの

タグにあるタグに記述します。そのページのURLがSNSでシェアされた時に、SNSのボットがそのURLにアクセスし、metaタグを解析することでSNSに表示する情報を決めています。

VueアプリのトップページをOGPに対応させる

public/index.html を編集して、OGPに対応させます。このファイルはビルドにより dist/app-name/index.html として出力されるのでそのままサーバーにアップロードして公開します。

public/index.html
<!DOCTYPE html>
<html lang="ja">
  <head prefix="og:http://ogp.me/ns# fb:http://ogp.me/ns/fb# website:http://ogp.me/ns/website#">
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="/favicon.ico">
    <title>[アプリ名]</title>
    <meta name="description" content="[アプリの説明]" />
    <meta property="og:type" content="website" />
    <meta property="og:site_name" content="[サイト名]" />
    <meta property="og:title" content="[アプリ名]" />
    <meta property="og:url" content="https://example.com/app-name/" />
    <meta property="og:image" content="https://example.com/app-name/img/app-name.jpg" />
    <meta property="og:description" content="[アプリの説明]" />
    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:site" content="@[アプリのTwitterアカウント名]" />
    <meta name="twitter:player" content="@[アプリのTwitterアカウント名]" />
  </head>
  <body>
    <noscript>
      <strong>We're sorry but vue_app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

公開後、OGPの情報が正しく設定されているかを確認するため、以下のツールにアプリのURLを入力して動作を確かめます。

Vue Routerで作ったページをOGPに対応させる

OGP対応で問題になるのが、Vue Routerを使ったアプリです。

OGPの情報を収集するFacebookやTwitterのボットはJavaScriptに対応していません。なので、Vue Routerで生成したURLにボットがアクセスしてもOGPの情報を収集することがでず、そのURLをSNSでシェアしても、写真などが展開されることはありません。

そこでビルドの際にヘッドレスブラウザーPuppeteerを使ってプリレンダリングを実施し、Vue RouterのURLごとにindex.htmlを成果物として作成します。出来上がったindex.htmlをそのままサーバーにアップロードして公開すれば、ボットがOGPの情報を収集できるようになります。

vue-meta を使ってURLごとにOGPを設定する

Nuxt.jsに含まれるvue-metaを使えば、URLごとに異なるOGP情報を設定できます。

vue-metaを使う時に気をつける必要があるのは、変更するタグには data-vmid 属性を付与することです。

ここでは、description, og:title, og:url, og:image, og:description の5つを変更するため、public/index.html を次のように修正します。

public/index.html
...
    <meta data-vmid="description" name="description" content="[アプリの説明]" />
    <meta data-vmid="og:title" property="og:title" content="[アプリ名]" />
    <meta data-vmid="og:url" property="og:url" content="https://example.com/app-name/" />
    <meta data-vmid="og:image" property="og:image" content="https://example.com/app-name/img/app-name.jpg" />
    <meta data-vmid="og:description" property="og:description" content="[アプリの説明]" />
...

その上で、vue-meta をインストールして、URLに応じてタグを書き換えるために metaInfo() を記述します。

$ npm install --save vue-meta
Spot.vue
...
<script>
...
  data: {
    return: function() {
      id: '...',
      titie: '...',
      desc: '...'
    }
  },
  metaInfo() {
    return {
      title,
      meta: [
        { vmid: 'description', name: 'description', content: desc },
        { vmid: 'og:title', property: 'og:title', content: title },
        { vmid: 'og:description', property: 'og:description', content: desc },
        { vmid: 'og:url', property: 'og:url', content: `https://example.com/app-name/spots/${this.id}/` },
        { vmid: 'og:image', property: 'og:image',
          content: `https://example.com/app-name/img/${this.id}.jpg`
        }
      ]
    }
  }
}
</script>

参考:メタタグが重複したときは? - NuxtJS

prerender-spa-plugin を使ってビルドの際にプリレンダリングを実施する

prerender-spa-pluginを使ってビルド時に開発用サーバーを起動し、PupeteerでVueアプリにアクセスすることでHTMLを生成していきます。

ここではVueアプリのURLとして /app-name/spots/00001/ から /app-name/spots/00100/ が存在するとします。

$ npm install --save prerender-spa-plugin

staticDir は app-name なし、indexPath は app-name ありで index.html まで指定することに気をつけてください。

なお、アクセスする先の /app-name/spots/:id は、アクセスしてから <div id="spot" /> が表示されるまでに少し時間がかかるので、Puppeteer にそのタグが出るまで待たせるようにしています。皆さんのアプリには不要なので削除してご利用ください。

vue.config.js
const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer

module.exports = {
  publicPath: '/app-name/',
  outputDir: 'dist/app-name/',
  configureWebpack: () => {
    if (process.env.NODE_ENV === 'production') {
      const routes = []
      for (let i = 1; i <= 100; i++) {
        let id = String(i).padStart(5, '0')
        routes.push(`/app-name/spots/${id}/`)
      }
      return {
        plugins: [
          new PrerenderSPAPlugin({
            staticDir: path.join(__dirname, 'dist'),
            indexPath: path.join(__dirname, 'dist/app-name/index.html'),
            routes,
            renderer: new Renderer({
              renderAfterElementExists: '#spot'
            }),
          })
        ]
      }
    }
  }
}

あとはビルドを実行して、dist/app-name/spots の下に 00001/index.html, ..., 00100/index.html がそれぞれ出力されて、タグに期待通りの情報が記述されていることを確認してください。

参考:プリレンダリングを用いてVue.jsのSPAをビルドする導入から設定まで

問題がなければdist/app-nameをサーバーにアップロードし、Facebook デバッガーなどで https://example.com/app-name/spots/00001/ などが期待通りに動作するか確認してください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vuexのmutationsの動きを理解する

vuex

image.png

画像は、Vue.js + Vuexでデータが循環する全体像を図解してみた - Qiitaを引用させていただきました。

この流れに沿って、本の詳細を表示するsetBookInfoメソッドの動きを見てみます。

①→コンポーネント側のsetBookInfoをコミットすることで、Vuex側のsetBookInfoが発火する。
②、③→発火された結果、mutationsからstateに値が渡される。

image.png

④→computedプロパティで、コンポーネントからvuexストアを参照することができる。参照した結果、template側で値を使うことができる。
※そのコンポーネントだけでVuexの値を参照したい場合は、computedで参照するのが望ましいが、複数のコンポーネントからVuexの値を参照したい場合は、gettersを使用する。

image.png

非同期処理や、actions,gettersについては、使い所などまだ理解できていないので、わかり次第また更新します。
理解間違っていましたら、ご指摘いただけると幸いです。

getters:複数コンポーネントで、Vuexストアから値を参照するときに使う。
state:値の状態
mutations:stateを変更するためのメソッドのようなもの。同期処理でなければならない。
actions:mutationsをコミットするためのもの。非同期処理でなければならない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む