20201115のvue.jsに関する記事は10件です。

Vue.Jsのv-forディレクティブはforEachと考えればしっくりくる!

この記事について

JavaScriptもろくに書けないにも関わらずいきなり、Vueをバリバリ使った案件にぶち込まれて、はや半年。最初に理解に苦しんだのが"v-for"。
今ではv-forにはかなりお世話になっているが、最初のうちはもの凄く苦しんだ。
何せfor文すら書けなかったのだから無理もない。

そもそもv-for とは??

まずVue.jsを触りはじめたときは、ディレクティブすら意味が分からなかった。
よくもまあそんな状態で初めたものである。

簡単にいうとディレティブとは、Html側でJavaScriptっぽいことができるように、
Vue.Jsで用意されている便利な部品のこと だと私は思っている。
Vueにはいろんなディレクティブが用意されている。それらを駆使することによって、
ScriptからHtmlへのアクセス(逆もしかり)が非常に簡単になるというのがVue.Jsの
本質かと思う。

それではv-for は何かというと
公式サイトによると

v-for で配列に要素をマッピングする
配列に基づいて、アイテムのリストを描画するために、v-for ディレクティブを使用することができます。v-for ディレクティブは item in items の形式で特別な構文を要求し、items はソースデータの配列で、item は配列要素がその上で反復されているエイリアスです:

半年前のど素人の私なら、完結に書かれすぎてさっぱり分からなかった。
今見返すと実に完結に書かれてある。(笑)
当然、初学者にはわかるはずもない(笑)私だけだったりして

私なりの見解だが、ある配列の値を表示するために、v-forというVueが用意した
便利な部品を使って、item in itemsみたいな書き方でその値を取得して、itemで反復して配列を呼び出すことができるというのがv-forだ。
自分で書いていてもよくわからなくなってきたので、JavaScriptのforEachを考えればしっくりくるのではないか?

試しにforEachを使って配列の値をconsole.logで表示させるコードを下記に示したい。
items という配列をitemという引数を使って、一つずつ描画していることがわかる。
Vue.jsではv-forを使うことで、いちいちforEachなんて書かなくて済むのだ。

JavaScript
const items = ['item1', 'item2', 'item3']
const copyItemsArray = []
items.forEach((item) => {
  copyItemsArray.push(item)
 console.log(item)
})
//結果は
//item1
//item2
//item3
//と表示される。

もう少しforEachにお付き合いいただきたい。
今度は連想配列で考えてみる。

JavaScript
const objectItems = [
  {name:"太郎",age:22},
  {name:"治郎",age:20},
  {name:"三郎",age:18}
]
const objectArray = []
objectItems.forEach((item)=>{
  objectArray.push(item)
  console.log(`名前:${item.name}/年齢:${item.age}`)
})
//名前:太郎/年齢:22
//名前:治郎/年齢:20
//名前:三郎/年齢:18

forEachを知って、かなりしっくりきた。
というかv-forもforEachもかわらんやん(笑)

さて、ここで、v-for を使用して描画とやらをやってみる。
CSSは苦手なので、Vuetifyを使用している。
それと普段のプロジェクトで、TypeScriptを触っている都合上
私が一番慣れている書き方で書かせていただいた。

Vue.js
<template>
    <div>
        <ul>
            <li v-for="(brother,i) in brothers" :key="i">名前{{brother.name}}/年齢:{{brother.age}}</li>
        </ul>
    </div>
</template>
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'

interface Person {
    name:string
    age:number
}
@Component({})
export default class extends Vue{
    brothers:Person[] = [
        {name:"太郎",age:22},
        {name:"治郎",age:20},
        {name:"三郎",age:18}
    ]
}
</script>

img1.png

↑こんな感じで描画される。
v-forを使用するときは、注意点としてkey要素というものをつけなければならない。
大抵の場合はオブジェクトの中にidなんかがあってそれをkey要素として返してあげるのだが、とくにid指定がないときなんかはindexで返してあげるのがベター。
その場合は、v-forのitemの2番目の引数としてindexを取ることができるので、
(item,i)のように書いてあげる。
このv-for のkeyの動作については下記のサイトに非常に詳しく載っているのでぜひ参考にしてみてほしい。
参考: 【徹底解説】これを見ればわかるvue.jsのv-forのkeyの動作

さて、せっかくUI frameworkとしてVuetify.jsを使用しているのだから、
何かコンポーネントを使用して描画してみよう。
今回はv-simple-tableというコンポーネントを使用。
その名の通り、めっちゃシンプルなコンポーネント。

Vue.js
<template>
    <div>
        <ul>
            <li v-for="(brother,i) in brothers" :key="i">名前{{brother.name}}/年齢:{{brother.age}}</li>
        </ul>
        <v-simple-table>
            <template v-slot:default>
                <thead>
                    <tr>
                        <th>名前</th>
                        <th>年齢</th>
                    </tr>
                </thead>
                <tbody>
                    <tr
                        v-for="(brother,i) in brothers" :key="i">
                        <td>{{brother.name}}</td>
                        <td>{{brother.age}}</td>    
                    </tr>
                </tbody>
            </template>
        </v-simple-table>
    </div>
</template>
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'

interface Person {
    name:string
    age:number
}
@Component({})
export default class extends Vue{
    brothers:Person[] = [
        {name:"太郎",age:22},
        {name:"治郎",age:20},
        {name:"三郎",age:18}
    ]
}
</script>

img2.png

↑こんな感じで描画される。

v-forなんて簡単

Vue.Jsなんて所詮はJavaScriptのフレームワーク(笑)って言ってみたかったのだが、
半年前の自分にいってやりたい。
まだまだひよっこエンジニアの私だが、最初は公式ページを見てもちんぷんかんぷん。
何度辞めたいと思ったことか、、、、、、

Vue.Jsは慣れれば非常に便利なツールだと思っている。
なんなら私なんて、innerHTML とか書けない(笑)
恥ずかしくて言えないが、、、、、
それはいいか悪いのか別として、非常に楽なツールである。
最初、Vueの勉強を始めたときは、”学習コストが低いのがVue。だから、初心者向きなんだ”って書いていたサイトを見て、俺は全く理解できていないから、もう辞めた方がいいのか、、、なんて本気で悩んだこともある。

勉強する順序が悪かっただけだった。
これからもVue.Jsを使ってガンガン開発に挑戦してみようと思っている。

最後まで読んでくださった方ありがとうございました。

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

【vue.js】Vuexを学ぶ

Vuexの概念が何となく理解出来たので、ついでに使い方もまとめておく。
自社の開発においても大量のコンポーネントを扱わなければならない中でVuexが必須となっているので、勉強し直した。

Vuex(ビューエックス)とは何か?

Vuex とは何か? | Vuex

ググったら一番最初に出てくるこれを見てもイマイチ分からない人に向け、
vue.jsの基本は一応分かっている前提で説明すると、
スクリーンショット 2020-11-14 19.07.13.png
沢山のコンポーネントがあるそれなりの規模のプロジェクトがあったとする。
さて、この図でComponentAのデータをComponentCやComponentEに渡すにはどうしたらいいだろうか。

親コンポーネントから子コンポーネントへ、
また子コンポーネントから親コンポーネントへのデータのやりとりが出来るのはわかると思う。でも、孫から子へ、それを親に渡し、さらにそれを子から孫へ、みたいなやりとりは手間である。($emitして$emitしてpropsしてpropsして見たいにやるのはスマートではない。)
スクリーンショット 2020-11-14 19.29.39.png
そこでVuexを使うと、グローバル変数のような感じでデータを扱うことが出来るようになると言う感じ(概念)。と言われれば分かり易いのではないだろうか。これで深いコンポーネント同士でのやりとりが出来るようになる。

導入

Vuexのパッケージをインストールする。
vue cliでプロジェクト作成時にセットでインストール済みである場合は必要ない。

npm install vuex

vuex用のファイルを新たに設ける。
ファイル名は何でも良いのだが、store.jsとするのが一般的。

store.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex); // vuexをvue全体で使用する宣言

export default new Vuex.Store({ // main.jsで読み込めるようにする
 // 以下で定義したものはどのコンポーネントでも使用出来る
  state: { 
    number: 2
  }
})

あとはmain.jsで読み込むだけ。

main.js
import Vue from 'vue';
import App from './App';
import router from './router';
import store from './store' // store.jsをインポート

Vue.config.productionTip = false;

new Vue({
  el: '#app',
  router,
  store, // store: store,
  components: { App },
  template: '<App/>',
});

stateを使ってみる

stateとは?

文字通りstoreの状態をプロパティーとして返すと言う位置付けになろうか。前述の通り、ここで定義した内容はグローバルに取得する事が出来るようになる。

store.js
export default new Vuex.Store({ // main.jsで読み込めるようにする
 // 以下で定義したものはどのコンポーネントでも使用出来る
  state: { 
    number: 2
  }
})

ComponentAで表示する変数を

ComponentA.vue
<template>
  <p>{{ number }}</p>
</template>

<script>
  export default {
    computed: {
      number() {
        // $storeとする事でどこからでも呼べるようになる
        return this.$store.state.number;
      }
    }
  }
</script>

ComponentEから操作出来る、みたいな事が出来る。

ComponentE.vue
<template>
  <b-button @click="increment">+1</b-button>
</template>

<script>
  export default {
    methods: {
      increment(){
        // $storeとする事でどこからでも呼べるようになる
        this.$store.state.number++;
      },
    },
  }
</script>

gettersを使ってみる

gettersとは?

storeの状態を算出したい時にgettersを定義する事で、算出プロパティーとして使う事が出来る感じ。

store.js
export default new Vuex.Store({ // インスタンスをmain.jsで読み込めるようにする
  state: { // 状態を全体で使用出来るようにする
    number: 0
  },
  getters: {
    counter: state => state.number++, 
  }
})

こんな感じでVuexの中でcomputedのような事が出来る。

ComponentA.vue
<template>
  <p>{{ count }}</p>
</template>

<script>
  export default {
    computed: {
      count() {
        return this.$store.getters.counter; // $store.gettersで呼ぶ
      }
    }
  }
</script>

まぁこの程度であればgettersを使う意味はないのだけど、
よりコンポーネントが複雑化してきた時に真価を発揮するものなのでそこはご容赦頂きたい。

複数のgettersを扱う時にもっと便利に使う事が出来るのが、mapGettersである。

store.js
export default new Vuex.Store({ 
  state: { 
    number: 2
  },
  // 複数定義しているものがあるとする
  getters: {
    doubleCount: state => state.number * 2,
    tripleCount: state => state.number * 3
  }
})

こう書くことも出来るが、

ComponentA.vue
<script>
  export default {
    computed: {
      doubleCount() {
        return this.$store.getters.doubleCount;
      },
      tripleCount() {
        return this.$store.getters.tripleCount;
      },
    }
  }
</script>

mapGettersを用いると一気に整理される。

ComponentA.vue
<script>
import { mapGetters } from "vuex"; // mapGettersをインポートする

  export default {
    // 配列
    computed: mapGetters(["doubleCount", "tripleCount"]),
  }
</script>

配列ではなく、オブジェクトでも良い。

ComponentA.vue
<script>
import { mapGetters } from "vuex"; // mapGettersをインポートする

  export default {
    // オブジェクトでも良い
    computed: mapGetters({
      doubleCount: "doubleCount",
      tripleCount: "tripleCount"
    }),
  }
</script>

必要なgettersだけを取ってくる事が出来るので是非使いこなしたい。
ちなみにこのままだと、computedにmapGetters以外に記述する事が出来ないので、普段使いでは

ComponentA.vue
<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    // スプレッド演算子(object spread operator)を使って組み込む
    ...mapGetters([
      'doubleCount',
      'tripleCount',
    ])
  }
}
</script>

という感じで使うっぽい。

mutationsを使ってみる

mutationsとは?

stateを使う事でコンポーネント間のデータのやりとりは簡単にはなるけれど、
何でもかんでも自由にやりとりをさせ過ぎると、逆に分かりづらくなったり、追跡、管理が煩雑になる。そこで使用するのがmutationsという事のようだ。

store.js
export default new Vuex.Store({
  state: {
    number: 2
  },
  mutations: {
    increment(state, str) { // 第一引数にstateをとり、実際の変更を記述する
      state.number += str;
    }
  }
})

呼び出す側ではstore.commitでmutationsを指定する。

ComponentA.vue
<script>
methods: {
  increment(){
    this.$store.commit('increment', 2);
  },
},
<script>

stateをあっちこっちで変更する事は思わぬバグを発生させる可能性があるから、
mutationsで扱いましょうという感じだろうか。

また、mutationsにもmapMutationsというヘルパーが存在する。

ComponentA.vue
<script>
import { mapMutations } from "vuex"; // mapMutationsをインポートする

  export default {
    methods: {
      // mapGettersと同じようにスプレット演算子で書ける
      ...mapMutations(["increment","decrement"]),
    },
  }
</script>

ちなみにmutationsは同期的でなければならないというルールがある。
では非同期を扱うにはどうしたらいいのか。

actionを使ってみる

actionとは?

actionはmutationsと異なり、任意の非同期処理を含む事が出来る。
なので、setTimeoutで一定時間後に特定の処理を行うという記述も可能となっている。

store.js
export default new Vuex.Store({ 
  state: { 
    count: 2
  },
  actions: {
    increment({ commit },number) {
      commit('increment', number);
    }
  }
})

store内のactionsでcommitし、コンポーネントでdispatchする。

ComponentA.vue
<script>
methods: {
  increment() {
    this.$store.dispatch('increment', 2); 
  }
},
</script>

非同期でもOK

store.js
actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

最後はmapActionsヘルパー、これまで出てきたmapと同じ感覚で使用出来る。

ComponentA.vue
<script>
import { mapActions } from "vuex";

  export default {
    methods: {
      // スプレット演算子で書ける
      ...mapActions(["increment","decrement"]),
    }
</script>

まとめると・・・

・stateでvuexの状態を管理
・gettersでstateの変更を算出
・mutationsでstateの状態を変更、commitで呼び出される
・actionで同期、非同期なデータの処理、必要に応じてcommitする

これらの基本に基づいて、導入するプロジェクト毎にどうVuexを使っていくかを取捨選択していく形になるという事が分かった。

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

AWS Lambdaを使ってサーバレスアプリを作成(CRUDのR)

記事を閲覧いただき、ありがとうございます。中村です!!
2020年10月にAWSエンジニアとして転職したので、AWSの予習も兼ねてLambdaを使ったアプリ作成について書いきます。

AWS Lambdaとは?

ここでつらつら説明するより公式動画の方がわかりやすいという結論に至りました。まずはご覧ください。



以下を使用して作成ます。

フロントエンド : Vue.js
AWS(インフラ) : S3, API Gateway, Lambda, DynamoDB

構成

構成はこんな感じです。
DBはDynamoDB、アプリケーションサーバの代わりにlambdaを使ってサーバレスにする。
lambdaプログラムをAPI Gatewayと連携し、APIとして呼べるようにする。
Webサーバのように使えるS3にフロントエンドのプログラムをデプロイする。
スクリーンショット 2020-10-17 16.27.34.png

画面

画面こんな感じです。 
めちゃシンプルですが、自分の尊敬する人/好きな有名人などの情報を記録するアプリを作ります。
(余談:gifって初めて使ったけど便利ですねぇ。)
persons画面イメージ.mov.gif

手順

以下のような手順で作成します。
が、死ぬほど長くなってしまったので、本記事は①②までとしました!!
③〜⑤は、後ほど作成します!カミングスーン!

①DynamoDBテーブルを作成する

1.1 テーブル作成
1.2 カラム作成

②GETメソッド作成(CRUDのR)

Lambda関数作成
API Gateway作成
Vue.js作成

③POSTメソッド作成(CRUDのC)

Lambda関数作成
API Gateway作成
Vue.js作成

④Deleteメソッドの作成(CRUDのD)

Lambda関数作成
API Gateway作成
Vue.js作成

⑤PUTメソッドの作成(CRUDのU)

前提条件

AWSアカウント作成ずみであること
(フルアクセス権限のIAMユーザを作ってそこから操作することをお勧めします。)

① DynamoDBテーブルを作成する

※ここからAWSマネジメントコンソール画面を操作していきますが、
今後、画面のレイアウト、表示項目などが変わる可能性があります!
本記事と、実際の画面が違った場合は、心で感じ取って、うまく進めてください!!

1.1 テーブル作成

AWSコンソールへログインし、DynamoDBの画面から「テーブル作成」を開く
DynamoDB Home

テーブル名(作ろうとしているアプリ名が良いですかね)とプライマリーキーを入力し、
「作成」を押下する
Create Table

1.2 カラム作成

画面左のメニューから「テーブル」をクリック
作成したテーブル名クリック
「項目」タブを開き
「項目の作成」をクリック
Create Columns

「+」をクリック、「Append」をクリック、「String」を選択
Create Columns 2

アプリに必要なカラム名を追加する。
add column name

同様の操作で必要なカラムを全て作成する。
add all columns

それぞれのカラムに適当なデータを入力します。(ドラえもんはPerson?? まぁいいやw)
put data

「保存」ボタンを押下します。
※データを入れてあげないと下のような赤文字のエラーが出ます。。。?
save

これでDBは完成です。 ぅえーい!(・∀・)
DB Table Done

② GETメソッド作成(CRUDのR)

ここからCRUDごとに処理を作っていきます。

2.1 Lambda関数作成

AWSコンソールからlambda画面へ行き、「関数の作成」をクリックする
create lambda button

「一から作成」を選択(デフォルト)、
関数名を入力(今回はgetメソッドなので-getとする)
「Node.js」を選択(デフォルト)
「関数の作成」をクリックする
creating lambda

このような画面に遷移すればOKです。
lambda created

2.1.1 アクセス権限設定(IAMロール)

作成したlambda関数は現時点でDynamoDBにアクセスできません。(上記の作成手順の時に「アクセス権限」がノータッチだった為)
なので、先にそちらの設定をやっちゃいます。

作成したlambda関数画面から「アクセス権限」タブを開き、ロール名をクリックします。
このIAMロールはlambda関数作成時に自動で作られています。
lambda access setting

IAMロール画面が開き、「ポリシーをアタッチします」をクリック
attaching policy to role

検索窓に「dynamo」くらいで検査し、「AmazonDynamoDBFullAccess」を選択する
「ポリシーのアタッチ」をクリックする
attaching policy to role

以下メッセージが出れば、ロール設定完了です。
attaching policy to role

2.1.2 Lambda関数内にGet処理を書く

いよいよlambda関数にgetメソッドを書いていきます。

index.js
const AWS = require('aws-sdk')
const dynamo = new AWS.DynamoDB.DocumentClient()

exports.handler = (event, context, callback) => {
    const httpMethod = event.httpMethod
    const params = {
        // ここで作成したDynamoDBテーブル名を指定
        'TableName': 'lambdaapp-persons'
    }
    dynamo.scan(params, function (err, data) {
        const response = {
            statusCode: 200,
            body: JSON.stringify(data.Items)
        }
        callback(null, response)
    })
};

コードエディタの部分にコードを貼り付け、「Deploy」をクリックする
edit lambda code

2.1.3 Lambda関数をテストする

「テストイベントの選択」プルダウンより「テストイベントの設定」を選択
lambda test

任意のイベント名を入力し、「作成」をクリック
※Getメソッドではパラメータは特にないので、エディタの操作は不要
skip test settings

作成したテストイベントが選択された状態で「テスト」をクリック
test

うまくいくと「実行結果:成功」と表示されます。
test success

結果の詳細を見てみると、DynamoDBに登録したデータが取れているのが分かります。
test success

2.2 API Gateway作成

2.2.1 API Gateway作成

先ほど作成したGetメソッドをフロント側から呼び出せるようにAPI Gatewayを作成します。

API Gateway画面から「API 作成」をクリック
create api

API Typeを選択します。「REST API」の「構築」をクリック
select api type

「新しいAPI」(デフォルト)を選択、
API名を入力(1つのAPIでCRUD全て追加するので、ここではget、postなどは入れていません)、
「APIの作成」をクリック
api name etc

2.2.2 メソッドの作成

作成したAPI Gatewayの画面に遷移します。
「アクション」から「メソッドの作成」をクリック
action create method

プルダウンから「GET」を選択し、チェックマークをクリック
method type get

「GET - セットアップ」画面が現れます。
統合タイプ「lambda関数」を選択し、先ほど作成したlambda関数名を入力
「保存」をクリック
lambda name and save

下記メッセージはOKでOK。 ぇ?w
API to lambda trigger

Lambda関数を見てみると、上記「権限を与える」操作によりAPI Gatewayがトリガーとして追加されます。
lambda has trigger

2.2.3 メソッドをテスト

作成したAPI Gatewayメソッドからlambda関数が実行できるかテストします。

「テスト」をクリック
api gateway test

「テスト」をクリック!!
api gateway test

ボタンの下にテスト結果が表示されます。
ステータス:200であればOK。 bodyの所にDynamoDB作成時に作ったデータが入っていますね。
test result

2.2.4 CORSの有効化

説明しよう! CORSとは!
Cross-Origin Resource Sharing(オリジン間リソース共有)の略で
異なるオリジンとの通信ができるようにブラウザへ指示する仕組みだそうです。
今回の例で言うと、HTMLファイルをS3にアップロードして、S3から発行されたURLでブラウザに表示します。
さらにHTMLファイルの中にAPI GatewayのURLを書いて、メソッドを呼び出しています。
①S3のURLと②API GatewayのURLが異なるオリジンということになり、
それらを同時に扱うためにCORSが必要という事ですね。
参考

「アクション」から「CORSの有効化」をクリック
enable cors

GETメソッドに✅が入っていることを確認し、「CORSを有効にして既存のCORSヘッダーを置換」をクリック
enable cors

「はい、既存の値を置き換えます」をクリック
enable cors

全ての項目に✅が付き、エラーが出なければOK
enable cors

2.2.5 メソッドをデプロイ

「アクション」から「APIのデプロイ」をクリックします。
api deploy

デプロイされるステージはプルダウンから「新しいステージ」を選択
ステージ名に今回は「dev」と入力。※開発用だったらdev、商用だったらprodといった具合に分けるようですね
ステージの説明、デプロイメントの説明の入力は任意
「デプロイ」をクリック
set stage

デプロイが成功すると、APIのURLが表示されます。先ほど入力したステージ名が末尾に入っていますね。
set stage

2.3 Vue.js作成

2.3.1 ローカルでVue.jsを作成

まずはローカル環境にindex.htmlを作成し、vue.jsで書いていきます。
[作成したAPI GatewayのURL]の箇所をCtrl + Fで探して頂き、作成したAPIのURLを書きます。
ソースはGithubでも参照いただけます。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Persons</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
  <div id="app">
    <div class="container mt-5">
      <div class="row">
        <div class="col-md-12">
          <h1>{{ title }}</h1>
          <h1>{{ psersons }}</h1>
          <p>Please add persons whom you like or respect, and describe them in detail.</p>
          <table class="table">
            <thead class="thead-dark">
              <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Description</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="person in persons">
                <td>{{ person.id }}</td>
                <td>{{ person.name }}</td>
                <td>{{ person.description }}</td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    const vue = new Vue({
      el: "#app",
      data: {
        title: 'Persons',
        form: {
          id:'',
          name:'',
          description:''
        },
        persons: '',
        editIndex: -1,
        createFlag: true
      },
      mounted(){
        axios
          .get('[作成したAPI GatewayのURL]')
          .then(response => (this.persons = JSON.parse(response.data.body)))
          .catch(function(error){ 
            alert('Personsデータの取得に失敗しましたっ!\n(・Д・)ナンダッテェ!!' );
            console.log(error);
          });
      }
    });
  </script>
</body>
</html>

HTMLファイルをブラウザで実行すると以下のような画面が表示されます。
(ドラえもんだけだと寂しいので、もう1レコード追加してみました。てかPersonじゃないっすねwww)
ui

2.3.2 作成したVue.jsをS3へアップロード

まずS3バケットを作成します。
S3画面へ飛び、「バケットを作成」をクリック
create bucket

バケット名を入力します。下へスクロール。
create name

「パブリックアクセスを全てブロック」の✅を外し、下2つに✅をつける。
なかなか分かりづらいですが、要するにS3にあるHTMLファアイルをインターネットから参照できるようにしています。
さらに下へスクロール。
S3 access control

バケットのバージョニングは有効にすると、間違ってアップロードした時なんかに
元に戻せたりするみたいですね。お好みでどうぞ。
筆者はデフォルトの「無効」とします。
versioning

さらに下へスクロールし、「バケットを作成」を押下する。
create bucket

正常に作成されました、と言うメッセージが表示されます。
作成したS3バケットをクリック
create bucket success

下図のエリアにhtmlファイルをドラッグ&ドロップする
html drug drop

HTMLファイルが表示されます。下へスクロール
html appeard

「アップロード」をクリック
html appeard

アップロード成功しました。
html appeard

作成したバケットの画面に戻り、
アップロードしたHTMLファイルに✅をつけ、
「アクション」から「公開する」をクリック
koukai suru

「公開する」をクリック
koukai suru

正常に公開されました。とメッセージが表示されますね。
koukai suru

再びバケット画面に戻り、HTMLファイルをクリック
go to its url

「オブジェクト URL」をコピー
url

URLをブラウザで実行して、動作確認
(余談ですが、Qiita執筆中にChromeのアプデが来ましたね。右上の「更新」ってやつ)
url

終わりに

Qiitaを書いてみて

Qiitaを書くこと自体にめちゃめちゃ時間がかかってしまい、
Getメソッドのみになってしまいました!!
画面操作をここまで細かく説明する必要あるのか?という疑問も浮かびましたが、なるべく初学者目線で、をモットーに
そのまま描き続けました。
果たして分かりやすい記事になったのだろうか。。。!!

lambdaについて

今回はシンプルなアプリを作成しましたが、
きっともっと色んなことがlambdaを使ってできるはずなので、
(動画や画像など、様々なデータを暑かったりとか)
引き続き、何かを作りながら知見を深めていこうと思います!!

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

Vue-router基本まとめ 備忘録

Vou-router

VueRouterとは

Webページのルーティングを実装してくれる公式のプラグイン。ユーザーが入力したパスに対してどのようなページ(コンポーネント)を渡すのかをVueRouterによって定義される。

router-rink

  • router-linkコンポーネントはナビゲーションとして使われる
  • リンク先を'to'プロパティに指定
  • デフォルトで<router-link>は<a>タグとして描画される
<p>
  <router-link to="/foo">Go to Foo</router-link>
  <router-link to="/Bar">Go to Bar</router-link>
</p>

router-view

  • ルートとマッチしたコンポーネントがここへ描画される
<router-view></router-view>

Routerの設定

// 1. ルートコンポーネントを定義する
// 他のファイルからインポートすることもできます
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

// 2. ルートをいくつか定義する
// 各ルートは 1 つのコンポーネントとマッピングされる必要がある。
// このコンポーネントは実際の `Vue.extend()`、
// またはコンポーネントオプションのオブジェクトでも構わない
const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
]

// 3. ルーターインスタンスを作成して、ルートオプションを渡す
const router = new VueRouter({
  routes // `routes: routes` の短縮表記
})

// 4. root となるインスタンスを作成してマウントする
// アプリケーション全体がルーターを認知できるように、
// ルーターをインジェクトすることを忘れない。
const app = new Vue({
  router
}).$mount('#app')

  • モジュールシステムを使っている場合 (例: vue-cli 経由で)、Vue と VueRouter をインポートし、Vue.use(VueRouter) を呼び出す必要がある。

image.png

動的ルートマッチング

  • パターンを使って同じコンポーネントにルートをマップする必要がしばしばある。例えば、異なるユーザーIDを持つ場合など。これで /user/foo や /user/bar などの URL 両方とも同じルートにマッチする。

  • 動的セグメントはコロン:を使って表される

const User = {
  template: '<div>User</div>'
}

const router = new VueRouter({
  routes: [
    // コロンで始まる動的セグメント
    { path: '/user/:id', component: User }
  ]
})
  • ルートがマッチした時、動的セグメントの値は全てのコンポーネント内でthis.$router.paramsとして利用可能
const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}

実際の画像:pathの値によって動的に変化する

image.png

パラメーター変更の検知

  • ルートのパラメーターを使う際に特筆すべき点は、ユーザーが /user/foo から /user/bar へ遷移するときに同じコンポーネントインスタンスが再利用されるということ
  • これはコンポーネントのライフサイクルフックが呼ばれないことを意味している
  • 同じコンポーネントでパラメーター変更を検知するためには、 $route オブジェクトを watch する、またはbeforeRouteUpdateナビゲーションガードを使用
const User = {
  template: '...',
  watch: {
    $route (to, from) {
      // ルートの変更の検知...
    }
  }
}
const User = {
  template: '...',
  beforeRouteUpdate (to, from, next) {
    // ルート変更に反応する...
    // next() を呼び出すのを忘れないでください
  }
}

すべてキャッチするルート/404 Not fountルート

通常のパラメータは、/ で区切られた url フラグメントの間にある文字だけにマッチする。何でも一致させたい場合は、アスタリスク(*)を使うことができる.

{
  // 全てにマッチします
  path: '*'
}
{
  // `/user-`から始まる任意のものにマッチします
  path: '/user-*'
}
  • アスタリスク ルートを使用するときは、アスタリスク ルートが最後になるようにルートを正しく順序付けてる。
  • { path: '*' }ルートは、通常クライアントサイドの404ページで使われる

マッチングの優先度

しばしば同じURLで複数のルートがマッチすることがあります。そのようなケースではマッチングの優先度はルートの定義された順番によって決定されます。先に定義されたルートほど優先度が高くなります。

つまり、404のエラーページへのルートは最後におく

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

vueのプロジェクトを「ロリポップ!」へGitHub Actionsをつかって自動デプロイする

はじめに

ロリポップなどのレンタルサーバにvueなどのものを手動でデプロイするのってめんどくさいですよね。
今回は「テスト->ビルド->デプロイ」の一連の流れをGitHub Actionsで自動にやってくれるように設定します。

前提

  • Vueのプロジェクトを作成しておりGitHubで管理している
  • 今回レンサバの例としてつかうのはロリポップ

workflowの作成

vueのプロジェクトのリポジトリの「Actions」タブを開くと以下のようなページになります。
githubactions.png
「Continuous integration workflows」の中にある「Node.js」を選択し、「set up this workflow」を押します。
githubactions2.png
このようなページになったら、好きなファイル名をつけ「Start commit」を押して保存しましょう。

workflowの編集

ワークフローの編集をしていきます。
FTPの接続情報などは、ファイルに書いてしまうと良くないので「GitHub Secrets」にあとで書いていきます。

.github/workflows/hoge.yml
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI

on:
  push:
    branches: [ main ]
-  pull_request:
-   branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
-        node-version: [10.x, 12.x, 14.x]
+        node-version: [12.x]

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
-    - run: npm ci
-    - run: npm run build --if-present
-    - run: npm test
+    - name: npm install        # パッケージをインストール
+      run: npm install
+    - name: test
+      run: npm run test:unit   # unitテストを実行
+    - name: build              # ビルド
+      run: npm run build --if-present
+
+    - name: List output files
+      run: ls                           # ファイルリストを表示
+
+    - name: FTP-Deploy-Action
+      uses: SamKirkland/FTP-Deploy-Action@2.0.0   # FTPを使ってサーバーにDeployするアクションを実行
+      env:                                        
+        FTP_SERVER: ${{ secrets.FTP_SERVER }}     # FTPサーバーのURLを設定
+        FTP_USERNAME: ${{ secrets.FTP_USERNAME }} # FTPのユーザー名を設定
+        FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }} # FTPのパスワードを設定
+        LOCAL_DIR: dist                           # どのディレクトリのデータをアップロードするか
+        REMOTE_DIR: /      # ロリポップ!FTPサーバのどのディレクトリにアップロードするか
+        ARGS: --delete

これで、mainブランチにpushされた際に自分のロリポップのFTPサーバのルートにvueのビルドした成果物が上げられるようになります。

FTP接続情報の設定

「GitHub Secrets」にFTPの接続情報を設定します。
まず、「ロリポップ!」にログインして「ユーザー設定ー>アカウント情報」を開いてください。
「サーバー情報」にある「FTPサーバー」、「FTPアカウント」、「FTPパスワード」をメモってください。
FireShot Capture 009 - ロリポップ!ユーザー専用ページ - アカウント情報 - user.lolipop.jp.png
メモったら、先程のリポジトリに戻って、リポジトリの「Settings」を開きます。
その中の「Secrets」を選択し、「New Repository secrets」を押して以下のものを作成します。FireShot Capture 012 - Secrets - github.com.png

  1. FTP_SERVER(先程メモったFTPサーバーをかく)
  2. FTP_USERNAME(先程メモったFTPアカウントをかく)
  3. FTP_PASSWORD(先程メモったFTPパスワードをかく)

これで、設定はすべて終了です。
このあとからmainブランチにPushされた場合、自動で「テスト・ビルド・デプロイ」が行われるようになっています。
結果や状態を見たい場合は、「Actions」を開くと以下のように表示されると思います。
このようにチェックマークがついていればすべてのjobが正常に終わっています。
3.png

参考にさせていただいた記事など

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

【Rails API + Vue】Active Storageを使って画像をアップロード・表示する

バックエンドはRails、フロントエンドはVueといった構成のときにActive Storageを使って画像をアップロード・表示する方法を、プロジェクトを1から作りながらまとめます
ソースコードはGitHubで公開しています

画像をアップロード・表示する処理の流れをざっくりと

  • Vueで画像を選択して送信するための画面を作る
  • 送信ボタンを押した時、画像をアップロードする処理を行うRails APIを呼び出す
  • Railsは受け取った画像をstorageディレクトリに保存し、保存した画像のURLを返す
  • Vueで画像のURLを受け取り、表示する

Railsプロジェクトを作成する

↓のようなディレクトリ構成で作成していきます

rails-vue-file-uploader-sample
└── backend   # Railsプロジェクト
└── frontend  # Vueプロジェクト

まずはRailsプロジェクトをAPIモードで作成します

$ mkdir rails-vue-file-uploader-sample
$ cd rails-vue-file-uploader-sample
$ rails _6.0_ new backend --api
$ cd backend
$ rails db:create

Active Storageを使えるようにする

$ rails active_storage:install
$ rails db:migrate

これらを実行するとactive_storage_blobsactive_storage_attachmentsという名前の2つのテーブルが作成されます
これらはActiveStorage::BlobActiveStorage::Attachmentの2つのモデルで扱われます

  • ActiveStorage::Blob:アップロードファイルのメタ情報を管理するためのモデル
  • ActiveStorage::Attachment:主となるモデルとActiveStorage::Blobとの中間テーブルに相当するモデル

例えばPostモデルに画像を持たせる場合は次のような関係になります
スクリーンショット 2020-11-15 16.54.33.png

モデルを作成する

titleとimageを属性に持つPostモデルを作成します
imageの型にはattachmentを指定します

$ rails g model post title:string image:attachment
$ rails db:migrate

これらを実行するとpostsテーブルが作成されます
マイグレーションファイルを見てみるとわかるのですが、postsテーブルにimageカラムは作られません
image属性の中身はActiveStorage::Blob及びActiveStorage::Attachmentに保存され、それを参照するようになります

生成されたapp/models/post.rbを見ると、has_one_attached :imageが指定されています
この指定によって画像を参照できるようになります

app/models/post.rb
class Post < ApplicationRecord
  has_one_attached :image
end

コントローラを作成する

$ rails g controller posts
app/controllers/posts.rb
class PostsController < ApplicationController
  def index
    render json: Post.all
  end

  def create
    post = Post.new(post_params)
    if post.save
      render json: post
    else
      render json: post.errors, status: 422
    end
  end

  def destroy
    post = Post.find(params[:id])
    post.destroy!
    render json: post
  end

  private

  def post_params
    params.permit(:title, :image)
  end
end

とりあえず普通に書きます
routesも設定します

config/routes.rb
Rails.application.routes.draw do
  scope :api do
    resources :posts, only: [:index, :create, :destroy]
  end
end

保存したファイルのURLを返すようにする

Postモデルに、紐づいている画像のURLを取得するメソッドを追加します
url_forメソッドを使うためにRails.application.routes.url_helpersをincludeする必要があります

app/models/post.rb
class Post < ApplicationRecord
  include Rails.application.routes.url_helpers

  has_one_attached :image

  def image_url
    # 紐づいている画像のURLを取得する
    image.attached? ? url_for(image) : nil
  end
end

アクションで返すJSONにimage_urlの値を追加します

app/controllers/posts.rb
class PostsController < ApplicationController
  def index
    render json: Post.all, methods: [:image_url]  # ここを変更
  end

  def create
    post = Post.new(post_params)
    if post.save
      render json: post, methods: [:image_url]  # ここを変更
    else
      render json: post.errors, status: 422
    end
  end

  def destroy
    post = Post.find(params[:id])
    post.destroy!
    render json: post
  end

  private

  def post_params
    params.permit(:title, :image)
  end
end

画像のURLを取得するためにconfig/environments/development.rbに次の設定を追加する必要があります

config/environments/development.rb
Rails.application.configure do
  ...

  # これを追加
  Rails.application.routes.default_url_options[:host] = 'localhost'
  Rails.application.routes.default_url_options[:port] = 3000
end

VueとのAPI通信をするためにCORSの設定をしておきます
Gemfileのgem 'rack-cors'のコメントを外してbundle installし、config/initializers/cors.rbを次のように書きます

config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://localhost:8080'

    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

Vueプロジェクトを作成する

ここからはVueを書いていきます
まずはルートディレクトリに戻ってVueプロジェクトを作成します

$ cd rails-vue-file-uploader-sample
$ vue create frontend
$ cd frontend

vue createの設定は以下のように選択しました

? Please pick a preset: Manually select features
? Check the features needed for your project: Vuex, Linter
? Pick a linter / formatter config: Prettier
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No

Vuexストアを作成する

Vuexを次のように書きます
axiosを使用するのでインストールしておきます

$ npm install --save axios
src/store/modules/posts.js
import axios from "axios";

const apiUrlBase = "http://localhost:3000/api/posts";
const headers = { "Content-Type": "multipart/form-data" };

const state = {
  posts: []
};

const getters = {
  posts: state => state.posts.sort((a, b) => b.id - a.id)
};

const mutations = {
  setPosts: (state, posts) => (state.posts = posts),
  appendPost: (state, post) => (state.posts = [...state.posts, post]),
  removePost: (state, id) =>
    (state.posts = state.posts.filter(post => post.id !== id))
};

const actions = {
  async fetchPosts({ commit }) {
    try {
      const response = await axios.get(`${apiUrlBase}`);
      commit("setPosts", response.data);
    } catch (e) {
      console.error(e);
    }
  },
  async createPost({ commit }, post) {
    try {
      const response = await axios.post(`${apiUrlBase}`, post, headers);
      commit("appendPost", response.data);
    } catch (e) {
      console.error(e);
    }
  },
  async deletePost({ commit }, id) {
    try {
      axios.delete(`${apiUrlBase}/${id}`);
      commit("removePost", id);
    } catch (e) {
      console.error(e);
    }
  }
};

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
};
src/store/index.js
import Vue from "vue";
import Vuex from "vuex";
import posts from "./modules/posts";

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    posts
  }
});

画像をアップロードするコンポーネントを作成する

画像を選択して送信するフォームを表示するためのsrc/components/PostForm.vueを作成します

src/components/PostForm.vue
<template>
  <div>
    <h2>PostForm</h2>
    <section>
      <label for="title">title: </label>
      <input type="text" name="title" v-model="title" placeholder="title" />
    </section>
    <section>
      <label for="image">image: </label>
      <input type="file" id="image" name="image" accept="image/png,image/jpeg" @change="setImage" />
    </section>
    <section>
      <button type="submit" @click="upload" :disabled="title === ''">upload</button>
    </section>
  </div>
</template>

<script>
import { mapActions } from "vuex";

export default {
  name: "PostForm",
  data: () => ({
    title: "",
    imageFile: null
  }),
  methods: {
    ...mapActions("posts", ["createPost"]),
    setImage(e) {
      e.preventDefault();
      this.imageFile = e.target.files[0];
    },
    async upload() {
      let formData = new FormData();
      formData.append("title", this.title);
      if (this.imageFile !== null) {
        formData.append("image", this.imageFile);
      }
      this.createPost(formData);
      this.resetForm();
    },
    resetForm() {
      this.title = "";
      this.imageFile = null;
    }
  }
};
</script>

選択された画像はe.target.filesで取り出すことができます
POSTリクエストを送信するときはFormDataに必要な値をappendしたものをパラメータとして指定します

画像を表示するコンポーネントを作成する

保存されている画像を取得して表示するためのsrc/components/PostList.vueを作成します

src/components/PostList.vue
<template>
  <div>
    <h2>PostList</h2>
    <div v-for="post in posts" :key="post.id">
      <h3>{{ post.title }}</h3>
      <img :src="post.image_url" />
      <br />
      <button type="submit" @click="del(post.id)">delete</button>
    </div>
  </div>
</template>

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

export default {
  name: "PostList",
  created() {
    this.fetchPosts();
  },
  computed: {
    ...mapGetters("posts", ["posts"])
  },
  methods: {
    ...mapActions("posts", ["fetchPosts", "deletePost"]),
    del(id) {
      this.deletePost(id);
    }
  }
};
</script>

<img :src="post.image_url" />でsrcに取得したURLを指定して表示させます

最後にApp.vueを編集してコンポーネントを表示します

src/App.vue
<template>
  <div id="app">
    <PostForm />
    <PostList />
  </div>
</template>

<script>
import PostForm from "./components/PostForm.vue";
import PostList from "./components/PostList.vue";

export default {
  name: "App",
  components: {
    PostForm,
    PostList
  }
};
</script>

完成

画像を選択してアップロードボタンを押すと、画像が保存されて表示されます
画像はbackend/storageディレクトリにバイナリ形式で保存されます

スクリーンショット 2020-11-15 17.43.33.png

ソースコードはGitHubで公開しています
参考になれば嬉しいです
https://github.com/youichiro/rails-vue-file-uploader-sample

参考

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

Error: EPERM: operation not permitted, open '/usr/src/app/src/App.vue'

npm run serve をした際に operation not permitted が出た。

Error: EPERM: operation not permitted, open '/usr/src/app/src/App.vue'

vue-cliアプリをビルドしようとすると、エラーが発生します。依存関係のインストールとアプリの提供は正常に機能していました。

結論

npm install -g @vue/cli --cache /tmp/empty-cache

上記コマンドで解決。

環境

  • Docker
  • Vue
  • Vue CLI
$ npm --version
$ @vue/cli 4.5.8

起きた原因

スクリーンショット 2020-11-15 13.45.26.png

Vue CLI Issue をみる限り、みなさん npm cache clean force はしているようですね。

自分が今回起きたのは 「build に時間がかかるな」 と思ったので 最近していなかった再起動をしました。
↑ 怒らないでください笑

そうすると 今回のErrorが出ました。
理由は正直わかりません。もしわかる方がいらっしゃいましたら、ご教授頂けますと幸いです。

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

Nuxt.js + Firebaseで簡易書籍検索アプリを2日で作った話

はじめに

社会人2年目のエンジニアです。普段はC#、TypeScriptを使ったWebアプリの開発やXamarin.Formsを使ったモバイル開発を行っています。
普段の業務では全く触る機会はありませんが、モダンなフロントエンドフレームワークに興味があったので
勉強がてらNuxt.jsとFirebaseで作った「Bookle」という簡易書籍検索アプリを作った話をまとめます。
期間としては、2日(16h)くらいで作成できました。

プロジェクト作成

プロジェクトの作成にはnpxを利用しました。

$ npx create-nuxt-app <project-name>

上記コマンドを実行すると、いくつか聞かれるのですが、今回は以下のように選択しました。

質問 選択した項目
Programming language TypeScript
Package manager Npm
UI framework Vuetify.js
Nuxt.js modules Axios
Linting tools ESLint, Prettier
Testing framework None
Rendering mode Single Page App
Deployment target Server (Node.js hosting)
Development tools 選択なし
Continuous integration None
Version control system Git

Vuexの導入

VuexとTypeScriptを組み合わせて使いました。
VuexをTypeScriptで書く場合様々な方法があるのですが、今回はvuex-module-decoratorsを使いました。
VuexでTypeScriptを用いる際にちょっとつまずいたので、ちょっとだけサンプルコードを載せます。

以下のサイトを参考にして作成しました。
https://github.com/championswimmer/vuex-module-decorators

ディレクトリ構造はこのような感じです

├── store
|    ├── Book
|    |     └── Interface.ts
|    ├── book.ts
|    └── index.ts
|
├── utils
     └── store-accessor.ts
book.ts
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators';
import { GoogleBookItem } from './Books/Interface';

@Module({ stateFactory: true, namespaced: true, name: 'books' })
export default class Books extends VuexModule {
    // state
    private books: GoogleBookItem[] = [];

    // Getter
    public get Books(): GoogleBookItem[] {
        return this.books;
    }

    @Mutation
    public SET_BOOKS(books: GoogleBookItem[]) {
        this.books = books;
    }

    @Mutation
    public SET_BOOK(book: GoogleBookItem) {
        this.books.push(book);
    }

    @Action({})
    public async setBooks(books: GoogleBookItem[]): Promise<void> {
        // 省略
        this.SET_BOOKS(books);
    }

    @Action({})
    public async setBook(book: GoogleBookItem): Promise<void> {
        this.SET_BOOK(book);
    }
}
index.ts
import { Store } from 'vuex';
import { initializeStores } from '~/utils/store-accessor';

const initializer = (store: Store<any>) => initializeStores(store);

export const plugins = [initializer]
export * from '~/utils/store-accessor'
store-accessor.ts
import { Store } from 'vuex';
import { getModule } from 'vuex-module-decorators';
import Books from '@/store/books'

let bookStore: Books;

function initializeStores(store: Store<any>): void {
    bookStore = getModule(Books, store);
}

export { initializeStores, bookStore }

上記のような手順を踏むことで別途初期化せずにつかうことができます。
またTypeScriptを使用しているので、TypeSafeにストアへアクセスすることができます。

(使用例)

index.vue
<template>
  <!-- 省略 -->
</template>
<script lang="ts">
import Vue from 'vue'
import { bookStore } from '@/store'
import axios from '@nuxtjs/axios'
import { GoogleBookItem } from '~/store/Books/Interface'

export default Vue.extend({
  // 省略
  methods: {
    async searchBooks() {
      await this.$axios.$get('https://hogehoge/api')
        .then((x: GoogleBookItem) => {
              bookStore.setBooks(x);
        })
        .catch(error => {
              console.error(error);
        });
    }
  }
})

GoogleBooksApiを叩いて書籍情報を取得

書籍情報は様々なAPIがあるそうですが、今回はGoogle Books APIを使用しました。

Google Books API

基本的な使い方はこんな感じです。
this.$axios.$get('https://www.googleapis.com/books/v1/volumes?q=title:search value')

例えば以下のようにしてAPIを叩くことができます。

sample.ts
this.$axios.$get('https://www.googleapis.com/books/v1/volumes?q=title:nuxt&maxResults=30')

デフォルトだと10件しか取得できないですが、maxResultsをつけて件数の上限を増やすこともできます。(0 ~ 40で指定可能)

Firebaseを利用してデプロイ

HostingにはFirebase hostingを利用しました。
無料で利用できて、比較的わかりやすかったので利用しました。

完成したアプリ

アプリ
Bookle

トップ画面

image.png

検索

タイトルでの検索しかできませんが、入力後Enterまたは検索ボタンを押すと関連書籍が出てきます。
image.png

詳細ページ

本のカードを選択すると、詳細ページに遷移します。
タイトルや著者が閲覧できます。(画像が荒い・・・)
image.png

まとめ

NuxtとFirebaseを使うと比較的簡単にアプリを作ることができるんだなと感じました。
Vue.jsの勉強が必要だったりしますが、JSの知識があればそこまで難しくない気がしました。
ただVuexはちょっとムズイ・・・

参考資料

Vueドキュメント

今回は使ってませんが、Vue 3.0も気になる
https://jp.vuejs.org/v2/guide/

Firebaseドキュメント

日本語なのでわかりやすい
https://firebase.google.com/docs/?hl=ja

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

Vue.jsでカスタムなページタイトルを設定する

はじめに

ちょっとの間悩んでた表題の問題が他人のコード見てたらふと解決したので軽くまとめていきます。Vue.jsでページタイトルをデフォルトのアプリ名から変更する方法です。

手順

まず、Vue.jsでページタイトルを規定してるコードはpublic/index.html内にあります。

public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title> <!--ここですここ、ここ-->
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

しかし、ここは変数で指定してありますしあまりこのファイルを弄るのはよくないようです。なのでどこでこの変数を指定するのか書いていきます。
まず、rootフォルダにvue.config.jsというJavaScriptファイルを作ります。
そしてそこに次のように入力してください。

vue.config.js
module.exports = {
    pages: {
        index: {
            entry: 'src/main.js', // ここは変えないで
            title: 'ページタイトル', // 好きな文字列をいれてください
        }
    }
}

そうしてビルドし直すと無事ページタイトルが変更されてると思います。

最後に

サイト自体のタイトルの後にページのタイトルをいれる方法はそのうち実装しようと思うので成功したら追記します。

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

vue.jsで湯婆婆を実装してみた(はやりに乗っかてみた)

初めに

原作者様 Javaで湯婆婆を実装してみる

はやりに乗っかってみました
これで湯屋から契約作業のwebページ作成案件が来ても安心です

湯婆婆コード

VueCLI version2.6.12を使用しています
設定はデフォルトです

yubaba.vue
<template>
  <div class="yubaba">
    <div>契約書だ。そこに名前を書きな!</div>
    <input type="text" v-model="name"><br><br>
    <button v-on:click="outputName(),createNewName()" id="keiyakubtn">契約する</button>
    <div>{{displayName}}</div><br>
    <div>{{newName}}</div>
    <div v-if="!name">{{allClear()}}</div>
    <button v-on:click="contractCancellation">契約を破棄する</button>
  </div>
</template>

<script>
export default {
  data(){
    return {
      name: '',
      displayName:'',
      newName: '',
      setTimeID: '',
    };
  },
  methods: {
    outputName() {
      if (!this.name) {
        // フォームが空白だったらアラートを表示する
        alert('名前を書きな!!')
      } else if (this.name) {
        // フォームに入力された文字列を表示する
        this.displayName = 'ふん。「'+this.name+'」っていうのかい。贅沢な名前だねぇ'
      }
    },
    createNewName() {
      // this.newName = this.name.substring(0,1)
      // 取得した文字列を配列に変換
      const displayNameArray = this.name.split('')
      // 取得した配列を表示
      // clearTimeout(this.setTimeID)
      this.setTimeID = setTimeout(() => {
        const randomName = displayNameArray[Math.floor(Math.random() * this.name.length)]
        this.newName = 'お前の名前は「'+randomName +'」だよ。いいかい「'+randomName+'」だよ。わかったら返事をするんだ!!'
      },3000)
    },
    allClear() {
      // フォームから要素が消えたら
      if (!this.name) {
        // すべてを空白にする
        this.displayName = ''
        this.newName = ''
      }
    },
    contractCancellation() {
      alert('契約破棄')
      this.name = '';
      this.displayName = ''
      this.newName = ''
      for(let i = 0; i < 1000; i++) {
        document.write('  わたしゃ残念だよ  ')
      }
    }
  },
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
#keiyakubtn {
  background-color: red;
  color: white;
  width: 4cm;
  height: 4cm;
}
</style>

実行画面

実行画面 (2).png

ちょっとした説明

以下使用した変数たちです

name: '',
displayName:'',
newName: '',
setTimeID: '',


契約書ページ

以下のHTMLコードはただ単にボタンやフォームの表示、スクリプトの呼び出しなどしています。

<template>
  <div class="yubaba">
    <div>契約書だ。そこに名前を書きな!</div>
    <input type="text" v-model="name"><br><br>
    <button v-on:click="outputName(),createNewName()" id="keiyakubtn">契約する</button>
    <div>{{displayName}}</div><br>
    <div>{{newName}}</div>
    <div v-if="!name">{{allClear()}}</div>
    <button v-on:click="contractCancellation">契約を破棄する</button>
  </div>
</template>


契約処理の実装

以下のコードでは、なんちゃって空白処理と奪われる前の名前表示を実装をしています。
名前が入力されなかったらアラートを表示します。
名前を入力したら湯屋の一員として一歩前進できます!

    outputName() {
      if (!this.name) {
        // フォームが空白だったらアラートを表示する
        alert('名前を書きな!!')
      } else if (this.name) {
        // フォームに入力された文字列を表示する
        this.displayName = 'ふん。「'+this.name+'」っていうのかい。贅沢な名前だねぇ'
      }    


名前を奪われます

以下のコードでは名前を奪う処理を実装しました。
入力された名前を配列形式に変換してからからランダムで一文字選びそれを新しい名前にします。

 createNewName() {
      // 取得した文字列を配列に変換
      const displayNameArray = this.name.split('')
      // 取得した配列を表示
      this.setTimeID = setTimeout(() => {
        const randomName = displayNameArray[Math.floor(Math.random() * this.name.length)]
        this.newName = 'お前の名前は「'+randomName +'」だよ。いいかい「'+randomName+'」だよ。わかったら返事をするんだ!!'
      },3000)
    }



見たまんまです。フォームが空白になると要素が消えます。
作成した理由はとくにありませんw

allClear() {
      // フォームから要素が消えたら
      if (!this.name) {
        // すべてを空白にする
        this.displayName = ''
        this.newName = ''
      }
    }


おばばのやさしさ

湯婆婆からの救済処置です。
契約破棄ボタンのスクリプトです。
破棄したら怖いことになります。

contractCancellation() {
      alert('契約破棄')
      this.name = '';
      this.displayName = ''
      this.newName = ''
      for(let i = 0; i < 1000; i++) {
        document.write('  わたしゃ残念だよ  ')
      }
    }
  }

説明なんてなかったんや...

最後に

今回初めての投稿です。目を通してくれた方がいましたらとてもうれしいです。
ブログ的なもの自体初めての経験なのでとても読みにくいと思います。
勉強中なのでご容赦いただきたいです。。。
気が向いたらこれからも、はやりに乗っかってみたり、自分でネタ的なものを書こうかなとおもいます
趣味でコーディングの勉強をしている学生なのでいろいろと適当です。
添削やコメントなど頂けましたら喜びます。
拙い文章でしたが最後まで見ていただきありがとうございました。

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