モーダル機能と学ぶ(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 つのクラスです。
v-enter
: enter の開始状態。要素が挿入される前に適用され、要素が挿入された 1 フレーム後に削除されます。v-enter-active
: enter の活性状態。トランジションに入るフェーズ中に適用されます。要素が挿入される前に追加され、トランジション/アニメーションが終了すると削除されます。このクラスは、トランジションの開始に対して、期間、遅延、およびイージングカーブを定義するために使用できます。v-enter-to
: バージョン 2.1.8 以降でのみ利用可能です。 enter の終了状態。要素が挿入された 1 フレーム後に追加され (同時にv-enter
が削除されます)、トランジション/アニメーションが終了すると削除されます。v-leave
: leave の開始状態。トランジションの終了がトリガされるとき、直ちに追加され、1フレーム後に削除されます。v-leave-active
: leave の活性状態。トランジションが終わるフェーズ中に適用されます。leave トランジションがトリガされるとき、直ちに追加され、トランジション/アニメーションが終了すると削除されます。このクラスは、トランジションの終了に対して、期間、遅延、およびイージングカーブを定義するために使用できます。v-leave-to
: バージョン 2.1.8 以降でのみ利用可能です。 leave の終了状態。leave トランジションがトリガされた 1 フレーム後に追加され (同時にv-leave
が削除されます)、トランジション/アニメーションが終了すると削除されます。
vに、transition name="〇〇"の〇〇部分を入れた状態で使用するみたいだ。
今回の例だと、fadeを入れているから、fade-enterやfade-leave-toと表している。
成果物
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
emitって何?
子コンポーネントの動きを親コンポーネントが察知するためのメソッド。
子⇨親
-
引数:
{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/)
VueでAPIを叩く(axios,fetch())
脳みそをオンプレミスではなくクラウドに移行したいことスバルです。
前々から気になっていたAPI。curlを入力するとjsonが返ってきて、それを読み込んでviewにjsonの中身を表示させるものかな?くらいの認識だから、もっと体型的に学んでいこうと思う。
それでは、VueでAPIを叩く(axios,fetch())を深掘っていきましょう。
APIとは
アプリと外界を繋ぐ中継役のこと。認証と認可でも出てきた。
アプリケーションからリクエストを送ったら、サーバーからjson形式データが返ってくる。
この送る場所と返ってくる場所の交流地点?薩摩藩と長州藩の仲介役だった坂本龍馬的な?
VueでAPIを叩くには?
APIを叩く用の外部リソースであるaxiosもしくは、外部APIであるFetch APIを使用する。
axiosとは
公式によると、下記のポイントが特徴とされている。
- Make XMLHttpRequests from the browser
XMLを使っている
- Make http requests from node.js
node.jsからhttpリクエストを送っている
リクエストとレスポンスの中継地点として双方のデータを変換している
- 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
オプションを持っているかを確認。- マウント(インスタンスの
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 が返されるのを待機するために使用します。
//json読み込み
async function hoge () {
const res = await fetch(url);
const json = await res.json();
// 処理 json.xxxx〜
}
- 関数の
function
宣言の前にasync
を書いて非同期(async)関数であることを宣言 - 変数
res
にawait
を書くことで非同期通信が終わった後にfetch()
メソッド(引数はurl
)を実行。 - 変数
json
にawait
を書くことで非同期通信が終わった後にres.json()
を実行 res
、json
が終わったら次の処理を実行する
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
RDBMSとNoSQLを学ぶ。
データベースの違いがわからない。rails newする時のデータベースって何がいいの、、
分からないのなら調べちゃおう。
という事で、今回は個人開発によく使われているMySQLとPostgreSQL特徴についてまとめていきたい。
データベースとは
データベースとは「構造化した情報の集合体 」であり、データベース管理システム(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) 以外のデータベース管理システムを指すおおまかな分類語
下記のような特徴を持つ
・キーバリュー型:キーとバリューのみのシンプルな組み合わせのモデル
・カラム指向型:キーバリュー型にカラムの概念をもたせたモデル
・ドキュメント指向型:JSONやXML形式で記述されたドキュメントの形で管理するモデル
・グラフ指向型:データとデータ間のつながりを管理するモデル
ちなみにRedisはNoSQL。
NoSQLの種類
NoSQLは下記のように分類される。
- どのような形でデータを持つか (データモデル)
- どのように分散してデータを持つか (アーキテクチャ)
データモデル
NoSQLのデータモデルには大別して次の4つがある。
- キー・バリュー型
- Key : Valueを単位としてデータを格納する。
- シンプルで応答が早い。
- カラム指向型
- 一般の行指向型DBと同様に表の構造を持ちつつ、カラム単位でデータを保持する。
- 行指向では苦手な列単位の大量集計、大量更新が得意。
- グラフ型
- ノード、リレーションシップ、プロパティによって定まるデータを単位とし、全体でグラフを形成する。
- グラフ型の名前の通り、Facebookの知り合い機能等で有効に利用できる。
- ドキュメント指向型
アーキテクチャ
アーキテクチャは次の3種類。
-
マスタ型
-
P2P型
-
イネーブラ型
- 正確にはアーキテクチャではないけれどEnablerということで、他者との組み合わせによって効果を発揮する型。
- オンメモリ型はデータの永続性という点でデータベースと呼ぶには不十分なものの、NoSQLの持つ応答性の遅さを改善することができる。
(引用:「NoSQLについて勉強する」 https://qiita.com/t_nakayama0714/items/0ff7644666f0122cfba1)
railsと相性がいいのは?
railsというより、個人開発程度の大規模ではない開発をするのであれば、データ量が肥大化しすぎる恐れがないので、RDBMSであるMySQLで事足りると思う。
で、どれ使う?
PostgreSQLとMySQLの使い分け方が下記のようにまとめられています。
-
複雑なクエリや大規模なデータベースを扱うことができる機能豊富なデータベースが必要か?複雑なクエリや大規模なデータベースを扱うことができる機能豊富なデータベースが必要ですか?PostgesSQLが良いでしょう。
-
比較的簡単に設定・管理でき、高速で信頼性が高く、ナレッジが普及しているシンプルなデータベースが必要ですか?MySQLが良いでしょう。
ナレッジが普及している方がエラーと対峙した時にググる力で対応できそうだからMySQLを採用することにする。
3種類のデータベースを徹底解説!(PostgreSQL、MySQL、SQLite)|BigData tools
コンポーネントって何。
今回は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にコードを書くことができる。
Vue Routerって何。
最近Vue、コンポーネント、リアクティブ性、拡張性の高さ、トランジションが特徴、の学習を始めました。Vue Router公式を参考にして特徴と使い方についてまとめていきたいと思います
Railsのルーティングとは違うのか?7と感じたので、学習していきます。
Vue Routerって何
vueが公式に提供しているライブラリの一つ。ルーティング機能を提供してくれる。
Vue routerを使うことで、SPA(Single Page Application)を作ることができるので、ユーザーの離脱率を抑えることができる。
- ネストされたルート/ビューマッピング
- モジュール式、コンポーネントベースのルータ構造
- ルートパラメータ、クエリ、ワイルドカード
- Vue.js の transition 機能による、transition エフェクトの表示
- 細かいナビゲーションコントロール
- 自動で付与される active CSS クラス
- HTML5 history モードまたは hash モードと IE9 の互換性
- カスタマイズ可能なスクロール動作
railsでいうroutes.rbと同じ役割をしてくれる認識
SPAって何?
Single Page Applicationの略。
シングルページアプリケーションとは、Webアプリケーションの構成法の一つで、Webブラウザ側でページの移動を行わず、最初に読み込んだWebページ上のスクリプトがサーバとの通信や画面遷移を行う方式。
メリット
- 通常のWeb ページでは実現できないユーザー体験を実現できる。
- 高速なページ遷移を実現できる。
下記記事にもあるとおり、ページのローディング速度が遅い程離脱率が上がるとのこと。
https://neilpatel.com/blog/loading-time/?wide=1
デメリット
Vueをどうやって使う?
- 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.
- 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で使われるインスタンスのこと。下記ライフサイクルフックにおいて、一番最初に行われる作業。
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などのフックスがあるみたいです。
いろんな記事のつぎはぎみたいになってしまった。実際にアプリ開発を進めていく段階で脳内の継ぎ目を綺麗にしていきたい。
DB設計のアンチパターン
やっとER図の作成が終わりました。これからポートフォリオ完成までの道のり、「issueを切り出す」ことをやっていきます。その前に、ER図作成時にやってしまったデータベース設計でのアンチパターンを2つまとめたいと思います。
当初のテーブル設計
下記テーブル設計が当初僕が書いていたテーブルになります。
この、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⇨削除に変更しました。
問題点①を理解する
これは生徒の例を使うと、なぜアンチパターンなのか理解することができました。
下記のような生徒クラスがあるとします。
生徒はそれぞれ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テーブルが今回のテーブル設計に不必要なものになったので、削除し、設計を見直しました。
と思いきや、、
しかし、まだ正規化が足りていません。順番とお酒の種類を持つテーブルになってしまっています。一つのテーブルで守らせたい挙動は一つに決まっているので、順番とアルコールの種類を切り分けて二つのテーブルに切り出しました。
これで現段階の正規化が完了しました。
これから本格的にポートフォリオを作成していきます。結果的にテーブル数が4つになってしまいましたが、拡張性の高いアプリではあるので、機能追加と主にテーブルを増やしていければと思います。なんとなくデータベースのdの字くらいはわかってきた気がするな?
テーブルの関連付け(一対一、一対多、多対多)
下記へ移行しました。