モーダル機能と学ぶ(transition,props,emit)

Vueの4つの特徴(コンポーネントトランジション、拡張性、リアクティブネス)の一つで

あるトランジションについて、モーダルの実装を通して学習していきたいと思う。

transitionって何?

Vue は、transition ラッパーコンポーネントを提供しています。このコンポーネントは、次のコンテキストにある要素やコンポーネントに entering/leaving トランジションを追加することを可能にします:

動きをつけたい箇所をtransitionで囲むってことなのかな?

そんでもって、動きをつけるかどうかは、v-ifを使って表すこともあるらしい。

v-ifに関して、

trueの時は、cssで定義したenter-active・leave-active、

falseの時はleave-to・enter

になる。

では、モーダルを使って考えてみる。

Enter/Leave とトランジション一覧 - Vue.js

どうやって使うの?

index.html

<div id="demo">
  <button v-on:click="show = !show">
    Toggle
  </button>
  <transition name="fade">
    <p v-if="show">hello</p>
  </transition>
</div>

v-on:clickで、showと!showの切り替えを行なっている。

v-onを使うとイベントハンドリングができるらしい。

つまり、Toggleをclickすると、showと!showの切り替えが行われるということになる。

ほんでもって、transition内にイベントの対象を記述している。

もしshowの場合、helloはデフォルトの状態のままで、!showだとcssのfade-enter-to

,fade-leave-toが適用される。(cssの内容に関しては後述する)

 #main.js
new Vue({
  el: '#demo',
  data: {
    show: true
  }
})

今回は、v-if = showがtrueだからhelloがデフォルトで表示されていることを指している。

デフォルトでは、showがtrueになっている。

stylesheet.css

.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}

この、enterやleaveというのは、トランジションクラスというらしい。

下記に引用文献を貼っておく。

これらは、enter/leave トランジションのために適用される 6 つのクラスです。

  1. v-enter: enter の開始状態。要素が挿入される前に適用され、要素が挿入された 1 フレーム後に削除されます。
  2. v-enter-active: enter の活性状態。トランジションに入るフェーズ中に適用されます。要素が挿入される前に追加され、トランジション/アニメーションが終了すると削除されます。このクラスは、トランジションの開始に対して、期間、遅延、およびイージングカーブを定義するために使用できます。
  3. v-enter-toバージョン 2.1.8 以降でのみ利用可能です。 enter の終了状態。要素が挿入された 1 フレーム後に追加され (同時に v-enter が削除されます)、トランジション/アニメーションが終了すると削除されます。
  4. v-leave: leave の開始状態。トランジションの終了がトリガされるとき、直ちに追加され、1フレーム後に削除されます。
  5. v-leave-active: leave の活性状態。トランジションが終わるフェーズ中に適用されます。leave トランジションがトリガされるとき、直ちに追加され、トランジション/アニメーションが終了すると削除されます。このクラスは、トランジションの終了に対して、期間、遅延、およびイージングカーブを定義するために使用できます。
  6. v-leave-toバージョン 2.1.8 以降でのみ利用可能です。 leave の終了状態。leave トランジションがトリガされた 1 フレーム後に追加され (同時に v-leave が削除されます)、トランジション/アニメーションが終了すると削除されます。

vに、transition name="〇〇"の〇〇部分を入れた状態で使用するみたいだ。

今回の例だと、fadeを入れているから、fade-enterやfade-leave-toと表している。

成果物

Image from Gyazo

props

コンポーネントから子コンポーネントに値を渡したい時に使う

props

  • 型: Array<string> | Object

  • 詳細:

    コンポーネントからデータを受け取るためにエクスポートされた属性のリスト/ハッシュです。シンプルな配列ベースの構文、そして型チェック、カスタム検証そしてデフォルト値などの高度な構成を可能とする配列ベースの代わりとなるオブジェクトベースの構文があります。

親⇨子

使用例

// シンプルな構文
Vue.component('props-demo-simple', {
  props: ['size', 'myMessage']
})

// バリデーション付きのオブジェクト構文
Vue.component('props-demo-advanced', {
  props: {
    // 単なる型チェック
    height: Number,
    // 型チェックとその他のバリデーション
    age: {
      type: Number,
      default: 0,
      required: true,
      validator: function (value) {
        return value >= 0
      }
    }
  }
})

v-for

繰り返し処理を記載する。

idがblog-post-demo

Postテーブルのデータで、idが1~3の各タイトル表示させたい。

その場合、script内に下記記述をする。

new Vue({
  el: '#blog-post-demo',
  data: {
    posts: [
      { id: 1, title: 'My journey with Vue' },
      { id: 2, title: 'Blogging with Vue' },
      { id: 3, title: 'Why Vue is so fun' }
    ]
  }
})

template内に下記記述をする。


<div id="blog-post-demo">
	<blog-post
	  v-for="post in posts"
	  v-bind:key="post.id"
	  v-bind:title="post.title"
	></blog-post>
</div>

すると、下記のようなアウトプットが得られる

My journey with Vue
Blogging with Vue
Why Vue is so fun

コンポーネントの基本 - Vue.js

プロパティ - Vue.js

emitって何?

コンポーネントの動きを親コンポーネントが察知するためのメソッド。

子⇨親

vm.$emit( eventName, […args] )

  • 引数:

    • {string} eventName
    • [...args]
  • 使用方法:

    現在の vm 上のカスタムイベントを監視します。イベントは vm.$emit によってトリガすることができます。それらのイベントトリガを行うメソッドに渡した追加の引数は、コールバックがすべて受け取ります。

  • 例:

    vm.$on('test', function (msg) {
      console.log(msg)
    })
    vm.$emit('test', 'hi')
    // => "hi"
    

$emitと$onはセットで使用する。

イベントと$emitを一緒に使うこともできる

$emit をイベント名のみと共に使う場合:

Vue.component('welcome-button', {
  template: `
    <button v-on:click="$emit('welcome')">
      Click me to be welcomed
    </button>
  `
})

welcome-buttonというコンポーネントを作った。template:以下を保持している。その保持している部品の中には、クリックされるとwelcomeを発火するbuttonが格納されている。

<div id="emit-example-simple">
  <welcome-button v-on:welcome="sayHi"></welcome-button>
</div>

HTMLにemit-example-simpleというタグをつけたdivをかく。

importしたwelcome-buttonコンポーネントにクリックするとwelcomeが実行されるメソッドをかく。

new Vue({
  el: '#emit-example-simple',
  methods: {
    sayHi: function () {
      alert('Hi!')
    }
  }
})

Vueインスタンスの中に、sayHiファンクションを書く。

まとめ

コンポーネント分割を行う際に片方のコンポーネントがもう片方のコンポーネントを取り込み(import)する時、importする側を「親(コンポーネント)」と呼び、される側を「子(コンポーネント)」と呼びます。

(参考文献: https://recruit.cct-inc.co.jp/tecblog/vue-js/vue-emit-props/)

propsと$emitでデータを引き渡す - Qiita

API - Vue.js

カスタムイベント - Vue.js

コンポーネントの基本 - Vue.js

API - Vue.js

VueでAPIを叩く(axios,fetch())

脳みそをオンプレミスではなくクラウドに移行したいことスバルです。

前々から気になっていたAPIcurlを入力するとjsonが返ってきて、それを読み込んでviewにjsonの中身を表示させるものかな?くらいの認識だから、もっと体型的に学んでいこうと思う。

それでは、VueでAPIを叩く(axios,fetch())を深掘っていきましょう。

APIとは

アプリと外界を繋ぐ中継役のこと。認証と認可でも出てきた。

アプリケーションからリクエストを送ったら、サーバーからjson形式データが返ってくる。

この送る場所と返ってくる場所の交流地点?薩摩藩長州藩の仲介役だった坂本龍馬的な?

つまりAPIとは坂本龍馬

VueでAPIを叩くには?

APIを叩く用の外部リソースであるaxiosもしくは、外部APIであるFetch APIを使用する。

axiosとは

公式によると、下記のポイントが特徴とされている。

XMLを使っている

  • Make http requests from node.js

node.jsからhttpリクエストを送っている

  • Supports the Promise API
  • Intercept request and response, Transform request and response data

リクエストとレスポンスの中継地点として双方のデータを変換している

  • Cancel requests, Automatic transforms for JSON data

リクエストをキャンセルすることも、JSONデータを自動的に変換することもできる。

  • Client side support for protecting against XSRF

そして、クロスサイトリクエストフォージェリから守ることができる。

一般的なアプローチは Promise ベースの HTTP クライアントの axios を使うことです。

APIを叩くときにaxiosがインターフェイスになってくれるみたい。

実際に使ってみる

yarn add axios
yarn add v1.22.10
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
warning " > bootstrap@4.0.0-beta" has unmet peer dependency "jquery@>=3.0.0".
warning " > bootstrap@4.0.0-beta" has unmet peer dependency "popper.js@^1.11.0".
warning " > vue-loader@15.9.8" has unmet peer dependency "css-loader@*".
warning " > vue-loader@15.9.8" has unmet peer dependency "webpack@^3.0.0 || ^4.1.0 || ^5.0.0-0".
warning " > webpack-dev-server@3.11.2" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning "webpack-dev-server > webpack-dev-middleware@3.7.3" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
warning Your current version of Yarn is out of date. The latest version is "1.22.15", while you're on "1.22.10".
info To upgrade, run the following command:
$ curl --compressed -o- -L <https://yarnpkg.com/install.sh> | bash
success Saved 1 new dependency.
info Direct dependencies
└─ axios@0.22.0
info All dependencies
└─ axios@0.22.0
✨  Done in 7.41s.

axiosをmain.jsで読み込む。

# packs/main.js 
import axios from '../plugins/axios'

Vue.prototype.$axios = axios

plugins/axiosにaxiosの取得方法を記載しておく

import axios from 'axios'

const axiosInstance = axios.create({
  baseURL: 'api'
})

export default axiosInstance

ライフサイクルフックとメソッドを活用してAPI通信をする。

#pages/task/index.vue
<script>
export default {
  name: "TaskIndex",
  data() {
    return {
   tasks: []
    }

  },

  created() {
    this.fetchTasks();
  },

  methods: {
    fetchTasks() {
      this.$axios.get("tasks") #tasksをgetメソッドで取得

        .then(res => this.tasks = res.data) #レスポンスデータをthis.tasksに格納
        .catch(err => console.log(err.status)); #エラー文処理
      ]
    }
  }
}
</script>

これでaxiosを使ったAPI通信の準備ができたみたいだ。

ちなみに、、

ライフサイクルとは

Vueインスタンスが生成されてから破棄されるまでの流れ

1.生成・初期化

  • Vueインスタンス生成後に初期化処理を行う。
  • リアクティブデータ(data)の初期化
    • dataの更新が画面の表示と同期されるようになる。
    • 対応関数:beforeCreate,created

created

APIを呼んでサーバなどからのデータ取得処理を書くことが多い。

他には、DOMへのマウントが完了して画面が描画されるまでのローディングの表示処理もここで書いたりする。

2.マウント

  • コンポーネントをDOMにマウント(VueインスタンスとDOMが紐付け)する。
  • Vueインスタンスelオプションが指定されているかを確認する。
    • 指定がある場合:次のフェーズへ移行する。
    • 指定がない場合:mount関数が実行されたタイミングで、次のフェーズに移行する。
  • templateオプションを持っているかを確認。
    • 持っている場合:templaterender関数にコンパイルされる。
    • 持っていない場合:elオプションで定義されたDOMをtemplateとしてコンパイルする。
  • マウント(インスタンスelementを作成し、elオプションに置換)する。
  • 対応関数:beforeMount,mounted

3.データ/画面の更新

  • 画面が表示されている状態。
  • データが変更されると、render関数が実行され再描画が行われる。
  • 対応関数:beforeUpdate,updated

4.インスタンスの破棄

  • インスタンスは画面遷移などで描画されなくなるタイミングで破棄される。
  • 対応関数:beforeDestroy,destroyed

(引用: https://qiita.com/KWS_0901/items/5105677462f69f197ad2)

Fetch API

Fetch APIとは、XMLHttpRequestと同じでHTTPリクエストを発行する APIですが、XMLHttpRequestよりシンプルでモダンな APIです。

Promiseを返すらしい。

リクエストとレスポンスの流れ?を作るときは、async/awaitを使って書く方法と、thenを使って書く方法があるらしい。

Promiseとは

Promise オブジェクトは非同期処理の最終的な完了処理 (もしくは失敗) およびその結果の値を表現します。

Promise インターフェイスは作成時点では分からなくてもよい値へのプロキシです。Promise を用いることで、非同期アクションが最終的に成功した時の値や失敗した時の理由に対するハンドラーを関連付けることができます。これにより、非同期メソッドは、最終的な値をすぐに返す代わりに、未来のある時点で値を持つ Promise を返すことで、同期メソッドと同じように値を返すことができるようになります。

※参考:Promise - JavaScript | MDN

非同期処理の完了結果を返してくれる。

async/await構文

async: 非同期

await: 待つ

async function 宣言は、 非同期関数 — AsyncFunction オブジェクトである関数を定義します。非同期関数はイベントループを介して他のコードとは別に実行され、結果として暗黙の Promise を返します。

※参考:async function - JavaScript | MDN

await 演算子は、async function によって Promise が返されるのを待機するために使用します。

※参考:await - JavaScript | MDN

//json読み込み
async function hoge () {
 const res = await fetch(url);
  const json = await res.json();
 // 処理 json.xxxx〜
}

  • 関数のfunction宣言の前にasyncを書いて非同期(async)関数であることを宣言
  • 変数resawaitを書くことで非同期通信が終わった後にfetch()メソッド(引数はurl)を実行。
  • 変数jsonawaitを書くことで非同期通信が終わった後にres.json()を実行
  • resjsonが終わったら次の処理を実行する

then()

then() メソッドは Promise を返します。最大2つの引数、 Promise が成功した場合と失敗した場合のコールバック関数を取ります。

※参考:Promise.prototype.then() - JavaScript | MDN

fetch(url).then(function(res) { ・・・①②
  return res.json(); ・・・③
}).then(function(json) { ・・・④
  //処理 json.xxxx〜
});

①fetch()メソッドを実行。引数はAPIのURL

②then()メソッドで次の処理をつなぐ。引数は無名関数でその引数はレスポンスのres

③urlのレスポンスresのjson()メソッドを実行し、結果(jsonデータ)をreturnで返す

④then()で次の処理を繋ぐ。引数は無名関数でその引数はjson

所感

一気に詰め込みすぎて脳内のメモリがショートしそう。

脳みそをオンプレミスじゃなくてクラウドに置きたい。

WindowOrWorkerGlobalScope.fetch() - Web API | MDN

【Vue.js】Vue CLIでaxiosを使う方法 - Qiita

axios を利用した API の使用 - Vue.js

RDBMSとNoSQLを学ぶ。

データベースの違いがわからない。rails newする時のデータベースって何がいいの、、

分からないのなら調べちゃおう。

という事で、今回は個人開発によく使われているMySQLPostgreSQL特徴についてまとめていきたい。

データベースとは

データベースとは「構造化した情報の集合体 」であり、データベース管理システム(DBMS)によって管理されます。データベースは構造によって主に「階層型」「ネットワーク型」「リレーショナル型」に分けられます。

今まで学んできたのは、RDBMSというリレーショナルデータベースだった。

RDBMSでは、正規化されたデータベースを関連づけるために、主キーと外部キーという概念が使われている。

Oracle Databaseとは?世界シェアNo.1のRDBMS!

それぞれの特徴は?

MySQL

高速で信頼性が高く、汎用的なリレーショナルデータベース管理システム

世界シェアがOracle Databaseに続いてNo.2のRDBMS

高い堅牢性と安全性に定評がある。

世界で多く使われているということは、それだけ文献や知見がネットに転がっていることを暗に示唆しているので、自身でアプリを作成する時にエラーと対峙しても大抵先人がエラーの解決方法をTeratailやSack Overflowに乗っけているそう。

だから、個人開発PFに使われることが多い。

Postgresql

複雑で大量のデータ操作を行うための最適なソリューション。WEBアプリケーションと相性がいい。

オブジェクトリレーショナルなので高度な同時実行性を持ち、NoSQLをサポートしている。

MySQL(RDBMS)は1台のサーバーで設計されているのに対し、PostgreSQL(NoSQL)は処理能力を向上させるために対応するサーバーの数を増やす際のスケールアウトに対応している。なので、大量データ操作に強い。

https://pgdash.io/blog/postgres-features.html

NoSQLって?

Not only SQLの略。関係データベース管理システム (RDBMS) 以外のデータベース管理システムを指すおおまかな分類語

下記のような特徴を持つ

・キーバリュー型:キーとバリューのみのシンプルな組み合わせのモデル

・カラム指向型:キーバリュー型にカラムの概念をもたせたモデル

・ドキュメント指向型:JSONXML形式で記述されたドキュメントの形で管理するモデル

・グラフ指向型:データとデータ間のつながりを管理するモデル

ちなみにRedisはNoSQL。

NoSQLの種類

NoSQLは下記のように分類される。

  • どのような形でデータを持つか (データモデル)
  • どのように分散してデータを持つか (アーキテクチャ)

データモデル

NoSQLのデータモデルには大別して次の4つがある。

  • キー・バリュー型
    • Key : Valueを単位としてデータを格納する。
    • シンプルで応答が早い。
  • カラム指向型
    • 一般の行指向型DBと同様に表の構造を持ちつつ、カラム単位でデータを保持する。
    • 行指向では苦手な列単位の大量集計、大量更新が得意。
  • グラフ型
    • ノード、リレーションシップ、プロパティによって定まるデータを単位とし、全体でグラフを形成する。
    • グラフ型の名前の通り、Facebookの知り合い機能等で有効に利用できる。
  • ドキュメント指向型
    • JSONXMLのような構造を持ったドキュメントを単位としてデータを格納する。
    • スキーマレスで格納できる。

アーキテクチャ

アーキテクチャは次の3種類。

  • マスタ型

    • マスタ型では分散データベースクラスタを形成するノードのうち、メタ情報などを管理するマスタが存在する。
    • マスタがクラスタの状態を管理するため、マスタに対する障害耐性が低かったり、マスタの処理性能がクラスタ全体の性能に影響を与えたりする。
  • P2P

    • P2P型ではマスタ型と異なり、クラスタを構成するノードが全て等価な役割を持つ。
    • 全ノードで等価なため、障害耐性の偏りがなく、スケールアウトがほぼリニアに実現する場合が多い。
    • ただし、ネットワーク分断が発生した場合にはいわゆる スプリットブレイン の危険性がある。
  • イネーブラ型

    • 正確にはアーキテクチャではないけれどEnablerということで、他者との組み合わせによって効果を発揮する型。
    • オンメモリ型はデータの永続性という点でデータベースと呼ぶには不十分なものの、NoSQLの持つ応答性の遅さを改善することができる。

    (引用:「NoSQLについて勉強する」 https://qiita.com/t_nakayama0714/items/0ff7644666f0122cfba1)

 

railsと相性がいいのは?

railsというより、個人開発程度の大規模ではない開発をするのであれば、データ量が肥大化しすぎる恐れがないので、RDBMSであるMySQLで事足りると思う。

で、どれ使う?

PostgreSQLMySQLの使い分け方が下記のようにまとめられています。

  • 複雑なクエリや大規模なデータベースを扱うことができる機能豊富なデータベースが必要か?複雑なクエリや大規模なデータベースを扱うことができる機能豊富なデータベースが必要ですか?PostgesSQLが良いでしょう。

  • 比較的簡単に設定・管理でき、高速で信頼性が高く、ナレッジが普及しているシンプルなデータベースが必要ですか?MySQLが良いでしょう。

ナレッジが普及している方がエラーと対峙した時にググる力で対応できそうだからMySQLを採用することにする。

3種類のデータベースを徹底解説!(PostgreSQL、MySQL、SQLite)|BigData tools

https://bigdata-tools.com/sql-db/

PostgreSQL vs MySQL: その違いとは

コンポーネントって何。

今回はVueの特徴の一つである「コンポーネント」について学習していきたいと思います。

component(コンポーネント)って何

部品という意味で、使いまわすことが可能な塊というイメージ。

ヘッダー、フッター、ローディング画面やエラー画面など、複数のviewにまたがって表示したい内容を書きたい時によく使われる。

railsでいうsharedのように、componentのみをまとめたフォルダで管理することができる。

このファイルは、呼び出したいファイルの最初の行あたりでimportすることで定義したcomponentを使用することができる。

使用例

# button-counter と呼ばれる新しいコンポーネントを定義する

Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

コンポーネントは、名前付きの再利用可能な Vue インスタンスだそう。

今回の例で言うと、<button-counter>が該当する。

<button-counter>をviewに書くと、template:の内容がHTMLで表示される。

また、このコンポーネントを new Vue で作成されたルート Vue インスタンス内でカスタム要素として使用することができる。

<div id="components-demo">
  <button-counter></button-counter>
</div>
#このファイルで<button v-on:click="count++">You clicked me {{ count }} times.</button>が表示される。
new Vue({ el: '#components-demo' })
#

props

templateの内容を動的に変更するためのプロパティ。

使用例

Vue.component('blog-post', {
  props: ['title'],
  template: '<h3>{{ title }}</h3>'
})

titleをHTMLタグに直接書き込むことができる。後任の人は、このコンポーネントは、My journey with Vueが表示されるんだな〜とわかるので、可読性が上がる。

<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>

生成されるHTML

My journey with Vue
Blogging with Vue
Why Vue is so fun

コンポーネントの登録

グローバル登録

登録後に作成された、全てのルート Vue インスタンス(new Vue)のテンプレート内で使用できることを意味している。

ユーザがダウンロードしなくてはならない JavaScript のファイルサイズを不要に増加させてしまう。Vue.componentを前につける。

使用例

# js
Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })

new Vue({ el: '#app' })
#HTML
# jsで定義した第二引数が、new Vueで宣言したidタグ内で使用される。
<div id="app">
  <component-a></component-a>
  <component-b></component-b>
  <component-c></component-c>
</div>

ローカル登録

コンポーネントは登録したページ内でしか使えない。

下記のようにして使用する。

#/* ... */は表示させたい内容を書く
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

他のモジュールで使いまわしたい時は、下記のように記述する

#ComponentB.vueもしくはComponent.jsで、ComponentA.vueを使いたい時の記述
import ComponentA from './ComponentA.vue'

export default {
  components: {
    ComponentA
  },
  // ...
}

複数のファイルをインポートすることも可能

import ComponentA from './ComponentA'
import ComponentC from './ComponentC'

export default {
  components: {
    ComponentA,
    ComponentC
  },
  // ...
}

まとめ

コンポーネントを使うことでDRYにコードを書くことができる。

https://zenn.dev/kj455/articles/c740c72edeabbe

コンポーネントの登録 - Vue.js

Vue Routerって何。

最近Vue、コンポーネント、リアクティブ性、拡張性の高さ、トランジションが特徴、の学習を始めました。Vue Router公式を参考にして特徴と使い方についてまとめていきたいと思います

Railsのルーティングとは違うのか?7と感じたので、学習していきます。

 

Vue Routerって何

vueが公式に提供しているライブラリの一つ。ルーティング機能を提供してくれる。

Vue routerを使うことで、SPA(Single Page Application)を作ることができるので、ユーザーの離脱率を抑えることができる。

https://router.vuejs.org/

  • ネストされたルート/ビューマッピング
  • モジュール式、コンポーネントベースのルータ構造
  • ルートパラメータ、クエリ、ワイルドカード
  • Vue.js の transition 機能による、transition エフェクトの表示
  • 細かいナビゲーションコントロール
  • 自動で付与される active CSS クラス
  • HTML5 history モードまたは hash モードと IE9 の互換性
  • カスタマイズ可能なスクロール動作

railsでいうroutes.rbと同じ役割をしてくれる認識

SPAって何?

Single Page Applicationの略。

シングルページアプリケーションとは、Webアプリケーションの構成法の一つで、Webブラウザ側でページの移動を行わず、最初に読み込んだWebページ上のスクリプトがサーバとの通信や画面遷移を行う方式。

メリット

  1. 通常のWeb ページでは実現できないユーザー体験を実現できる。
  2. 高速なページ遷移を実現できる。

下記記事にもあるとおり、ページのローディング速度が遅い程離脱率が上がるとのこと。

https://neilpatel.com/blog/loading-time/?wide=1

デメリット

  1. SEO対策のためにサーバーサイドレンダリング(SSR)という技術で対策をしなければならない
  2. 開発者が少ない。

Vueをどうやって使う?

  1. yarnまたはnpmパッケージを用いてインストールするか、CDNをheadタグに直接書き込む方法がある。
$ yarn add vue-router
yarn add v1.22.10
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
warning " > bootstrap@4.0.0-beta" has unmet peer dependency "jquery@>=3.0.0".
warning " > bootstrap@4.0.0-beta" has unmet peer dependency "popper.js@^1.11.0".
warning " > vue-loader@15.9.8" has unmet peer dependency "css-loader@*".
warning " > vue-loader@15.9.8" has unmet peer dependency "webpack@^3.0.0 || ^4.1.0 || ^5.0.0-0".
warning " > webpack-dev-server@3.11.2" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning "webpack-dev-server > webpack-dev-middleware@3.7.3" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ vue-router@3.5.2
info All dependencies
└─ vue-router@3.5.2
✨  Done in 6.51s.
  1. app/javascript配下にrouter/index.jsを作成する
import Vue from "vue";
import Router from "vue-router";

import TopIndex from "../pages/top/index";
import TaskIndex from "../pages/task/index";

Vue.use(Router)

const router = new Router({
  mode: "history",
  routes: [
    {
      path: "/",
      component: TopIndex,
      name: "TopIndex",

    },
    {
      path: "/tasks",
      component: TaskIndex,
      name: "TaskIndex",
    },
  ],
})

export default router

import Vue from "vue"

Vueを使えるようにしている。

import Router from "vue-router"

vue-routerを使えるようにしている。

Vue.use(Router)

VueでimportしてきたRouterを使うためのインスタンスが生成される

const router = new Router({

mode: "history", routes: [

ルーティングを定義している。

ホームディレクトリでTopIndexに遷移し、/tasksでTaskIndexに飛ぶようにしている。

vue-router のデフォルトは hash モード です - 完全な URL を hash を使ってシミュレートし、 URL が変更された時にページのリロードが起きません。

デフォルトの挙動ではURLに#が含まれるが、mode:historyを指定することでURLからhashを取り除くことができる。

3. エントリポイントとなるJavaScriptファイルに記述を追加する

app/javascript/packs配下にhello_vue.jsを作成し、このファイルをエントリポイントとする。ちなみにエントリポイントとは、アプリケーションの最初に読み込まれて、実行される部分を指す。

import router from '../router'
import 'bootstrap/dist/css/bootstrap.css'

Vue.config.productionTip = false

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    **router,**
    render: h => h(App)
  }).$mount()
  document.body.appendChild(app.$el)

このようにプラグインでrouterを宣言することで、this.$routerで他のファイルでもVue Routerを呼び出せるようにしている。

4. Viewを実装する

ルーティング制御によって表示したいコンテンツの場所に、<router-view>タグを記述する。

app/javascript/app.vue

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

railsの<%= yield%>に似ている。javascript/pages/hoge/hoge.vueの内容がapp.vueの<template>に描画されるイメージ

5. router-linkコンポーネントを利用して遷移を実装する

<router-link :to="{ name: 'TopIndex' }" class="btn btn-dark mt-5">戻る</router-link>

const router = new Router({
  mode: "history",
  routes: [
    {
      path: "/",
      component: TopIndex,
      name: "TopIndex",

    },
    {
      path: "/tasks",
      component: TaskIndex,
      name: "TaskIndex",
    },
  ],
})

遷移先をto属性の値として指定する。<router-link>タグは<a>タグとしてレンダリングされる。

Vueインスタンスって何

Viewで使われるインスタンスのこと。下記ライフサイクルフックにおいて、一番最初に行われる作業。

Untitled

vm (ViewModel の略) を Vue インスタンスの変数名としてよく使うようです。

https://jp.vuejs.org/v2/guide/instance.html

詳しい説明は以下の通り

各 Vue インスタンスは、生成時に一連の初期化を行います。例えば、データの監視のセットアップやテンプレートのコンパイル、DOM へのインスタンスのマウント、データが変化したときの DOM の更新などがあります。その初期化の過程で、特定の段階でユーザー自身のコードを追加する**ライフサイクルフック(lifecycle hooks)**と呼ばれる関数を実行します。

(公式ページhttps://jp.vuejs.org/v2/guide/instance.html#ライフサイクルダイアグラム)

Vueにはtemplate,mounted,watcherなどのフックスがあるみたいです。

いろんな記事のつぎはぎみたいになってしまった。実際にアプリ開発を進めていく段階で脳内の継ぎ目を綺麗にしていきたい。

Vue インスタンス - Vue.js

Vue インスタンスにつて勉強する - Qiita

VueRouterを使用してSPAを実現

DB設計のアンチパターン

やっとER図の作成が終わりました。これからポートフォリオ完成までの道のり、「issueを切り出す」ことをやっていきます。その前に、ER図作成時にやってしまったデータベース設計でのアンチパターンを2つまとめたいと思います。

当初のテーブル設計

下記テーブル設計が当初僕が書いていたテーブルになります。

Image from Gyazo

この、AccumulatedOrdersテーブルとAlcoholOrdersテーブルのカラムに問題がありました。

それぞれのやりたかった挙動をまとめます。

使用するモデル

class AccumulatedOrders < ApplicationRecord
  has_many :alcohol_orders
end
class AlcoholOrders < ApplicationRecord
  belongs_to :accumulated_order
end

エンティティ内のカラム

create_table "accumulated_orders", force: :cascade do |t|
    t.integer "is_accumulated_orders", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["analysis_id"], name: "index_accumulated_orders_on_analysis_id"
  end
create_table "alcohol_orders", force: :cascade do |t|
    t.integer "is_order", null: false
		t.integer "alcohol_types", default: 0, null:false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["accumulated_orders_id"], name: "index_alcohol_orders_on_accumulated_orders_id"
  end

カラム説明

  • is_accumulated_orders: お酒の順番を格納する
  • is_order: お酒の順番を格納する
  • alcohol_types: enumでお酒の種類をポリモーフィック関連させる。同じメソッドを持つ酒インスタンスを複数作ることができる。

やりたかった挙動

AccumulatedOrders

お酒の順番をまとめて管理

AlcoholOrders

お酒の順番と種類を個別に管理

accumulated_order_id: 外部キー、bmn

is_order: 何番目のお酒になるのかを格納

alcohol_types: お酒の種類を格納

AccumulatedOrders
id: 1・・・①
is_accumulated_orders: ["1","2","3","4"] #お酒の順番

AlcoholOrders
accumulated_order_id: 1 #AccumulatedOrdersのidと対応する
id: 1
is_order: 1 #is_accumulated_ordersの"1"に対応する
alcohol_types: 1 

accumulated_order_id: 1・・・③
id: 2
is_order: 2
alcohol_types: 2

accumulated_order_id: 1・・・③
id: 3
is_order: 3
alcohol_types: 3

accumulated_order_id: 1・・・③
id: 4
is_order: 4
alcohol_types: 4
  • 診断結果に基づいて飲み物を飲む順番をユーザーに提供する
  • AccumulatedOrdersテーブルのis_accumulated_ordersで順番を保持する。
  • AlcoholOrdersテーブルのaccumulated_order_idはAccumulatedOrdersの外部キーで、AccumulatedOrdersテーブルのPK(id)と対応する。

問題点① お酒の順番を格納するカラムが重複している。

AccumulatedOrdersとAlcoholOrdersそれぞれにお酒の順番を格納するカラムがあります。

同じ挙動をするカラムが二つ以上あることはテーブル設計上冗長なので、どちらかにまとめる必要がある。

問題点② カラム名が適切でない

is_order,is_accumulated_ordersというカラム名が使われています。こちらの記事を参考にしてテーブルの切り分けを行ったためis_〇〇というカラム名にしました。

どうやらisを接頭につけると、格納される値はtrue, falseになるのがベストのようです。なぜなら、isはクローズドクエスチョンだからです。なので、カラム名をそれぞれis_order⇨orders,is_accumulated_orders⇨削除に変更しました。

問題点①を理解する

これは生徒の例を使うと、なぜアンチパターンなのか理解することができました。

下記のような生徒クラスがあるとします。

Image from Gyazo

生徒はそれぞれidと出席番号を持っていて、クラスIDの組に所属すると仮定します。

ここに4人の生徒がいます。


id:4
出席番号:4
クラスID:1
名前: 安倍

id:1
出席番号:1
クラスID:1
名前: 青木

id:3
出席番号:3
クラスID:1
名前: 麻生

id:2
出席番号:2
クラスID:1
名前: 秋元

1組に所属する生徒を取得したい場合、SQLは以下のように書きます。

SELECT s.name
FROM Student s
WHERE s.class_id = 1
#=> "安倍","青木","麻生","秋元"

さらに、順番を昇順に取得したい場合はORDER句を使用して以下のように書きます

SELECT s.name
FROM Student s
WHERE s.class_id = 1
ORDER BY absence_id
#=> "青木","秋元","麻生","安倍"

つまり、Studentクラスのみで、生徒の順番や所属する場所を絞り込み検索することができるので、わざわざ順番だけを保持するクラスや、所属する場所だけを保持するクラスを作る必要が内容です。

当初のテーブル設計は正規化が足りていなかったみたいです。

最終形態(第3正規化)

順番だけを持つAccumulatedOrdersテーブルが今回のテーブル設計に不必要なものになったので、削除し、設計を見直しました。

Image from Gyazo

と思いきや、、

しかし、まだ正規化が足りていません。順番とお酒の種類を持つテーブルになってしまっています。一つのテーブルで守らせたい挙動は一つに決まっているので、順番とアルコールの種類を切り分けて二つのテーブルに切り出しました。

Image from Gyazo

これで現段階の正規化が完了しました。

これから本格的にポートフォリオを作成していきます。結果的にテーブル数が4つになってしまいましたが、拡張性の高いアプリではあるので、機能追加と主にテーブルを増やしていければと思います。なんとなくデータベースのdの字くらいはわかってきた気がするな?

マスタとトランザクション - Qiita

クイズアプリにおけるデータベース設計のアンチパターン - Qiita