- 投稿日:2020-09-25T18:17:43+09:00
vue.js(nuxt.js)のdataはvuexのgettersを使ってデータ管理しよう
はじめに
vueinstanceでは親子component同士でdataを通信をする場合は
Props/$emitco
やcomponent同士でデータ通信をするためにはEvent Bus
を使うのが一般ですがこのように複雑なdata通信を簡単に通信することができる考え方がVuxです。
Vuexとは
ざっくりですが下記のイメージ図のように中央管理所からデータを必要な時呼び出しして使えるイメージです。
自分のイメージだと親と子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 vuexnuxtのプロジェクトを作成すると直下に
store
フォルダが作成されるのでstore
直下にindex.jsを新しく作って下記のサンプルコードのようにvuexをimportして必要なdataの登録をします。store/index.jsimport Vuex from 'vuex' export const appStore = () => { return new Vuex.Store({ state: {//data myData:{ firstName: 'thunder', lastName: 'fury', } }, mutations: { }, actions: { } }) }; export default appStorevuexの
state
はvueinstanceで言うと下記のdataの役割です。
storeにあるdataを使うためには$store
で呼び出しができます。<template lang="pug"> div p {{ $store.state.myData.firstName }} p {{ $store.state.myData.lastName }} </template>
これでも良いですがdataのオブジェクトの下層が深い場合など繰り返しコードが多くなってしまうのでその時使うのがGetters
です。Getters実行イメージ
storeのindex.jsにstateのdataをgettersに関数として登録してreturnして使える流れです。
store/index.js.jsgetters: { myData: state => { return state.myData }, //上下同じ getters: { myData: function(state) { return state.myData },どちらでも書き方は問題ないです。
少しでもにコード量を減らすためにはarrow functionが良いか思います。登録が終わったら使いたいvueのcomponent
mapGetters
をimportをしてcomputed
にstore/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>
結果$store.state.myData.firstName
を呼び出した時と同じくなります。
getters
で宣言した関数名を別の名前で受け取りする方法も有って配列で受けたのをオブジェクトで受け取ってのkeyとvalueで割当して使うこともできます。computed: { ...mapGetters({ testData:'myData' }) }同じ結果です。
このように別のvueComponentを作ってもわざわざ処理をしなくてもimportだけでdataを呼び出しして使うことができます。グロバル関数系のmutations
やactions
もありますが自分はMixin
でグロバル化して使っても十分使えるかと思いました。終わり
ページを遷移した場合データの渡すしや受けるなどサイトが膨大になると複雑になるので中大サイトをもし、vueで作成する場合vuexのgettersを使って簡単に管理するのがどうですか
- 投稿日:2020-09-25T18:17:43+09:00
dataはvuexのgettersでデータ管理しよう
はじめに
vueinstanceでは親子component同士でdataを通信をする場合は
Props/$emitco
やcomponent同士でデータ通信をするためにはEvent Bus
を使うのが一般ですがこのように複雑なdata通信を簡単に通信することができる考え方がVuxです。
Vuexとは
ざっくりですが下記のイメージ図のように中央管理所からデータを必要な時呼び出しして使えるイメージです。
自分のイメージだと親と子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 vuexnuxtのプロジェクトを作成すると直下に
store
フォルダが作成されるのでstore
直下にindex.jsを新しく作って下記のサンプルコードのようにvuexをimportして必要なdataの登録をします。store/index.jsimport Vuex from 'vuex' export const appStore = () => { return new Vuex.Store({ state: {//data myData:{ firstName: 'thunder', lastName: 'fury', } }, mutations: { }, actions: { } }) }; export default appStorevuexの
state
はvueinstanceで言うと下記のdataの役割です。
storeにあるdataを使うためには$store
で呼び出しができます。<template lang="pug"> div p {{ $store.state.myData.firstName }} p {{ $store.state.myData.lastName }} </template>
これでも良いですがdataのオブジェクトの下層が深い場合など繰り返しコードが多くなってしまうのでその時使うのがGetters
です。Getters実行イメージ
storeのindex.jsにstateのdataをgettersに関数として登録してreturnして使える流れです。
store/index.js.jsgetters: { myData: state => { return state.myData }, //上下同じ getters: { myData: function(state) { return state.myData },どちらでも書き方は問題ないです。
少しでもにコード量を減らすためにはarrow functionが良いか思います。登録が終わったら使いたいvueのcomponent
mapGetters
をimportをしてcomputed
にstore/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>
結果$store.state.myData.firstName
を呼び出した時と同じくなります。
getters
で宣言した関数名を別の名前で受け取りする方法も有って配列で受けたのをオブジェクトで受け取ってのkeyとvalueで割当して使うこともできます。computed: { ...mapGetters({ testData:'myData' }) }同じ結果です。
このように別のvueComponentを作ってもわざわざ処理をしなくてもimportだけでdataを呼び出しして使うことができます。グロバル関数系のmutations
やactions
もありますが自分はMixin
でグロバル化して使っても十分使えるかと思いました。終わり
ページを遷移した場合データの渡すしや受けるなどサイトが膨大になると複雑になるので中大サイトをもし、vueで作成する場合vuexのgettersを使って簡単に管理するのがどうですか
- 投稿日:2020-09-25T18:17:43+09:00
Nuxr.jsのdataはvuexのstate,gettersでデータ管理しようstate、
はじめに
vueinstanceでは親子component同士でdataを通信をする場合は
Props/$emitco
やcomponent同士でデータ通信をするためにはEvent Bus
を使うのが一般ですがこのように複雑なdata通信を簡単に通信することができる考え方がVuxです。
Vuexとは
ざっくりですが下記のイメージ図のように中央管理所からデータを必要な時呼び出しして使えるイメージです。
自分のイメージだと親と子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 vuexnuxtのプロジェクトを作成すると直下に
store
フォルダが作成されるのでstore
直下にindex.jsを新しく作って下記のサンプルコードのようにvuexをimportして必要なdataの登録をします。store/index.jsimport Vuex from 'vuex' export const appStore = () => { return new Vuex.Store({ state: {//data myData:{ firstName: 'thunder', lastName: 'fury', } }, mutations: { }, actions: { } }) }; export default appStorevuexの
state
はvueinstanceで言うと下記のdataの役割です。
storeにあるdataを使うためには$store
で呼び出しができます。<template lang="pug"> div p {{ $store.state.myData.firstName }} p {{ $store.state.myData.lastName }} </template>
これでも良いですがdataのオブジェクトの下層が深い場合など繰り返しコードが多くなってしまうのでその時使うのがGetters
です。Getters実行イメージ
storeのindex.jsにstateのdataをgettersに関数として登録してreturnして使える流れです。
store/index.js.jsgetters: { myData: state => { return state.myData }, //上下同じ getters: { myData: function(state) { return state.myData },どちらでも書き方は問題ないです。
少しでもにコード量を減らすためにはarrow functionが良いか思います。登録が終わったら使いたいvueのcomponent
mapGetters
をimportをしてcomputed
にstore/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>
結果$store.state.myData.firstName
を呼び出した時と同じくなります。
getters
で宣言した関数名を別の名前で受け取りする方法も有って配列で受けたのをオブジェクトで受け取ってのkeyとvalueで割当して使うこともできます。computed: { ...mapGetters({ testData:'myData' }) }同じ結果です。
このように別のvueComponentを作ってもわざわざ処理をしなくてもimportだけでdataを呼び出しして使うことができます。グロバル関数系のmutations
やactions
もありますが自分はMixin
でグロバル化して使っても十分使えるかと思いました。終わり
ページを遷移した場合データの渡すしや受けるなどサイトが膨大になると複雑になるので中大サイトをもし、vueで作成する場合vuexのgettersを使って簡単に管理するのがどうですか
- 投稿日:2020-09-25T18:17:43+09:00
Nuxt.jsのdataはvuexのstate,gettersでデータ管理しよう
はじめに
vueinstanceでは親子component同士でdataを通信をする場合は
Props/$emitco
やcomponent同士でデータ通信をするためにはEvent Bus
を使うのが一般ですがこのように複雑なdata通信を簡単に通信することができる考え方がVuxです。
Vuexとは
ざっくりですが下記のイメージ図のように中央管理所からデータを必要な時呼び出しして使えるイメージです。
自分のイメージだと親と子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 vuexnuxtのプロジェクトを作成すると直下に
store
フォルダが作成されるのでstore
直下にindex.jsを新しく作って下記のサンプルコードのようにvuexをimportして必要なdataの登録をします。store/index.jsimport Vuex from 'vuex' export const appStore = () => { return new Vuex.Store({ state: {//data myData:{ firstName: 'thunder', lastName: 'fury', } }, mutations: { }, actions: { } }) }; export default appStorevuexの
state
はvueinstanceで言うと下記のdataの役割です。
storeにあるdataを使うためには$store
で呼び出しができます。<template lang="pug"> div p {{ $store.state.myData.firstName }} p {{ $store.state.myData.lastName }} </template>
これでも良いですがdataのオブジェクトの下層が深い場合など繰り返しコードが多くなってしまうのでその時使うのがGetters
です。Getters実行イメージ
storeのindex.jsにstateのdataをgettersに関数として登録してreturnして使える流れです。
store/index.js.jsgetters: { myData: state => { return state.myData }, //上下同じ getters: { myData: function(state) { return state.myData },どちらでも書き方は問題ないです。
少しでもにコード量を減らすためにはarrow functionが良いか思います。登録が終わったら使いたいvueのcomponent
mapGetters
をimportをしてcomputed
にstore/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>
結果$store.state.myData.firstName
を呼び出した時と同じくなります。
getters
で宣言した関数名を別の名前で受け取りする方法も有って配列で受けたのをオブジェクトで受け取ってのkeyとvalueで割当して使うこともできます。computed: { ...mapGetters({ testData:'myData' }) }同じ結果です。
このように別のvueComponentを作ってもわざわざ処理をしなくてもimportだけでdataを呼び出しして使うことができます。グロバル関数系のmutations
やactions
もありますが自分はMixin
でグロバル化して使っても十分使えるかと思いました。終わり
ページを遷移した場合データの渡すしや受けるなどサイトが膨大になると複雑になるので中大サイトをもし、vueで作成する場合vuexのgettersを使って簡単に管理するのがどうですか
- 投稿日:2020-09-25T18:17:43+09:00
Nuxt.jsのdataはvuexのstate,gettersで管理しよう
はじめに
vueinstanceでは親子component同士でdataを通信をする場合は
Props/$emitco
やcomponent同士でデータ通信をするためにはEvent Bus
を使うのが一般ですがこのように複雑なdata通信を簡単に通信することができる考え方がVuxです。
Vuexとは
ざっくりですが下記のイメージ図のように中央管理所からデータを必要な時呼び出しして使えるイメージです。
自分のイメージだと親と子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 vuexnuxtのプロジェクトを作成すると直下に
store
フォルダが作成されるのでstore
直下にindex.jsを新しく作って下記のサンプルコードのようにvuexをimportして必要なdataの登録をします。store/index.jsimport Vuex from 'vuex' export const appStore = () => { return new Vuex.Store({ state: {//data myData:{ firstName: 'thunder', lastName: 'fury', } }, mutations: { }, actions: { } }) }; export default appStorevuexの
state
はvueinstanceで言うと下記のdataの役割です。
storeにあるdataを使うためには$store
で呼び出しができます。<template lang="pug"> div p {{ $store.state.myData.firstName }} p {{ $store.state.myData.lastName }} </template>
これでも良いですがdataのオブジェクトの下層が深い場合など繰り返しコードが多くなってしまうのでその時使うのがGetters
です。Getters実行イメージ
storeのindex.jsにstateのdataをgettersに関数として登録してreturnして使える流れです。
store/index.js.jsgetters: { myData: state => { return state.myData }, //上下同じ getters: { myData: function(state) { return state.myData },どちらでも書き方は問題ないです。
少しでもにコード量を減らすためにはarrow functionが良いか思います。登録が終わったら使いたいvueのcomponent
mapGetters
をimportをしてcomputed
にstore/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>
結果$store.state.myData.firstName
を呼び出した時と同じくなります。
getters
で宣言した関数名を別の名前で受け取りする方法も有って配列で受けたのをオブジェクトで受け取ってのkeyとvalueで割当して使うこともできます。computed: { ...mapGetters({ testData:'myData' }) }同じ結果です。
このように別のvueComponentを作ってもわざわざ処理をしなくてもimportだけでdataを呼び出しして使うことができます。グロバル関数系のmutations
やactions
もありますが自分はMixin
でグロバル化して使っても十分使えるかと思いました。終わり
ページを遷移した場合データの渡すしや受けるなどサイトが膨大になると複雑になるので中大サイトをもし、vueで作成する場合vuexのgettersを使って簡単に管理するのがどうですか
- 投稿日:2020-09-25T16:04:12+09:00
vue.js if null チェック
nullチェックしたい。
<div v-if="res.social.amazon !== null"> あるでー </div> <div v-else> ないでー </div>
- 投稿日:2020-09-25T15:03:43+09:00
Vue.jsでビジュアルコンテンツエディタ作成
概要
記事コンテンツの管理ツールの需要があって、Vue.jsを用いて作成てみました。
諸事情によってサーバーサイドを利用できず、すべてフロントで完結する必要があります。
イメージとしては簡易版のワードプレス管理画面のようなものを想定して設計しました。
- フロントエンド(エンドユーザー向け)
- ReactやVueなどで記事コンテンツのjsonを用いて描画する
- バックエンド
- エディタを使って、このような記事コンテンツのjsonファイルを作成、管理、更新
第一弾としてはバックエンドのパートをご紹介します。
デモはこちらページ構成
- トップ
- 既存記事コンテンツのjsonをアップロードして、リスト形式で表示、完成したデータをjson形式でダウンロード
- 記事作成画面
- Vue-Quill-Editorを使ってビジュアルエディタを導入、各項目の入力
- 記事編集画面
- 基本的には作成と同じく、各項目の入力、既存記事コンテンツの更新、削除
必要なライブラリー
vue:ページ遷移時に、propsの受け渡しが必要ですので、CLIの導入が必要です。
vue-router:ページ遷移のために、必要となります。
axios:画像ファイルのアップロード時に必要です。
vue-quill-editor:ビジュアルエディタのプラグインです。ファイルの構成
main.js
router.jsの導入
main.jsimport 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.jsimport 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 routerApp.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>引き続き、第二弾ではフロント側の描画をご紹介致します。
完成次第、またこちらにて投稿します。
- 投稿日:2020-09-25T12:28:17+09:00
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.tsimport 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.tsimport 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); }); }); }, }これで実装できます
- 投稿日:2020-09-25T12:07:08+09:00
未経験からフロントエンドエンジニアとして自社開発企業へ転職するまでにやったこと
はじめに
こんにちは☺
このたび、働きながら独学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.jsやReactなどを使用するのがよいと思います。
わたしは
・日本語の公式ドキュメントが充実している
・学習コストが低い
などの理由から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代でも自社開発企業に転職することができました!
転職活動にあたって、
・フロントエンドエンジニア志望でもしっかりポートフォリオを作成すること
・自己分析を行い、過去の職歴に基づく強みをアピールすること
が重要だと思いました。
また、完全独学だったので、早めにメンターサービスを利用してもよかったなと思います。いつ転職を目指すか、どのような学習方法が合っているかは個人によって異なりますので、自分に合ったやり方が一番です。
わたしの場合は転職先決定までの期間を短く設定していますが、長期目標として基礎をしっかり身につけた上でポートフォリオ作成してもよいと思いますし、いきなり正社員でなくともインターンをしながら学習するのもよいと思います。
ただ、やると決めたら徹底的にやること、それだけです!少しでも参考になれば嬉しいです☺
- 投稿日:2020-09-25T00:32:26+09:00
DockerでRuby on Rails + Vue + MySQLの環境構築をする方法【2020/09最新版】
はじめに
フロントにVue、APIサーバにRailsという組み合わせは日本ではメジャーな構成の一つです。今回はこの構成をDockerを使って構築していきます。
この記事を読むことで、以下の構成をDocker作成出来るようになります!
API: Rails 6系
フロント:Vue(TypeScript) 2系
DB: MySQL 5.7環境構築の流れ
- Rails、VueのDockerfile作成
- docker-compose.yaml作成
- 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.xapi/DockerfileFROM 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/Gemfilesource 'https://rubygems.org' gem 'rails', '~> 6.0.3'空のGemfile.lockも作成します。
$ touch api/Gemfile.lockこの時点でのディレクトリ構成
[project_name] ├── api │ ├── Dockerfile <- New! │ ├── Gemfile <- New! │ └── Gemfile.lock <- New! └── front2-2. Vue
指定しているバージョン
- node 12.18.3
- Vue 2系 <- これは後述のコンテナ内でVueプロジェクトを作成する際に指定しますfront/DockerfileFROM 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.yamlversion: '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 └── Dockerfile4. プロジェクトの作成
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-bundle
:bundle install
を省略します。次のdockerイメージのビルドでbundle install
をするためです。dockerイメージ更新
Gemfileが更新されたので、buildしてdocker imageを更新します。$ docker-compose build
DBの設定ファイルを修正
RailsのDB設定ファイル
api/config/database.yml
を修正します。api/config/database.ymldefault: &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
にアクセスし以下のページが表示されることを確認
5-2. Vue
localhost:8080
にアクセスし以下のページが表示されることを確認
おわりに
お疲れさまでした。
今回はDockerでRuby on Rails + Vue + MySQLの環境構築する方法について書きました。VueとRailsはそこまで学習コストが高くないため、初心者にもオススメ出来る構成です。ぜひお試しください!
- 投稿日:2020-09-25T00:32:26+09:00
DockerでRails + Vue + MySQLの環境構築をする方法【2020/09最新版】
はじめに
フロントにVue、APIサーバにRailsという組み合わせは日本ではメジャーな構成の一つです。今回はこの構成をDockerを使って構築していきます。
この記事を読むことで、以下の構成をDocker作成出来るようになります!
API: Rails 6系
フロント:Vue(TypeScript) 2系
DB: MySQL 5.7環境構築の流れ
- Rails、VueのDockerfile作成
- docker-compose.yaml作成
- 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.xapi/DockerfileFROM 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/Gemfilesource 'https://rubygems.org' gem 'rails', '~> 6.0.3'空のGemfile.lockも作成します。
$ touch api/Gemfile.lockこの時点でのディレクトリ構成
[project_name] ├── api │ ├── Dockerfile <- New! │ ├── Gemfile <- New! │ └── Gemfile.lock <- New! └── front2-2. Vue
指定しているバージョン
- node 12.18.3
- Vue 2系 <- これは後述のコンテナ内でVueプロジェクトを作成する際に指定しますfront/DockerfileFROM 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.yamlversion: '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 └── Dockerfile4. プロジェクトの作成
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-bundle
:bundle install
を省略します。次のdockerイメージのビルドでbundle install
をするためです。dockerイメージ更新
Gemfileが更新されたので、buildしてdocker imageを更新します。$ docker-compose build
DBの設定ファイルを修正
RailsのDB設定ファイル
api/config/database.yml
を修正します。api/config/database.ymldefault: &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
にアクセスし以下のページが表示されることを確認
5-2. Vue
localhost:8080
にアクセスし以下のページが表示されることを確認
おわりに
お疲れさまでした。
今回はDockerでRuby on Rails + Vue + MySQLの環境構築する方法について書きました。VueとRailsはそこまで学習コストが高くないため、初心者にもオススメ出来る構成です。ぜひお試しください!
- 投稿日:2020-09-25T00:31:48+09:00
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.tsimport { 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.tsimport 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を使用してテストしたかったのですが、記述方法がわかりませんでした。ご存知の方がいらっしましたら教えていただけると嬉しいです。