この記事は、Vue.js Advent Calender 2016 5日目の記事です。
Vue.jsを使って、メモアプリを作ってみました。
* この記事では、説明のためのサンプルコードを書いていますが、Githubにあるコードとは似て異なります。ご注意ください。
環境
vue-router、vuex、Firebase を使っています。
vuefire はあえて使用していません。
vuex から Firebase を呼び出しています。
- vue-router@2.x.
- vuex@2.x.
- vuex-router-sync@3.0.0
- babel (es2015, stage-2)
- webpack
- webpack-dev-server
- firebase@3.6.2
- lodash@4.17.x
- moment@2.17.x
画面遷移
vue-router を使い、3つの画面を管理しています。
- Index(タイムライン)
- Viewer(ひとつのメモを見る)
- Editor(メモを追加、編集)
それぞれ、URLが個別に割り当てられるのがポイントです。
var router = new VueRouter({
routes: [
{
path: '/',
name: 'index',
component: Index
}, {
path: '/editor',
name: 'newEditor',
component: Editor
}, {
path: '/editor/:id',
name: 'updateEditor',
component: Editor
}, {
path: '/viewer/:id',
name: 'viewer',
component: Viewer
}
]
})
状態管理
vuex を使い、全コンポーネントから利用できるデータを管理しています。
Backend API
にあたる部分が Firebase
になっています。
Vue Component から vuex の state を取得
computed: mapGetters(['user', 'currentUserName'])
vuex で保持する state を Component 内で直接変更することはできませんので、取得したデータを Component 内で変更するとエラーになります。
<input v-model="currentUserName">
その代わりに、次のように、書く事ができます。
<input :value="currentUserName" @input="something">
Vue Component から Vuex の Action を呼ぶ
methods: mapActions(['fetchMemos', 'signIn', 'signOut'])
signIn
は template
内で <p click="signIn">signIn</p>
のように呼び出せます。
methods: {
...mapActions(['fetchMemos']),
addCount () {
this.count++
},
submit () {
this.$store.dispatch('setUserInfo')
}
}
mapActions
で Vuex に書いた Actions を取得できます。
Vuex
- Action を呼び出すのが Dispatch
- Mutation を呼び出すのが Commit
- Mutation では同期処理のみ
- Component から Vuex の State を 呼び出すのが Getter
- Firebase は Backend API として Actions から呼び出すのを基本にした
- Firebase のログイン状態監視の部分だけ、 Backend API が (Vue, Vuex) に依存する
Firebase
- rule を決めるだけで良い。便利
- vuex とのやりとりは、全て Promise にした
- サーバーサイドのコード1つも書かずにできた
{
"rules": {
".read": true,
"users": {
"$uid": {
".write": "auth !== null && auth.uid === $uid",
"name": {
".validate": "newData.isString() && 2 <= newData.val().length && newData.val().length <= 100"
}
}
},
"memos": {
"$uid": {
".write": "auth !== null && auth.uid === $uid",
"$mid": {
"title": {
".validate": "newData.isString() && newData.val().length <= 200"
}
}
}
},
"publicMemos": {
"$mid": {
".write": "root.child('memos/' + auth.uid ).hasChild($mid)",
"title": {
".validate": "newData.isString() && newData.val().length <= 200"
},
"author": {
"uid": {
".validate": "newData.val() == auth.uid"
},
"name": {
".validate": "newData.isString() && 2 <= newData.val().length && newData.val().length <= 100"
}
}
}
}
}
}
大きく分けて、3つのツリーがあります。
- users(ユーザーの名前を管理、ログイン管理は、Firebaseが自動でやってくれます)
- memos(ユーザー個別ID別にメモを管理)
- publicMemos(全てのメモを一元管理)
memos と publicMemos には同じデータが入っています。
memos には個別ユーザーID毎にツリーがあり、その下に各ユーザーのメモを格納します。
publicMemos には、全てのメモがズラーっと入っています。
memos に存在するメモしか、publicMemosには書き込みができないよう権限の設定をしました。
そのため、書き込み・削除の順番を意識する必要があります。
全てのツリーはログインしていないと書き込みができません。
Firebase と vuex の共存について
VueFire - Firebase meets Vue.js
Evan氏が2016年4月13日に書いている Firebaseブログです。
Vuex と Firebase の 統合について Vue.jsチームは調査中とあります。
Githubで、vuefire、vuexfire、vuex、Firebase 関連のサンプルを探したものの、「これは良さそう」というレポジトリはありませんでした。そのため、今回 vuefire はあえて使用せずに、vuex の Backend API の 部分に Firebase を配置しました。
vuefire の良いとこどりをした構造を考えて、今回のようなアプリに組み込めたら良いんですが、いずれにせよ大規模になると複雑になりそうです。vuefire と vuex の2つで状態管理をしたら良いのかな。。
参考
今回、この記事を書くにあたり、いくつかのレポジトリや記事を参考にさせていただきましたので、ご紹介させて頂きます!感謝!
Tech Blog Vue.js + Vuexで開発をしてみよう!
vuexを理解するのに参考になりました。
【意訳】Webpackの混乱ポイント
webpackの理解が深まりました。
Firebase Web Codelab
Friendly Chat というアプリ制作を通して、 Firebase の使い方を学ぶ事ができる公式サービスです。
60分で、Firebase を Web で使うための基礎を学ぶ事ができます。
vue-cli
ここにある Webpack のテンプレートをベースに、今回作成した Vue-memo の構造を考えました。
Vue-memo は、vue-cli にあるテンプレート webpack と webpack-simple の中間くらいの規模です。
vue-boilerplate
vuex と vue-router の共存させる書き方の参考になりました。
vuex
vuex のレポジトリにあるサンプルコードを何回も読みました。
できる限りここのサンプルから構造を真似して、自分でロジックを勝手に作らないように意識しました。
vue-hackernews-2.0
Vue.js 関連のサンプルコードの中で、最も参考にするべきレポジトリではないでしょうか。
しかし、私にはサーバーサイドレンダリングなど、理解が追いついておらず。。
いつかはサンプルコードを全て理解したい。そんなレポジトリです。
作ってみて感じた事
- Webpack (もしくは、 Browserify)を使いこなすのが難しい
- babel の理解が必要(ES2015, stage-2など)
- webpack-dev-server の hot-reload は開発が捗る
- Vue.js で、作ってみた系の記事・レポジトリが少ない(見つけられないだけかも。。)
- ベストプラクティスを探すより、プロジェクトに合うアーキテクチャをチームで考えるのが良さそう
babel の presets で stage-2 を入れないと、spread演算子...
を使えない。
babel の presets を設定します。
babel: {
presets: ['es2015', 'stage-2'],
}
これで、次のような書き方が可能になります。
...mapGetters(['currentMemo', 'currentMemoID'])
このコード、Github にある Vue.js 関連の サンプルによく出てきます。
ちなみに、
今回制作した Vue-memo は1年前(2015/12/13)に書いた記事「Vue.jsを使って、簡易メモアプリを作る。facebookログイン機能付き。」を Vue.js v2.x. に対応させるという目標のもと、結局全て作り変えたたものです。
最後に
プルリクエスト大歓迎です。
日本語Vue.jsメンバーの皆様にはこの場をお借りして、いつも素晴らしい情報を発信してくださっているお礼を申し上げます。本当にありがとうございます。
明日は@mediba-kanamoriさんです。