vue.js

Vue.jsで同人イベントの告知ページを作った話

以前書いたRiotで同人イベントの告知ページを作ったよという話の続編みたいなかんじです。
(内容はあんまり関係ないです)

背景(なぜVueなのか)

イベントサイト立ち上げ時は制作期間が全然無かった(デザイン+コーディング数日程度)ので、
普通にHTMLを書いて、エフェクト等のJSもES6+Webpackな感じで書いていました。

しかし、アマチュアがやっている即売会とはいえ、使いやすいものを作りたいという気持ちがありました。
使いやすさの上で一番大切な参加サークル一覧ページは、情報を探しやすくする機能を盛り込みたい。
ということで、その辺の機能を実装しやすくするために、いい感じのView系フレームワークを使って書き直してしまおう、という感じ。
前回Riotを使って開発したのでそれでも良いのですが、ナレッジが探しづらく、
結局いろいろとハマってしまった苦い経験もあり、別のフレームワークで開発したい気持ちがありました。

そこで、色々見てるとVueがホットだし(ちょうどReactのライセンス関係でゴタゴタしている時期でもあった)、
HTMLで書けるしナレッジもWeb上で探しやすく、良さそう!ということで、Vueを採用してみました。
(やっぱりユーザー数多いフレームワークは困ったときにも情報が探しやすいので助かる…!)

環境構築とか

Vue-cliは神。
webpack を使って書いていきたかったので、$ vue init webpackからの質問に答えつつ適宜設定する感じでダイアログに答えていきます。
今回はRouter入れて、ESLintはAirbnbベースで調整するよう設定しました。

htmlはPugで書きたいので、yarn add pugで。(※これだけで使える。凄い!)
スタイルはSCSSで書きたいので、yarn add node-sass

これだけで書ける。便利!

制作の流れ

上記の通り、HTML/CSS/エフェクト等JSは既存のものがあるので、だいたい丸ごとVueに移植してます。
(ゼロから書く場合はどうしたら良いんだろう? 適当な雛形から肉付けしていくのが良さそうかな。)

HTML/CSSなど

既存のpug/sassファイルから、ある程度の機能ごとに.vueファイルへ分割していく感じで作業を進めていきました。

情報ページの中身については、Riotベースで書いたときのようにJSONデーター読ませたいんですが、
結局編集がやりづらい感触があったため今回はvueファイルに直書きしました……。

ただ、コンテンツの修正の度にソース触ってbuildして本番アップというのは非現実的なので、
将来的にはCMS的なものを使って更新できるようにしたいなあ、とは思っています。
※AWS S3上に置いているのですが、上手い方法はないものかしら……。
編集画面からLambda叩いてデータ更新、みたいな仕組みが作れれば良いのかなあとは思ってます。

JSの移植

こちらもだいたい同様に、機能ごとに.vueに落とし込んでいった感じ。

コンポーネント間連携

作り込んでいくと、イベントのハンドリングやデータの相互通信が問題になってくることがありますね。
ページロード検知でのアニメーション開始を作り込むときとか。

RiotでやったときはObservableを使ったのですが、
今回は公式ドキュメントにあるように、専用のvueインスタンスを作り、それを経由してデータのやり取りをしました。

こんなかんじ。

main.js
// windowにbusという名前でイベントやり取りのVueインスタンスを登録しておく
window.bus = new Vue();
example_emitter.vue
// イベント発出側
window.bus.$emit('hogeEvent', '居酒屋のメニュー');
example_reciever.vue
// イベント受信側
window.bus.$on('hogeEvent', (arg) => {
  console.log('%sが喋った!', arg); // -> 居酒屋のメニューが喋った!
});

といっても、ちょっと複雑化すると混乱してくるので、
ある程度の規模になる事が予想されるなら、素直にvuexを使った方が良さそう。

詳細モーダルについて

/list/?circle=(ID)でサークル詳細が開くようになっていますが、このあたりはrouterで実装しました。
URLにIDが渡されていたらモーダルを開く、という動作を下記のような感じで実装しています。
(もうちょっと頭良いやり方はあるのかも…)

routes.js
export default new Router({
    routes: [
    // 中略
    {
        // 中略
        props    : route => ({ circleID: route.query.circle }),
    },
})

という感じで、query文字に渡されたIDがサークルリストページのpropsに渡され、

circleList/index.vue
<template lang="pug">
main
    // 中略
    // ↓モーダル部分
    .modal(:class="{shown: modalOpen}")
</template>

<script>
export default {
    // 中略
    props: {
        circleID: String,
    },
    computed: {
        modalOpen() {
            // circleID が渡されていたらisModalOpenがtrueになる
            const isModalOpen = (this.circleID !== undefined);
            // isModalOpenがtrue時の処理など略
            return isModalOpen;
        },
    },
}
</script>

(/list/?circle=(ID) にアクセスされ、)詳細モーダルを開くフラグが立っていれば、モーダル表示用のclassが付加される仕組み。
画面上の詳細を開くリンク部には、このURLが開かれるようrouter-linkを設定しています。
逆に詳細モーダルを閉じる動作は、routerに/list/をpushすることで実装しています。

Vueを使ってみて

  • やっぱりユーザー数多い方が良いね…
  • ドキュメントも割と読みやすいのでありがたい。
  • HTMLを書くのとあまり変わらない感覚でコード書けるので楽。
  • スタイルやらクラス名やら、何でもかんでもデータバインディングで書けるのでめっちゃ楽…
    → データを渡すだけでDOMの内容が書き換わってくれるので、お品書き登録みたいな機能もサクッと実装できてしまったし、SNSでつぶやくボタンの実装も手間いらず。

最後に

ということで、「アクアマリンドリーム」をよろしくね。

あ、参考までにここにコード置いてます。
現在は非公開リポジトリでメンテナンスしてますので参考程度ですが…。