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

vue.js(nuxt.js)のdataはvuexのgettersを使ってデータ管理しよう

はじめに

vueinstanceでは親子component同士でdataを通信をする場合はProps/$emitcoやcomponent同士でデータ通信をするためにはEvent Busを使うのが一般ですがこのように複雑なdata通信を簡単に通信することができる考え方がVuxです。
image.png

Vuexとは

ざっくりですが下記のイメージ図のように中央管理所からデータを必要な時呼び出しして使えるイメージです。

image.png

自分のイメージだと親と子component同士関係なく中央管理所からdataを管理し必要な時使うのがVuexでつまり中央管理所はVuexとのことです。

公式からの説明は下記です。

Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。 また Vue 公式の開発ツール拡張と連携し、設定なしでタイムトラベルデバッグやステートのスナップショットのエクスポートやインポートのような高度な機能を提供します。
https://vuex.vuejs.org/ja/

vuex使い方

nuxtインストールは公式サイト参考 https://ja.nuxtjs.org/guide/installation
nuxtをインストールしたらvuexをインストールします。

$ npm i vuex

nuxtのプロジェクトを作成すると直下にstoreフォルダが作成されるのでstore直下にindex.jsを新しく作って下記のサンプルコードのようにvuexをimportして必要なdataの登録をします。

store/index.js
import Vuex from 'vuex'

export const appStore = () => {
  return new Vuex.Store({
    state: {//data
      myData:{
        firstName: 'thunder',
        lastName: 'fury',
      }
    },
    mutations: {
    },
    actions: {
    }
  })
};
export default appStore

vuexのstateはvueinstanceで言うと下記のdataの役割です。
storeにあるdataを使うためには$storeで呼び出しができます。

<template lang="pug">
  div
    p {{ $store.state.myData.firstName }}
    p {{ $store.state.myData.lastName }}
</template>

image.png
これでも良いですがdataのオブジェクトの下層が深い場合など繰り返しコードが多くなってしまうのでその時使うのがGettersです。

Getters実行イメージ

image.png

storeのindex.jsにstateのdataをgettersに関数として登録してreturnして使える流れです。

store/index.js.js
getters: {
   myData: state => {
      return state.myData
   },
//上下同じ
getters: {
   myData: function(state) {
      return state.myData
   },

どちらでも書き方は問題ないです。
少しでもにコード量を減らすためにはarrow functionが良いか思います。

登録が終わったら使いたいvueのcomponentmapGettersをimportをしてcomputedstore/index.js宣言した関数を配列の中に入れます。
そうすることに寄って$store.stateから呼び出したのをcomponent内でで宣言したようにで呼び出しすることができます。

<template lang="pug">
  div
    p {{ myData.firstName }} // p {{ $store.state.myData.firstName }}が省略できる
    p {{ myData.lastName }} // p {{ $store.state.myData.firstName }}が省略できる
</template>
<script>
import { mapGetters } from 'vuex' //ここにmapGettersをimport
export default {
  computed: {
    ...mapGetters(['myData']) //ここでmapGettersを使う
  }
}
</script>

image.png
結果$store.state.myData.firstNameを呼び出した時と同じくなります。
gettersで宣言した関数名を別の名前で受け取りする方法も有って配列で受けたのをオブジェクトで受け取ってのkeyとvalueで割当して使うこともできます。:sunny:

computed: {
    ...mapGetters({
       testData:'myData'
    })
  }

image.png

同じ結果です。:sunny:
このように別のvueComponentを作ってもわざわざ処理をしなくてもimportだけでdataを呼び出しして使うことができます。グロバル関数系のmutationsactionsもありますが自分はMixinでグロバル化して使っても十分使えるかと思いました。

終わり

ページを遷移した場合データの渡すしや受けるなどサイトが膨大になると複雑になるので中大サイトをもし、vueで作成する場合vuexのgettersを使って簡単に管理するのがどうですか

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

dataはvuexのgettersでデータ管理しよう

はじめに

vueinstanceでは親子component同士でdataを通信をする場合はProps/$emitcoやcomponent同士でデータ通信をするためにはEvent Busを使うのが一般ですがこのように複雑なdata通信を簡単に通信することができる考え方がVuxです。
image.png

Vuexとは

ざっくりですが下記のイメージ図のように中央管理所からデータを必要な時呼び出しして使えるイメージです。

image.png

自分のイメージだと親と子component同士関係なく中央管理所からdataを管理し必要な時使うのがVuexでつまり中央管理所はVuexとのことです。

公式からの説明は下記です。

Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。 また Vue 公式の開発ツール拡張と連携し、設定なしでタイムトラベルデバッグやステートのスナップショットのエクスポートやインポートのような高度な機能を提供します。
https://vuex.vuejs.org/ja/

vuex使い方

nuxtインストールは公式サイト参考 https://ja.nuxtjs.org/guide/installation
nuxtをインストールしたらvuexをインストールします。

$ npm i vuex

nuxtのプロジェクトを作成すると直下にstoreフォルダが作成されるのでstore直下にindex.jsを新しく作って下記のサンプルコードのようにvuexをimportして必要なdataの登録をします。

store/index.js
import Vuex from 'vuex'

export const appStore = () => {
  return new Vuex.Store({
    state: {//data
      myData:{
        firstName: 'thunder',
        lastName: 'fury',
      }
    },
    mutations: {
    },
    actions: {
    }
  })
};
export default appStore

vuexのstateはvueinstanceで言うと下記のdataの役割です。
storeにあるdataを使うためには$storeで呼び出しができます。

<template lang="pug">
  div
    p {{ $store.state.myData.firstName }}
    p {{ $store.state.myData.lastName }}
</template>

image.png
これでも良いですがdataのオブジェクトの下層が深い場合など繰り返しコードが多くなってしまうのでその時使うのがGettersです。

Getters実行イメージ

image.png

storeのindex.jsにstateのdataをgettersに関数として登録してreturnして使える流れです。

store/index.js.js
getters: {
   myData: state => {
      return state.myData
   },
//上下同じ
getters: {
   myData: function(state) {
      return state.myData
   },

どちらでも書き方は問題ないです。
少しでもにコード量を減らすためにはarrow functionが良いか思います。

登録が終わったら使いたいvueのcomponentmapGettersをimportをしてcomputedstore/index.js宣言した関数を配列の中に入れます。
そうすることに寄って$store.stateから呼び出したのをcomponent内でで宣言したようにで呼び出しすることができます。

<template lang="pug">
  div
    p {{ myData.firstName }} // p {{ $store.state.myData.firstName }}が省略できる
    p {{ myData.lastName }} // p {{ $store.state.myData.firstName }}が省略できる
</template>
<script>
import { mapGetters } from 'vuex' //ここにmapGettersをimport
export default {
  computed: {
    ...mapGetters(['myData']) //ここでmapGettersを使う
  }
}
</script>

image.png
結果$store.state.myData.firstNameを呼び出した時と同じくなります。
gettersで宣言した関数名を別の名前で受け取りする方法も有って配列で受けたのをオブジェクトで受け取ってのkeyとvalueで割当して使うこともできます。:sunny:

computed: {
    ...mapGetters({
       testData:'myData'
    })
  }

image.png

同じ結果です。:sunny:
このように別のvueComponentを作ってもわざわざ処理をしなくてもimportだけでdataを呼び出しして使うことができます。グロバル関数系のmutationsactionsもありますが自分はMixinでグロバル化して使っても十分使えるかと思いました。

終わり

ページを遷移した場合データの渡すしや受けるなどサイトが膨大になると複雑になるので中大サイトをもし、vueで作成する場合vuexのgettersを使って簡単に管理するのがどうですか

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

Nuxr.jsのdataはvuexのstate,gettersでデータ管理しようstate、

はじめに

vueinstanceでは親子component同士でdataを通信をする場合はProps/$emitcoやcomponent同士でデータ通信をするためにはEvent Busを使うのが一般ですがこのように複雑なdata通信を簡単に通信することができる考え方がVuxです。
image.png

Vuexとは

ざっくりですが下記のイメージ図のように中央管理所からデータを必要な時呼び出しして使えるイメージです。

image.png

自分のイメージだと親と子component同士関係なく中央管理所からdataを管理し必要な時使うのがVuexでつまり中央管理所はVuexとのことです。

公式からの説明は下記です。

Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。 また Vue 公式の開発ツール拡張と連携し、設定なしでタイムトラベルデバッグやステートのスナップショットのエクスポートやインポートのような高度な機能を提供します。
https://vuex.vuejs.org/ja/

vuex使い方

nuxtインストールは公式サイト参考 https://ja.nuxtjs.org/guide/installation
nuxtをインストールしたらvuexをインストールします。

$ npm i vuex

nuxtのプロジェクトを作成すると直下にstoreフォルダが作成されるのでstore直下にindex.jsを新しく作って下記のサンプルコードのようにvuexをimportして必要なdataの登録をします。

store/index.js
import Vuex from 'vuex'

export const appStore = () => {
  return new Vuex.Store({
    state: {//data
      myData:{
        firstName: 'thunder',
        lastName: 'fury',
      }
    },
    mutations: {
    },
    actions: {
    }
  })
};
export default appStore

vuexのstateはvueinstanceで言うと下記のdataの役割です。
storeにあるdataを使うためには$storeで呼び出しができます。

<template lang="pug">
  div
    p {{ $store.state.myData.firstName }}
    p {{ $store.state.myData.lastName }}
</template>

image.png
これでも良いですがdataのオブジェクトの下層が深い場合など繰り返しコードが多くなってしまうのでその時使うのがGettersです。

Getters実行イメージ

image.png

storeのindex.jsにstateのdataをgettersに関数として登録してreturnして使える流れです。

store/index.js.js
getters: {
   myData: state => {
      return state.myData
   },
//上下同じ
getters: {
   myData: function(state) {
      return state.myData
   },

どちらでも書き方は問題ないです。
少しでもにコード量を減らすためにはarrow functionが良いか思います。

登録が終わったら使いたいvueのcomponentmapGettersをimportをしてcomputedstore/index.js宣言した関数を配列の中に入れます。
そうすることに寄って$store.stateから呼び出したのをcomponent内でで宣言したようにで呼び出しすることができます。

<template lang="pug">
  div
    p {{ myData.firstName }} // p {{ $store.state.myData.firstName }}が省略できる
    p {{ myData.lastName }} // p {{ $store.state.myData.firstName }}が省略できる
</template>
<script>
import { mapGetters } from 'vuex' //ここにmapGettersをimport
export default {
  computed: {
    ...mapGetters(['myData']) //ここでmapGettersを使う
  }
}
</script>

image.png
結果$store.state.myData.firstNameを呼び出した時と同じくなります。
gettersで宣言した関数名を別の名前で受け取りする方法も有って配列で受けたのをオブジェクトで受け取ってのkeyとvalueで割当して使うこともできます。:sunny:

computed: {
    ...mapGetters({
       testData:'myData'
    })
  }

image.png

同じ結果です。:sunny:
このように別のvueComponentを作ってもわざわざ処理をしなくてもimportだけでdataを呼び出しして使うことができます。グロバル関数系のmutationsactionsもありますが自分はMixinでグロバル化して使っても十分使えるかと思いました。

終わり

ページを遷移した場合データの渡すしや受けるなどサイトが膨大になると複雑になるので中大サイトをもし、vueで作成する場合vuexのgettersを使って簡単に管理するのがどうですか

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

Nuxt.jsのdataはvuexのstate,gettersでデータ管理しよう

はじめに

vueinstanceでは親子component同士でdataを通信をする場合はProps/$emitcoやcomponent同士でデータ通信をするためにはEvent Busを使うのが一般ですがこのように複雑なdata通信を簡単に通信することができる考え方がVuxです。
image.png

Vuexとは

ざっくりですが下記のイメージ図のように中央管理所からデータを必要な時呼び出しして使えるイメージです。

image.png

自分のイメージだと親と子component同士関係なく中央管理所からdataを管理し必要な時使うのがVuexでつまり中央管理所はVuexとのことです。

公式からの説明は下記です。

Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。 また Vue 公式の開発ツール拡張と連携し、設定なしでタイムトラベルデバッグやステートのスナップショットのエクスポートやインポートのような高度な機能を提供します。
https://vuex.vuejs.org/ja/

vuex使い方

nuxtインストールは公式サイト参考 https://ja.nuxtjs.org/guide/installation
nuxtをインストールしたらvuexをインストールします。

$ npm i vuex

nuxtのプロジェクトを作成すると直下にstoreフォルダが作成されるのでstore直下にindex.jsを新しく作って下記のサンプルコードのようにvuexをimportして必要なdataの登録をします。

store/index.js
import Vuex from 'vuex'

export const appStore = () => {
  return new Vuex.Store({
    state: {//data
      myData:{
        firstName: 'thunder',
        lastName: 'fury',
      }
    },
    mutations: {
    },
    actions: {
    }
  })
};
export default appStore

vuexのstateはvueinstanceで言うと下記のdataの役割です。
storeにあるdataを使うためには$storeで呼び出しができます。

<template lang="pug">
  div
    p {{ $store.state.myData.firstName }}
    p {{ $store.state.myData.lastName }}
</template>

image.png
これでも良いですがdataのオブジェクトの下層が深い場合など繰り返しコードが多くなってしまうのでその時使うのがGettersです。

Getters実行イメージ

image.png

storeのindex.jsにstateのdataをgettersに関数として登録してreturnして使える流れです。

store/index.js.js
getters: {
   myData: state => {
      return state.myData
   },
//上下同じ
getters: {
   myData: function(state) {
      return state.myData
   },

どちらでも書き方は問題ないです。
少しでもにコード量を減らすためにはarrow functionが良いか思います。

登録が終わったら使いたいvueのcomponentmapGettersをimportをしてcomputedstore/index.js宣言した関数を配列の中に入れます。
そうすることに寄って$store.stateから呼び出したのをcomponent内でで宣言したようにで呼び出しすることができます。

<template lang="pug">
  div
    p {{ myData.firstName }} // p {{ $store.state.myData.firstName }}が省略できる
    p {{ myData.lastName }} // p {{ $store.state.myData.firstName }}が省略できる
</template>
<script>
import { mapGetters } from 'vuex' //ここにmapGettersをimport
export default {
  computed: {
    ...mapGetters(['myData']) //ここでmapGettersを使う
  }
}
</script>

image.png
結果$store.state.myData.firstNameを呼び出した時と同じくなります。
gettersで宣言した関数名を別の名前で受け取りする方法も有って配列で受けたのをオブジェクトで受け取ってのkeyとvalueで割当して使うこともできます。:sunny:

computed: {
    ...mapGetters({
       testData:'myData'
    })
  }

image.png

同じ結果です。:sunny:
このように別のvueComponentを作ってもわざわざ処理をしなくてもimportだけでdataを呼び出しして使うことができます。グロバル関数系のmutationsactionsもありますが自分はMixinでグロバル化して使っても十分使えるかと思いました。

終わり

ページを遷移した場合データの渡すしや受けるなどサイトが膨大になると複雑になるので中大サイトをもし、vueで作成する場合vuexのgettersを使って簡単に管理するのがどうですか

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

Nuxt.jsのdataはvuexのstate,gettersで管理しよう

はじめに

vueinstanceでは親子component同士でdataを通信をする場合はProps/$emitcoやcomponent同士でデータ通信をするためにはEvent Busを使うのが一般ですがこのように複雑なdata通信を簡単に通信することができる考え方がVuxです。
image.png

Vuexとは

ざっくりですが下記のイメージ図のように中央管理所からデータを必要な時呼び出しして使えるイメージです。

image.png

自分のイメージだと親と子component同士関係なく中央管理所からdataを管理し必要な時使うのがVuexでつまり中央管理所はVuexとのことです。

公式からの説明は下記です。

Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。 また Vue 公式の開発ツール拡張と連携し、設定なしでタイムトラベルデバッグやステートのスナップショットのエクスポートやインポートのような高度な機能を提供します。
https://vuex.vuejs.org/ja/

vuex使い方

nuxtインストールは公式サイト参考 https://ja.nuxtjs.org/guide/installation
nuxtをインストールしたらvuexをインストールします。

$ npm i vuex

nuxtのプロジェクトを作成すると直下にstoreフォルダが作成されるのでstore直下にindex.jsを新しく作って下記のサンプルコードのようにvuexをimportして必要なdataの登録をします。

store/index.js
import Vuex from 'vuex'

export const appStore = () => {
  return new Vuex.Store({
    state: {//data
      myData:{
        firstName: 'thunder',
        lastName: 'fury',
      }
    },
    mutations: {
    },
    actions: {
    }
  })
};
export default appStore

vuexのstateはvueinstanceで言うと下記のdataの役割です。
storeにあるdataを使うためには$storeで呼び出しができます。

<template lang="pug">
  div
    p {{ $store.state.myData.firstName }}
    p {{ $store.state.myData.lastName }}
</template>

image.png
これでも良いですがdataのオブジェクトの下層が深い場合など繰り返しコードが多くなってしまうのでその時使うのがGettersです。

Getters実行イメージ

image.png

storeのindex.jsにstateのdataをgettersに関数として登録してreturnして使える流れです。

store/index.js.js
getters: {
   myData: state => {
      return state.myData
   },
//上下同じ
getters: {
   myData: function(state) {
      return state.myData
   },

どちらでも書き方は問題ないです。
少しでもにコード量を減らすためにはarrow functionが良いか思います。

登録が終わったら使いたいvueのcomponentmapGettersをimportをしてcomputedstore/index.js宣言した関数を配列の中に入れます。
そうすることに寄って$store.stateから呼び出したのをcomponent内でで宣言したようにで呼び出しすることができます。

<template lang="pug">
  div
    p {{ myData.firstName }} // p {{ $store.state.myData.firstName }}が省略できる
    p {{ myData.lastName }} // p {{ $store.state.myData.firstName }}が省略できる
</template>
<script>
import { mapGetters } from 'vuex' //ここにmapGettersをimport
export default {
  computed: {
    ...mapGetters(['myData']) //ここでmapGettersを使う
  }
}
</script>

image.png
結果$store.state.myData.firstNameを呼び出した時と同じくなります。
gettersで宣言した関数名を別の名前で受け取りする方法も有って配列で受けたのをオブジェクトで受け取ってのkeyとvalueで割当して使うこともできます。:sunny:

computed: {
    ...mapGetters({
       testData:'myData'
    })
  }

image.png

同じ結果です。:sunny:
このように別のvueComponentを作ってもわざわざ処理をしなくてもimportだけでdataを呼び出しして使うことができます。グロバル関数系のmutationsactionsもありますが自分はMixinでグロバル化して使っても十分使えるかと思いました。

終わり

ページを遷移した場合データの渡すしや受けるなどサイトが膨大になると複雑になるので中大サイトをもし、vueで作成する場合vuexのgettersを使って簡単に管理するのがどうですか

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

vue.js if null チェック

nullチェックしたい。

<div v-if="res.social.amazon !== null">
    あるでー
</div>
<div v-else>
    ないでー
</div>

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

Vue.jsでビジュアルコンテンツエディタ作成

概要

記事コンテンツの管理ツールの需要があって、Vue.jsを用いて作成てみました。
諸事情によってサーバーサイドを利用できず、すべてフロントで完結する必要があります。
イメージとしては簡易版のワードプレス管理画面のようなものを想定して設計しました。

フロントエンド(エンドユーザー向け)
ReactやVueなどで記事コンテンツのjsonを用いて描画する
バックエンド
エディタを使って、このような記事コンテンツのjsonファイルを作成、管理、更新

第一弾としてはバックエンドのパートをご紹介します。
デモはこちら

ページ構成

トップ
既存記事コンテンツのjsonをアップロードして、リスト形式で表示、完成したデータをjson形式でダウンロード
記事作成画面
Vue-Quill-Editorを使ってビジュアルエディタを導入、各項目の入力
記事編集画面
基本的には作成と同じく、各項目の入力、既存記事コンテンツの更新、削除

必要なライブラリー

vue:ページ遷移時に、propsの受け渡しが必要ですので、CLIの導入が必要です。
vue-router:ページ遷移のために、必要となります。
axios:画像ファイルのアップロード時に必要です。
vue-quill-editor:ビジュアルエディタのプラグインです。

ファイルの構成

corefiles.png
メインのファイルを紹介します。

main.js

router.jsの導入

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')

router/index.js

各ページへのURLルールとテンプレートを設定
データの受け渡しが必要ですので、propsはtrueにします。

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,
    props: true
  },
  {
    path: '/new',
    name: 'New',
    component: () => import('../views/New.vue'),
    props: true
  },
  {
    path: '/detail/:aid',
    name: 'Detail',
    component: () => import('../views/Detail.vue'),
    props: true
  }
]
const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})
export default router

App.vue

特筆すべき点もなく、基本的に各Viewsに振り分けます。

App.vue
<template>
  <div id="app">
    <router-view/>
  </div>
</template>

Home.vue

既存記事コンテンツのjsonをアップロードして、リスト形式で表示、完成したデータをjson形式でダウンロード、ほかのページへ遷移するときに、記事コンテンツの最新情報を渡す

views/Home.vue
<template>
  <div>
    <header>
      <p><img src="logo.png" alt="" width="48"></p>
      <h1>Quill Editor</h1>
    </header>
    <main>
      <div class="homeArea">
        <div class="homeDisplay"> 
          <p>Please upload a json file.</p>
          <input class="jsonUL" type="file" @change="upload" />
          <h2>Article List</h2>
          <ul class="articleList">
            <li v-for="item in items" :key="item.aid">
              <router-link v-bind:to="{ name: 'Detail', params: { aid:item.aid, getItems: items }}">
                <div>
                  <h3>{{ item.title }}</h3>
                  <p>{{ item.description }}</p>
                </div>
                <div>
                  <img :src="item.thumbnail">
                </div>
              </router-link>
            </li>
          </ul>
        </div>
        <div class="homeDescription">
          <h3>How to use</h3>
          <ul>
            <li>Upload a json format data file (<span class="sample"><a v-on:click="sample()">Sample</a></span>) to start.</li>
            <li>Click "Create a new article" to create a new one, input the necessary information and click "Add" to finish.</li>
            <li>Click each article to edit contents, and click "save" when finished.</li>
            <li>Click "Download" to export the newest article data.</li>
            <li>Please do not use browser "back" or "refresh", this may remove all the data.</li>
          </ul>
          <h3>Element in article</h3>
          <dl>
            <dt>aid (Require/Unique)</dt>
            <dd>Article id, can not change after created, please do not use same aid for different article, we recommond "yyyymmddID" (eg. "2020101401")</dd>
            <dt>Title (Require)</dt>
            <dd>Article title, can be seen in list</dd>
            <dt>Description (Require)</dt>
            <dd>Article description, can be seen in list</dd>
            <dt>Thumbnail (Require)</dt>
            <dd>Article thumbnail image, can be seen in list</dd>
            <dt>Content (Require)</dt>
            <dd>Article contents</dd>
            <dt>Category</dt>
            <dd>Article category, planning to be used for filter</dd>
            <dt>Tag</dt>
            <dd>Article category, planning to be used for filter</dd>
          </dl>
        </div> 
      </div>
    </main>
    <footer>
      <router-link class="greenBtn button" v-bind:to="{ name: 'New', params: { getItems: items }}">Create a new article</router-link>
      <a class="blackBtn button" v-on:click="download()">Download</a>
    </footer>
  </div>
</template>
<script>
  import Vue from 'vue'
  import VueQuillEditor from 'vue-quill-editor'
  import 'quill/dist/quill.core.css'
  import 'quill/dist/quill.snow.css'
  import 'quill/dist/quill.bubble.css'
  Vue.use(VueQuillEditor)

  const today = new Date();
  const update = today.getFullYear() + "/" + (today.getMonth() + 1) + "/" + today.getDate() + " " + today.getHours() + ":" + today.getMinutes()

  export default {
    props: {
      getItems: Array,
    },
    data(){
      return {
        results: [],
        items: this.getItems,
        dlData: {
          update: update,
          data: this.getItems
        },
        sampleData: {
          update: update,
          data: [
            {
              aid: '2020010101',
              title: 'Sample Title1',
              description: 'Sample Description1',
              thumbnail: '',
              category: 'Sample Category1',
              tag: 'Sample Tag1',
              content: 'Sample Contents1'
            },
            {
              aid: '2020010203',
              title: 'Sample Title2',
              description: 'Sample Description2',
              thumbnail: '',
              category: 'Sample Category2',
              tag: 'Sample Tag2',
              content: 'Sample Contents2'
            },
          ]
        }
      }
    },
    methods: {
      upload: function(e) {
        const file = e.target.files[0]
        const reader = new FileReader()
        reader.onload = (e) => {
          this.results = JSON.parse(e.target.result)
          this.items = this.results.data
        }
        reader.readAsText(file);
      },
      download: function() {
        const output = this.dlData
        const data = JSON.stringify(output)
        const blob = new Blob([data], {
          type: 'text/plain'
        })
        const e = document.createEvent('MouseEvents'),
          a = document.createElement('a');
        a.download = "data.json";
        a.href = window.URL.createObjectURL(blob);
        a.dataset.downloadurl = ['text/json', a.download, a.href].join(':');
        e.initEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
        a.dispatchEvent(e);
      },
      sample: function() {
        const output = this.sampleData
        const data = JSON.stringify(output)
        const blob = new Blob([data], {
          type: 'text/plain'
        })
        const e = document.createEvent('MouseEvents'),
          a = document.createElement('a');
        a.download = "data.json";
        a.href = window.URL.createObjectURL(blob);
        a.dataset.downloadurl = ['text/json', a.download, a.href].join(':');
        e.initEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
        a.dispatchEvent(e);
      }
    }
  }
</script>

Detail.vue

Vue-Quill-Editorを使ってビジュアルエディタを導入、各項目の入力、保存、トップへ戻すときにデータを渡す

views/Detail.vue
<template>
  <div>
    <header>
      <p><img src="logo.png" alt="" width="48"></p>
      <h1>Article Detail</h1>
    </header>
    <main> 
      <div class="detail">
        <div id="editorArea">
          <h2>Editor</h2>
          <div class="inner">
            <h3 class="required">Contents</h3>
            <quill-editor v-model="content" ref="quillEditor" :options="editorOption"></quill-editor>
          </div>
          <h3>Information</h3>
          <dl class="detailList">
            <dt>aid</dt>
            <dd>{{aid}}</dd>
            <dt class="required">Title</dt>
            <dd><input v-model="title" size="40"></dd>
            <dt class="required description">Description</dt>
            <dd class="description"><textarea v-model="description" rows="3" cols="40"></textarea></dd>
            <dt>Category</dt>
            <dd><input v-model="category" size="15"></dd>
            <dt>Tag</dt>
            <dd><input v-model="tag" size="15"></dd>
            <dt class="required">Thumbnail</dt>
            <dd><input type="file" @change="upload" accept="image/*" /></dd>
          </dl>
          <p><img class="thumbnail" :src="thumbnail"></p>
        </div>
        <div id="previewArea">
          <h2>Preview</h2>
          <div class="inner">
            <div id="preview" class="content ql-editor" v-html="content"></div>
          </div>
        </div>
      </div>
    </main>
    <footer>
      <a class="greenBtn button" v-on:click="save()">Save</a>
      <router-link class="redBtn button" @click.native="remove()" v-bind:to="{ name: 'Home', params: { getItems: postItems }}">Remove</router-link>
      <router-link class="blackBtn button" v-bind:to="{ name: 'Home', params: { getItems: postItems }}">Back to home</router-link>
    </footer>
  </div>
</template>
<script>
  import Vue from 'vue'
  import axios from 'axios';
  import VueQuillEditor from 'vue-quill-editor'
  import 'quill/dist/quill.core.css'
  import 'quill/dist/quill.snow.css'
  import 'quill/dist/quill.bubble.css'

  Vue.use(VueQuillEditor)

  export default {
    props: {
      aid: String,
      getItems: Array,
    },
    data() {
      return {
        item : this.getItems.filter(item => item.aid === this.aid)[0],
        index: this.getItems.findIndex(item => item.aid === this.aid),
        content: this.getItems.filter(item => item.aid === this.aid)[0].content,
        title: this.getItems.filter(item => item.aid === this.aid)[0].title,
        description: this.getItems.filter(item => item.aid === this.aid)[0].description,
        thumbnail: this.getItems.filter(item => item.aid === this.aid)[0].thumbnail,
        category: this.getItems.filter(item => item.aid === this.aid)[0].category,
        tag: this.getItems.filter(item => item.aid === this.aid)[0].tag,
        postItems: this.getItems,
        editorOption: {
          theme: 'snow'
        }
      }
    },
    methods: {
      upload(e) {
        const file = e.target.files[0]
        const reader = new FileReader()
        reader.onload = (e) => {
          this.thumbnail = e.target.result
          this.postData()
        }
        reader.readAsDataURL(file)
      },
      postData() {
        const params = new FormData()
        params.append('image', this.image)
        axios.post('http://0.0.0.0:9999/', params).then(res => {
          this.thumbnail = res.data.url
        })
      },
      save: function() {
        this.postItems[this.index].content = this.content
        this.postItems[this.index].title = this.title
        this.postItems[this.index].description = this.description
        this.postItems[this.index].thumbnail = this.thumbnail
        this.postItems[this.index].category = this.category
        this.postItems[this.index].tag = this.tag
      },
      remove: function() {
        this.postItems.splice(this.index, 1);
      }
    }
  }
</script>

New.vue

Vue-Quill-Editorを使ってビジュアルエディタを導入、各項目の入力、保存、トップへ戻すときにデータを渡す

views/New.vue
<template>
  <div>
    <header>
      <p><img src="logo.png" alt="" width="48"></p>
      <h1>New Article</h1>
    </header>
    <main> 
      <div class="detail">
        <div id="editorArea">
          <h2>Editor</h2>
          <div class="inner">
            <h3 class="required">Contents</h3>
            <quill-editor v-model="content" ref="quillEditor" :options="editorOption"></quill-editor>
          </div>
          <h3>Information</h3>
          <dl class="detailList">
            <dt class="required">aid</dt>
            <dd><input v-model="aid" size="15"></dd>
            <dt class="required">Title</dt>
            <dd><input v-model="title" size="40"></dd>
            <dt class="required description">Description</dt>
            <dd class="description"><textarea v-model="description" rows="3" cols="40"></textarea></dd>
            <dt>Category</dt>
            <dd><input v-model="category" size="15"></dd>
            <dt>Tag</dt>
            <dd><input v-model="tag" size="15"></dd>
            <dt class="required">Thumbnail</dt>
            <dd><input type="file" @change="upload" accept="image/*" /></dd>
          </dl>
          <p><img class="thumbnail" :src="thumbnail"></p>
        </div>
        <div id="previewArea">
          <h2>Preview</h2>
          <div class="inner">
            <div id="preview" class="content ql-editor" v-html="content"></div>
          </div>
        </div>
      </div>
    </main>
    <footer>
      <router-link class="greenBtn button" @click.native="add()" v-bind:to="{ name: 'Home', params: { getItems: postItems }}">Add</router-link>
      <router-link class="blackBtn button" v-bind:to="{ name: 'Home', params: { getItems: postItems }}">Back to home</router-link>
    </footer>
  </div>
</template>
<script>
  import Vue from 'vue'
  import axios from 'axios';
  import VueQuillEditor from 'vue-quill-editor'
  import 'quill/dist/quill.core.css'
  import 'quill/dist/quill.snow.css'
  import 'quill/dist/quill.bubble.css'

  Vue.use(VueQuillEditor)

  export default {
    props: {
      getItems: Array,
    },
    data() {
      return {
        aid: '',
        content: '',
        title: '',
        description: '',
        thumbnail: '',
        category: '',
        tag: '',
        postItems: this.getItems,
        editorOption: {
          theme: 'snow'
        },
      }
    },
    methods: {
      upload(e) {
        const file = e.target.files[0]
        const reader = new FileReader()
        reader.onload = (e) => {
          this.thumbnail = e.target.result
          this.postData()
        }
        reader.readAsDataURL(file)
      },
      postData() {
        const params = new FormData()
        params.append('image', this.image)
        axios.post('http://0.0.0.0:9999/', params).then(res => {
          this.thumbnail = res.data.url
        })
      },
      add: function() {
        const newItem = {
          "aid": this.aid,
          "title": this.title,
          "description": this.description,
          "thumbnail": this.thumbnail,
          "category": this.category,
          "tag": this.tag,
          "content": this.content
        }
        this.postItems.push(newItem)
      }
    }
  }
</script>

完成版はこちらです。

引き続き、第二弾ではフロント側の描画をご紹介致します。
完成次第、またこちらにて投稿します。

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

Vue ✖️ Firebase Storageで画像保存

はじめに

Vue.jsとFirebaseの導入については、省いております.
ご了承ください.

画像投稿機能 Vue.jsのコード

プレビュー付き画像フィールド

VImgField.vue
<template>
  <div>
    <v-row v-if="uploadedImage" justify="center" align="center">
      <v-sheet width="300">
        <v-img :src="uploadedImage" />
      </v-sheet>
    </v-row>
    <div v-cloak @drop.prevent="addDropFile" @dragover.prevent>
      <v-row justify="center" align="center">
        <v-col cols="11" sm="8" md="6">
          <v-file-input
            v-model="file"
            accept="image/png, image/jpeg, image/jpg, image/bmp"
            prepend-icon="mdi-camera"
            placeholder="画像ファイル(png, jpeg, jpg, bmp)を選択"
          />
        </v-col>
      </v-row>
    </div>
  </div>
</template>

<script lang="ts">
import {
  defineComponent,
  SetupContext,
  ref,
  watch,
} from '@vue/composition-api';

// アップロードを許可する拡張子
const allowExts: string[] = ['jpg', 'jpeg', 'png', 'bmp'];

// ファイル名から拡張子を取得する関数
const getExt = (filename: string): string => {
  const pos = filename.lastIndexOf('.');
  if (pos === -1) {
    return '';
  }
  return filename.slice(pos + 1);
};

// ファイルが許可されている拡張子か確認する関数
const checkExt = (file: File | undefined): boolean => {
  if (file) {
    const ext = getExt(file.name).toLowerCase();
    if (allowExts.indexOf(ext) === -1) {
      return false;
    }
  }
  return true;
};

// 画像ファイルをBase64に変換
const getBase64 = (file: File): Promise<string | ArrayBuffer | null> => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => resolve(reader.result);
    reader.onerror = (error) => reject(error);
  });
};

// Composition API
export default defineComponent({
  setup(props, context: SetupContext) {
    const uploadedImage = ref<string | ArrayBuffer | null>(null);
    const file = ref<File | undefined>(undefined);

    // ファイルがドラッグで追加されたときのメソッド
    const addDropFile = (e: DragEvent) => {
      const dt: DataTransfer | null = e.dataTransfer;
      if (dt) {
        file.value = dt.files[0];
      }
    };

    // ファイルの変更を監視し
    // 変更があれば、Base64に変換しviewを表示
    watch(file, (newFile, oldFile) => {
      if (checkExt(newFile)) {
        if (newFile) {
          getBase64(newFile).then((image: string | ArrayBuffer | null) => {
            uploadedImage.value = image;
            context.emit('change-file', file.value);
          });
        } else {
          uploadedImage.value = null;
        }
      } else {
        file.value = oldFile;
        context.emit('error-occurred', 'ファイル形式が正しくありません');
      }
    });

    return {
      uploadedImage,
      file,
      addDropFile,
    };
  },
});
</script>

<style scoped>
label > input {
  display: none;
}

label {
  padding: 0 1rem;
  border: solid 1px #888;
}

label::after {
  content: '+';
  font-size: 1rem;
  color: #888;
  padding-left: 1rem;
}
</style>

画像送信フォーム

VPostForm.vue
<template>
  <v-row>
    <v-col cols="11" sm="8">
      <v-alert v-show="error" type="error">{{ error }}</v-alert>
    </v-col>
    <v-col cols="11">
      <a :href="url" target="_blank">{{ url }}</a>
    </v-col>
    <v-col cols="11">
      <v-img-field
        @change-file="changeFile"
        @error-occurred="catchError"
      />
    </v-col>
    <v-col cols="10">
      <v-btn @click="putImage">
        Post
        <v-icon>
          mdi-telegram
        </v-icon>
      </v-btn>
    </v-col>
  </v-row>
</template>

<script lang="ts">
import Vue from 'vue';
import VImgField from '@/components/VImgField.vue';
// Firebaseにアップロードするモジュール
import Uploader from '@/utils/uploader';

export default Vue.extend({
  name: 'VPostForm',
  components: {
    VImgField
  },
  data: () => ({
    file: {} as File,
    error: '',
    url: ''
  }),
  methods: {
    changeFile(uploadedFile: File) {
      this.file = uploadedFile
    },
    catchError(msg: string) {
      this.error = msg
    },
    // アップロード
    async putImage() {
      this.isLoading = true;
      try {
        this.url = await Uploader.put(this.file);
      } catch (err) {
        this.error = err;
      }
      this.isLoading = false;
    },
  }
})
</script>

これでVueでView機能付きのフォームを作ることができました.

少し脱線

今回のフォームは、Composition APIとOptions APIの両方を使用して、作っています.
理由として、私は、メソッドが多く複雑なコンポーネントは、Composition APIを使用して、単純なコンポーネントはOptions APIを使用するみたいな使い分けをしています.

Firebase側の実装

Firebaseのsetting

@/plugins/firebase.ts
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/storage';

const config = {
  apiKey: process.env.VUE_APP_FIREBASE_API_KEY,
  authDomain: process.env.VUE_APP_FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.VUE_APP_FIREBASE_DATABASE_URL,
  projectId: process.env.VUE_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.VUE_APP_FIREBASE_STORAGE_PROJECT,
  messagingSenderId: process.env.VUE_APP_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.VUE_APP_FIREBASE_APP_ID,
  measurementId: process.env.VUE_APP_FIREBASE_MEASUREMENT_ID,
};

firebase.initializeApp(config);

export default firebase;

アップロードをする関数

@/utils/uploader.ts
import firebase from '@/plugins/firebase'
const storageRef = firebase.storage().ref();

export default {
  put(file: File): Promise<string> {
    return new Promise((resolve, reject) => {
      // ファイルのパスを生成
      const mountainsRef = storageRef.child(`insects/${file.name}`);

      // ファイルをアップロード
      mountainsRef
        .put(file)
        .then((snapshot) => {
          // アップロードしたファイルのURLを取得
          snapshot.ref
            .getDownloadURL()
            .then((downloadURL: string) => {
              resolve(downloadURL);
            })
            .catch((error) => {
              reject(error.message);
            });
        })
        .catch((error) => {
          reject(error.message);
        });
    });
  },
}

これで実装できます

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

未経験からフロントエンドエンジニアとして自社開発企業へ転職するまでにやったこと

はじめに

こんにちは☺
このたび、働きながら独学5ヶ月でフロントエンドエンジニアとして自社開発企業へ転職することができました。
今回は、学習開始から転職活動〜内定までを振り返ってみたいと思います。
フロントエンドエンジニアの転職活動についてまとめたものをあまり見なかったので、誰かの参考になれば嬉しいです。

わたしの経歴について

地方国立大学文系学部卒業後、法律事務所勤務を経て地方公務員として働いています。
学生の頃、趣味でホームページを作成した経験はあるものの、HTML/CSS以外のプログラミング言語についてはまったく知らない状態でした。

準備(2020年4月頃)

まずはじめに以下の環境を整えました。

・PCを購入(MacBook Air)
・VScodeインストール
・iTerm2インストール
・学習用Twitterアカウント開設

また、短期目標として下記を設定しました。
・5月でインプット
・6〜7月でポートフォリオ作成
・8月〜転職活動開始、年内に転職先決定

1ヶ月目(2020年5月):Webデザイン&コーディング

前述のとおりホームページ作成した経験もあって最初はWebデザイナーに興味があり、Webデザインとコーディングの学習から始めました。
書籍やUdemyでインプットし、まずはサイト模写やバナートレースなどのアウトプットを行いました。

また、学習用に開設したTwitterアカウントで日々の積み上げやトレースしたバナーをつぶやいたり、自分と同じように勉強している方を見つけたりしてモチベーションを上げていました。

2ヶ月目(2020年6月):アプリ開発、ポートフォリオ検討

ネット上のチュートリアルを参考に簡単なアプリ開発を始めました。
この頃から、Webデザインよりアプリ開発のほうが楽しいと感じ、目標をWebデザイナーからフロントエンドエンジニアに変更し、学習内容のプランとポートフォリオの内容についてプランを練り直しました。

3ヶ月目(2020年7月):ポートフォリオ作成

未経験から転職活動を行うにあたり、学習の成果や技術レベルを客観的に証明できるポートフォリオは必須です。
(実際に選考でもポートフォリオ必須の企業はいくつかありましたし、しっかりポートフォリオを作成したことで高評価をいただくこともありました。)

フロントエンドエンジニアのポートフォリオについてはあまり情報がありませんでしたが、
①ログイン認証のない、誰でも気軽に利用できるアプリ
②ログイン認証があり、データベースへのCRUD処理ができるアプリ
③ポートフォリオサイト
の3つを作成することに決め、アプリのアイデアから考えました。

アプリのアイデアについて

「なにか困っていることを解決したい」というような自分の”想い”が大切だと思います。
わたしは、”誰もが簡単に発言できる時代だからこそ、ひと手間かけて相手に気持ちを伝えることの大切さ”や”嬉しいできごとを共有して皆がHappyになってほしい”という想いから
①のアプリについては嬉しい出来事をカードにしてTwitterでシェアできるアプリ
②のアプリは嬉しい出来事をユーザーで投稿して共有できるアプリ
を開発しました。
(①については嬉しい出来事だけでなく誕生日などのお祝いや感謝の気持ちも伝えられるカードアプリを目標としていました。現在、追加実装中です・・)

アプリの使用言語について

JavaScriptのフレームワークやライブラリであるVue.jsReactなどを使用するのがよいと思います。
わたしは
・日本語の公式ドキュメントが充実している
・学習コストが低い
などの理由からVue.jsを選びました。
バックエンドについてはBaaS(Backend as a Service)であるFirebaseを利用しました。

コードはすべてGitHubへ

ポートフォリオで作成したアプリのコードはすべてGitHubにpushしました。
選考の段階でGitHubのURLを送ってほしいと依頼されることもあったので、すべてpushしておいたほうがよいと思います。

4ヶ月目(2020年8月)

ポートフォリオが完成したため、さっそく転職活動開始。
わたしの場合は地域を限定していたので、勤務希望地とフロントエンドエンジニアで検索して、実務経験必須の求人にも応募しました。

転職活動中の学習について

転職活動中も、アプリの機能修正や書籍を読んだりしました。
また、面接で聞かれた技術的な質問でわからなかったことは必ずすぐ調べ、まとめるようにしました。

5ヶ月目(2020年9月)

転職活動開始から1ヶ月、面談や面接は5社ほど受け、第一希望の企業より内定をいただきました。

学習に使用した教材やサービスについて

基本的に書籍とUdemyを利用しインプットして、チュートリアル等でコードを書いていくようにしました。
仕事の休み時間などのスキマ時間はQiitaの記事を読んだり、そのときわからないことをググってまとめるようにしています。

Udemy

幅広い講座の中から、目的や好みに合った講座を購入できます。
セール時の購入がオススメです。

書籍

◯1冊ですべて身につくHTML&CSSとWebデザイン入門講座(Mana著)

第一歩はここから。

◯JavaScript本格入門

THE入門書。基礎が大事なので、理解できないところは時間をかけてでも。
ポートフォリオ作成と並行してもOKだと思います。

◯開眼!JavaScript

こちらも上記書籍と同様に。

◯Vue.js入門

Udemy講座終了後、ポートフォリオアプリ開発中こちらで復習しました。

◯Vue.jsのコツとツボがゼッタイにわかる本

こちらも復習用に読みました。

◯Webを支える技術

Webサービス設計の基礎について学ぶことができます。

◯リーダブルコード

独学でコードを書くことに不安があったため読みました。
読みやすいコードはどうあるべきか?さまざまなパターンが紹介されています。

◯安全なWebアプリケーションの作り方

選考中の企業のエンジニアの方からお勧めされた本。
転職活動中に読みました。

公式ドキュメント

コードを書いていてわからなければまず公式ドキュメントを確認していました。

ドットインストール

JavaScript〜ざっとスキマ時間を利用して復習で使用しました。
2分程度の動画の講座なので、ここから入るのも理解しやすいと思います。

MENTA!

自分に合ったメンターさんと契約できるサービスです。
コードレビューや転職サポート(職務経歴書の添削など)をしてもらったり、メンターさんによってはチーム開発にも参加できるので、独学の方は利用をオススメします!

Youtube

しまぶーのIT大学さんのYouTube講座は全体像がわかりやすく、オススメです!

転職活動について

利用した転職エージェント

Wantedly

「まずは話を聞きたい」から企業に連絡をとることができるので、カジュアル面談から入るところが多く気軽に話を聞きやすい。

Green

初回応募時に選考理由等フォーム入力するため敷居が高い印象だが、そのぶん初回応募で通ると面接に進みやすい。

履歴書、職務経歴書について

まず面接の前に提出を求められることが多いため、履歴書と職務経歴書も用意しておきます。
面接に向け、志望動機もより掘り下げて考えるようにしました。
できれば第三者に見てもらって意見をもらい、校正したほうがよいと思います。

面接事前準備について

企業理念や社風に共感できるか、魅力を感じるかを重要視していたため、その点を重点的に調べました(ここは人によると思います)。
企業や業界研究をする中で、自己分析が進み、志望動機がより明確になりました。

まとめ

未経験からエンジニア転職は難しいと言われていますが、わたしのような文系未経験30代でも自社開発企業に転職することができました!

転職活動にあたって、
・フロントエンドエンジニア志望でもしっかりポートフォリオを作成すること
・自己分析を行い、過去の職歴に基づく強みをアピールすること
が重要だと思いました。 
また、完全独学だったので、早めにメンターサービスを利用してもよかったなと思います。

いつ転職を目指すか、どのような学習方法が合っているかは個人によって異なりますので、自分に合ったやり方が一番です。
わたしの場合は転職先決定までの期間を短く設定していますが、長期目標として基礎をしっかり身につけた上でポートフォリオ作成してもよいと思いますし、いきなり正社員でなくともインターンをしながら学習するのもよいと思います。
ただ、やると決めたら徹底的にやること、それだけです!

少しでも参考になれば嬉しいです☺

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

DockerでRuby on Rails + Vue + MySQLの環境構築をする方法【2020/09最新版】

はじめに

フロントにVue、APIサーバにRailsという組み合わせは日本ではメジャーな構成の一つです。今回はこの構成をDockerを使って構築していきます。

この記事を読むことで、以下の構成をDocker作成出来るようになります!
API: Rails 6系
フロント:Vue(TypeScript) 2系
DB: MySQL 5.7

環境構築の流れ

  1. Rails、VueのDockerfile作成
  2. docker-compose.yaml作成
  3. Rails、Vueのプロジェクト作成

最終的なディレクトリ構成は以下のようになります。

[project_name]
├── api // Rails
│   ├── Dockerfile
│   ├── ...
├── docker-compose.yaml
└── front // Vue
    ├── Dockerfile
    ├── ...

では環境構築に入っていきましょう!

1. ディレクトリ作成

プロジェクトディレクトリとその配下にAPI、フロントのディレクトリを作成します

$ mkdir project_name
$ cd project_name
$ mkdir api front

以降のコマンドはカレントディレクトリがプロジェクトディレクトリ想定で記載しています。

2. Dockerfile作成

RailsとVueそれぞれのDockerfileを作成します。

2-1. Rails

指定しているバージョン
- Ruby 2.7.1
- Rails 6.0.x

api/Dockerfile
FROM ruby:2.7.1

RUN apt-get update -qq && \
    apt-get install -y build-essential \ 
                       libpq-dev \        
                       nodejs \
   && rm -rf /var/lib/apt/lists/* 

RUN mkdir /app
ENV APP_ROOT /app
WORKDIR $APP_ROOT

ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock

RUN bundle install

rm -rf /var/lib/apt/lists/*はaptのキャッシュを削除しています。これはDockerのイメージファイルサイズを軽量化するためです。

このDockerfileではRailsを含むGemfileを必要とするので、以下のファイルをapi/に作成します。

api/Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 6.0.3'

空のGemfile.lockも作成します。

$ touch api/Gemfile.lock

この時点でのディレクトリ構成

[project_name]
├── api 
│   ├── Dockerfile   <- New!
│   ├── Gemfile      <- New!
│   └── Gemfile.lock <- New!
└── front

2-2. Vue

指定しているバージョン
- node 12.18.3
- Vue 2系 <- これは後述のコンテナ内でVueプロジェクトを作成する際に指定します

front/Dockerfile
FROM node:12.18.3-alpine

ENV APP_HOME /app
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME

RUN apk update && npm install -g @vue/cli

この時点でのディレクトリ構成

[project_name]
├── api 
│   ├── Dockerfile
│   ├── Gemfile
│   └── Gemfile.lock
└── front 
    └── Dockerfile <- New!

3. docker-compose.yaml作成

以下のdocker-compose.yamlをプロジェクトディレクトリに作成します

docker-compose.yaml
version: '3'

services:
  web:
    build: ./api
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    ports:
      - '3000:3000'
    depends_on:
      - db
    volumes:
      - ./api:/app
      - bundle:/usr/local/bundle
    tty: true
    stdin_open: true
  db:
    image: mysql:5.7
    volumes:
      - mysql_data:/var/lib/mysql/
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3306:3306'
  front:
    build: ./front
    volumes:
      - ./front:/app
    ports:
      - '8080:8080'
    tty: true
    stdin_open: true
    command: npm run serve

volumes:
  mysql_data:
  bundle:

MySQLとbundleのデータはボリュームにマウントし永続化しています。これによってコンテナを削除しても、データは消えません。
bundleはマウントしなくても、Gemを追加するたびにイメージのビルドすれば良いのですが時間がかかります。bundleをマウントすることでGemの追加がdocker-compose run api bundle installで済むようにです。

この時点でのディレクトリ構成

[project_name]
├── api 
│   ├── Dockerfile
│   ├── Gemfile
│   └── Gemfile.lock
├── docker-compose.yaml <- New!
└── front 
    └── Dockerfile

4. プロジェクトの作成

4-1. Rails

Railsプロジェクトの作成
rails newでRailsプロジェクトを作成します

$ docker-compose run web rails new . --force --database=mysql --api --skip-bundle

rails newの引数について
--force:Gemfileを強制的に上書き更新する
--database:使用するデータベースをMySQLにする
--api:APIモードでプロジェクトを作成。APIモードではUIに関係するファイルが省略されます。
--skip-bundlebundle installを省略します。次のdockerイメージのビルドでbundle installをするためです。

dockerイメージ更新
Gemfileが更新されたので、buildしてdocker imageを更新します。

$ docker-compose build

DBの設定ファイルを修正

RailsのDB設定ファイルapi/config/database.ymlを修正します。

api/config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
- password:
+ password: password
- host: localhost
+ host: db
  • passwordはdocker-compose.yamlの環境変数MYSQL_ROOT_PASSWORDで指定したもの
  • hostはDBのサービス名 に対応しています。

DBの作成

$ docker-compose run web rails db:create 

これでRailsの環境構築は完了です!

4-2. Vue

vue-cliでVueプロジェクトを作成
コンテナ内に入り、vue-cliを使ってVueプロジェクトの作成を対話的にします。

以下の設定項目はvue-cli v4.5.6のものです。設定内容は一例なのでお好みでどうぞ。

Vueコンテナでシェルを実行

$ docker-compose run front sh

以下フロントコンテナ内で対話的に設定していきます。

$ vue create .
# 現在のディレクトリ(/app)に作成するかの確認
? Generate project in current directory? (Y/n) Yes

# プリセットを使用するかどうか
? Please pick a preset: (Use arrow keys)
  Default ([Vue 2] babel, eslint)
  Default (Vue 3 Preview) ([Vue 3] babel, eslint)
❯ Manually select features # TypeScriptをインストールするためこちらを選択

# プロジェクトにインストールするライブラリの選択
? Check the features needed for your project:
 ◉ Choose Vue version #
 ◉ Babel
❯◉ TypeScript # TypeScriptをインストールする場合はこれを選択
 ◯ Progressive Web App (PWA) Support
 ◯ Router
 ◯ Vuex
 ◯ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

# Vueバージョンの選択
? Choose a version of Vue.js that you want to start the project with (Use arrow keys)
❯ 2.x
  3.x (Preview)

# Class styleを使用するかどうか。私はObject styleを使うため No
? Use class-style component syntax? (Y/n) No

# TypeScriptと一緒にbabelを使うか
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n) Yes

# LintとFormatterの設定に何を使うか
? Pick a linter / formatter config:
  ESLint with error prevention only
  ESLint + Airbnb config
  ESLint + Standard config
❯ ESLint + Prettier
  TSLint (deprecated)

# Lintの実行タイミング
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◉ Lint on save # 保存時にLintを実行
 ◯ Lint and fix on commit (requires Git)

# Babel, ESLintなどの設定をどこに記述するか
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
❯ In dedicated config files # 各設定ファイルにする
  In package.json

# 今回設定した内容をプリセットで保存するか。基本的にはプロジェクトを以降作成することはないため No
? Save this as a preset for future projects? No

# パッケージマネージャーに何を使うか
? Pick the package manager to use when installing dependencies: (Use arrow keys)
❯ Use Yarn
  Use NPM

インストール完了後、Ctrl+Dでコンテナを停止します。

5. 動作確認

コンテナを立ち上げる

$ docker-compose up -d

-d: バックグラウンドでプロセスを実行する

5-1. Rails

localhost:3000にアクセスし以下のページが表示されることを確認
スクリーンショット 2020-09-24 22.37.32.png

5-2. Vue

localhost:8080にアクセスし以下のページが表示されることを確認
スクリーンショット 2020-09-24 23.30.07.png

おわりに

お疲れさまでした。
今回はDockerでRuby on Rails + Vue + MySQLの環境構築する方法について書きました。

VueとRailsはそこまで学習コストが高くないため、初心者にもオススメ出来る構成です。ぜひお試しください!

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

DockerでRails + Vue + MySQLの環境構築をする方法【2020/09最新版】

はじめに

フロントにVue、APIサーバにRailsという組み合わせは日本ではメジャーな構成の一つです。今回はこの構成をDockerを使って構築していきます。

この記事を読むことで、以下の構成をDocker作成出来るようになります!
API: Rails 6系
フロント:Vue(TypeScript) 2系
DB: MySQL 5.7

環境構築の流れ

  1. Rails、VueのDockerfile作成
  2. docker-compose.yaml作成
  3. Rails、Vueのプロジェクト作成

最終的なディレクトリ構成は以下のようになります。

[project_name]
├── api // Rails
│   ├── Dockerfile
│   ├── ...
├── docker-compose.yaml
└── front // Vue
    ├── Dockerfile
    ├── ...

では環境構築に入っていきましょう!

1. ディレクトリ作成

プロジェクトディレクトリとその配下にAPI、フロントのディレクトリを作成します

$ mkdir project_name
$ cd project_name
$ mkdir api front

以降のコマンドはカレントディレクトリがプロジェクトディレクトリ想定で記載しています。

2. Dockerfile作成

RailsとVueそれぞれのDockerfileを作成します。

2-1. Rails

指定しているバージョン
- Ruby 2.7.1
- Rails 6.0.x

api/Dockerfile
FROM ruby:2.7.1

RUN apt-get update -qq && \
    apt-get install -y build-essential \ 
                       libpq-dev \        
                       nodejs \
   && rm -rf /var/lib/apt/lists/* 

RUN mkdir /app
ENV APP_ROOT /app
WORKDIR $APP_ROOT

ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock

RUN bundle install

rm -rf /var/lib/apt/lists/*はaptのキャッシュを削除しています。これはDockerのイメージファイルサイズを軽量化するためです。

このDockerfileではRailsを含むGemfileを必要とするので、以下のファイルをapi/に作成します。

api/Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 6.0.3'

空のGemfile.lockも作成します。

$ touch api/Gemfile.lock

この時点でのディレクトリ構成

[project_name]
├── api 
│   ├── Dockerfile   <- New!
│   ├── Gemfile      <- New!
│   └── Gemfile.lock <- New!
└── front

2-2. Vue

指定しているバージョン
- node 12.18.3
- Vue 2系 <- これは後述のコンテナ内でVueプロジェクトを作成する際に指定します

front/Dockerfile
FROM node:12.18.3-alpine

ENV APP_HOME /app
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME

RUN apk update && npm install -g @vue/cli

この時点でのディレクトリ構成

[project_name]
├── api 
│   ├── Dockerfile
│   ├── Gemfile
│   └── Gemfile.lock
└── front 
    └── Dockerfile <- New!

3. docker-compose.yaml作成

以下のdocker-compose.yamlをプロジェクトディレクトリに作成します

docker-compose.yaml
version: '3'

services:
  web:
    build: ./api
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    ports:
      - '3000:3000'
    depends_on:
      - db
    volumes:
      - ./api:/app
      - bundle:/usr/local/bundle
    tty: true
    stdin_open: true
  db:
    image: mysql:5.7
    volumes:
      - mysql_data:/var/lib/mysql/
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3306:3306'
  front:
    build: ./front
    volumes:
      - ./front:/app
    ports:
      - '8080:8080'
    tty: true
    stdin_open: true
    command: npm run serve

volumes:
  mysql_data:
  bundle:

Railsの開発サーバ起動時に-bでホストを指定します。省略するとホストからコンテナにアクセスできません。0.0.0.0を指定することで、コンテナが持つ全てのインターフェースでlistenできるようになるため、ホスト側からコンテナにアクセスできるようになります。

MySQLとbundleのデータはボリュームにマウントし永続化しています。これによってコンテナを削除しても、データは消えません。
bundleはマウントしなくても、Gemを追加するたびにイメージのビルドすれば良いのですが時間がかかります。bundleをマウントすることでGemの追加がdocker-compose run api bundle installで済むようにです。

この時点でのディレクトリ構成

[project_name]
├── api 
│   ├── Dockerfile
│   ├── Gemfile
│   └── Gemfile.lock
├── docker-compose.yaml <- New!
└── front 
    └── Dockerfile

4. プロジェクトの作成

4-1. Rails

Railsプロジェクトの作成
rails newでRailsプロジェクトを作成します

$ docker-compose run web rails new . --force --database=mysql --api --skip-bundle

rails newの引数について
--force:Gemfileを強制的に上書き更新する
--database:使用するデータベースをMySQLにする
--api:APIモードでプロジェクトを作成。APIモードではUIに関係するファイルが省略されます。
--skip-bundlebundle installを省略します。次のdockerイメージのビルドでbundle installをするためです。

dockerイメージ更新
Gemfileが更新されたので、buildしてdocker imageを更新します。

$ docker-compose build

DBの設定ファイルを修正

RailsのDB設定ファイルapi/config/database.ymlを修正します。

api/config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
- password:
+ password: password
- host: localhost
+ host: db
  • passwordはdocker-compose.yamlの環境変数MYSQL_ROOT_PASSWORDで指定したもの
  • hostはDBのサービス名 に対応しています。

DBの作成

$ docker-compose run web rails db:create 

これでRailsの環境構築は完了です!

4-2. Vue

vue-cliでVueプロジェクトを作成
コンテナ内に入り、vue-cliを使ってVueプロジェクトの作成を対話的にします。

以下の設定項目はvue-cli v4.5.6のものです。設定内容は一例なのでお好みでどうぞ。

Vueコンテナでシェルを実行

$ docker-compose run front sh

以下フロントコンテナ内で対話的に設定していきます。

$ vue create .
# 現在のディレクトリ(/app)に作成するかの確認
? Generate project in current directory? (Y/n) Yes

# プリセットを使用するかどうか
? Please pick a preset: (Use arrow keys)
  Default ([Vue 2] babel, eslint)
  Default (Vue 3 Preview) ([Vue 3] babel, eslint)
❯ Manually select features # TypeScriptをインストールするためこちらを選択

# プロジェクトにインストールするライブラリの選択
? Check the features needed for your project:
 ◉ Choose Vue version #
 ◉ Babel
❯◉ TypeScript # TypeScriptをインストールする場合はこれを選択
 ◯ Progressive Web App (PWA) Support
 ◯ Router
 ◯ Vuex
 ◯ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

# Vueバージョンの選択
? Choose a version of Vue.js that you want to start the project with (Use arrow keys)
❯ 2.x
  3.x (Preview)

# Class styleを使用するかどうか。私はObject styleを使うため No
? Use class-style component syntax? (Y/n) No

# TypeScriptと一緒にbabelを使うか
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n) Yes

# LintとFormatterの設定に何を使うか
? Pick a linter / formatter config:
  ESLint with error prevention only
  ESLint + Airbnb config
  ESLint + Standard config
❯ ESLint + Prettier
  TSLint (deprecated)

# Lintの実行タイミング
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◉ Lint on save # 保存時にLintを実行
 ◯ Lint and fix on commit (requires Git)

# Babel, ESLintなどの設定をどこに記述するか
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
❯ In dedicated config files # 各設定ファイルにする
  In package.json

# 今回設定した内容をプリセットで保存するか。基本的にはプロジェクトを以降作成することはないため No
? Save this as a preset for future projects? No

# パッケージマネージャーに何を使うか
? Pick the package manager to use when installing dependencies: (Use arrow keys)
❯ Use Yarn
  Use NPM

インストール完了後、Ctrl+Dでコンテナを停止します。

5. 動作確認

コンテナを立ち上げる

$ docker-compose up -d

-d: バックグラウンドでプロセスを実行する

5-1. Rails

localhost:3000にアクセスし以下のページが表示されることを確認
スクリーンショット 2020-09-24 22.37.32.png

5-2. Vue

localhost:8080にアクセスし以下のページが表示されることを確認
スクリーンショット 2020-09-24 23.30.07.png

おわりに

お疲れさまでした。
今回はDockerでRuby on Rails + Vue + MySQLの環境構築する方法について書きました。

VueとRailsはそこまで学習コストが高くないため、初心者にもオススメ出来る構成です。ぜひお試しください!

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

Vuex + TypeScript + vuex-module-decoratorsでのmutationとgetterの単体テスト

やりたいこと

vuex-module-decoratorsを使うことで、TyepScriptでVuexを記述する際により型付けの恩恵を受けられるようになります。この記事ではvuex-module-decoratorsを使った際のVuexのmutationとgetterのJestでの単体テストの記述方法を紹介したいと思います。

以下の環境で動作検証しています。

  • Vue.js: 2.6.19
  • Vuex: 3.0.1
  • vuex-module-decorators: 0.9.9
  • 単体テストユニット: Jest

テスト対象のVuex

テスト対象として以下のVuexを作成します。

counter.ts
import { Action, Module, Mutation, VuexModule } from "vuex-module-decorators";

@Module({ name: "counter", namespaced: true })
export class Counter extends VuexModule {

  // state
  private count: number = 0;

  // mutaion
  // カウンターをインクリメントする
  @Mutation
  public increment(): void {
    this.count++;
  }

  // getter
  // カウンターの値を取得
  get getCount() {
    return this.count;
  }

  // action
  // カウンターの値を2インクリメントするアクション
  @Action({})
  public add2(): void {
    this.increment();
    this.increment();
  }
}

Jestでの単体テスト

以下のように単体テストを記述できます。ポイントとしては、Counter.mutations!.increment(mockState, {})のようにモック化したstateに対してmutation, getterを実行することでテストケースごとに別のステートを使用している点です。

テストケースごとにステートの値を変更できる点と、mutaionとgetterを独立してテストできる点がメリットです。

counter.spec.ts
import Vuex from "vuex";
import { Counter } from "@/store/modules/counter";
import { createLocalVue } from "@vue/test-utils";
import { cloneDeep } from "lodash";
import { Action, getModule } from "vuex-module-decorators";

// ローカルのVueインスタンを使用する
const localVue = createLocalVue();
localVue.use(Vuex);

describe("Counter test", () => {

  // mutationnのテスト
  it("mutation test increment1", () => {

    // ステートをモック化
    const mockState = {
      count: 0
    };

    Counter.mutations!.increment(mockState, {});
    expect(mockState.count).toBe(1);
  });

  // mutationnのテスト
  it("mutation test increment2", () => {

    // ステートをモック化
    const mockState = {
      count: 0
    };

    // テストケースごとに別のモック化したステートを使用できる
    expect(mockState.count).toBe(0);
    Counter.mutations!.increment(mockState, {});
    Counter.mutations!.increment(mockState, {});
    expect(mockState.count).toBe(2);
  });

  // getterのテスト
  it("getter test", () => {

    // ステートをモック化、カウンターの初期値を変更
    const mockState = {
      count: 3
    };

    expect(Counter.getters!.getCount(mockState, null, null, null)).toBe(3);
  });
});

おわりに

actionも同様にモック化したstatenを使用してテストしたかったのですが、記述方法がわかりませんでした。ご存知の方がいらっしましたら教えていただけると嬉しいです。

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