社内業務ツールをVue.js/Vuex/rxjs/RailsでSPAで作ってみた話

配送サービスハコベルの担当の蟻塚です。

ここ1ヶ月半ほど、社内業務ツールを1人でSPAでモリモリ開発していたので、その話を書かせていただきます。

筆者のバックグラウンド

サーバーサイドエンジニア。SPAはBackbone/Marionette, React/Reduxで3回ぐらい書いたことがある。CSSもアニメーションも書けないのでフロントエンドエンジニアからは程遠い。SSRとかも詳しくない。

業務ツールの概要

  • 毎日の業務に使うので、使いやすさは重要
  • ジオコーディング等の非同期処理が数100〜件発生する
  • 社内でのみ使うツールであり、使用者は少人数。
  • 一度運用が回るようになりさえすれば改修はほとんど発生しない(想定)。
  • SSRとかは100%不要

今回使ったもの

フロントエンド

バックエンド

フロントエンドの話

なぜVue.jsを使うことにしたのか

1年ほど前に、React/Reduxで業務ツールを作ったことがあるのですが、その時の思い出として、

  • 開発速度が全然でない
  • 非同期処理を少し書くだけで実装どうするか迷う
  • middlewareを調べるのが手間
  • 関数型のノリが馴染まない

上記のような超個人的感想を持ちました。なので今回は、Vue.jsでSPAを作ってみることにしました。HTMLにJS直書きしているような雑なツールを作る際にVue.jsを使っていて、こっちのが楽なんじゃね、って思ったのもあります。

Vuex

reduxの影響でglobalなstoreでstate管理しようと思ってvuexを導入しました。ですが結局のところ、

  • コンポーネントlocalなstateはコンポーネントで管理する
  • 複数コンポーネント間でデータをやり取りしたいときだけ、Vuexを使う

という形にしました。たとえば、以下のようなものはVuexで管理することにしました。

  • ログイン情報
  • グローバルなメッセージ表示(snackbar)
  • 通信ローディング表示

こういうルールにした理由は、

  • URLに対応するようなコンポーネント(ex. アイテムリスト、アイテム詳細ページ)が破棄された場合は、そのデータを破棄したい。globalなstoreにデータを保存している場合は、明示的に破棄処理を書く必要がある
  • vuex使うと単純に実装時間が増える。
  • vuex付属のデバッガーをそれほど使わない(自分は)
  • そもそもあんまりstate管理に困っていなかった

上記のような理由からです。作るものによっては有用だと思うのですが、今回はそれほどメリットを感じなかったのでlocal管理にしました。

HTTP Responseをhookしてグローバルなメッセージを出す

例えばHTTPのレスポンスコードが500だった場合、エラー表示を出したいケース等あると思います。今回はできるだけ楽したかったので、axiosにインターセプターを登録して、レスポンスコードが200じゃなかった場合はエラーメッセージを表示することにしました。

  function serverErrorInterceptor(error) {
    const { message } = error
    store.commit(types.SHOW_MSG, {
      message: message,
      level: 'error',
    })
    store.commit(types.HIDE_LOADING)
    return Promise.reject(error)
  }

  client.interceptors.response.use(serverErrorInterceptor)

こういった処理はVuexを使ったおかげでスッキリ書けた印象があります。

rxjs

100回非同期処理を行って、10回成功する毎にDBに保存するためにサーバーにリクエストを投げる、みたいなことをシンプルにやりたくてrxjsを使いました。

人を選ぶツールかつ、イニシャルの学習コストが高いので気軽に導入しない方が良いと思いますが、時と場合によってはとても便利に使えます。上記のような例を普通に書くととても汚くなりがちですが、rxjsを使うとスッキリ書けます。

var Rx = require('rxjs/Rx')

const geocode = (v) => {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('geocode:' + v)
      resolve(v)
    }, v * 500)
  })
}

const save = (v) => {
  console.log("save: " + v)
  return new Promise(resolve => {
    setTimeout(() => resolve(v), 100)
  })
}

Rx.Observable.from([1, 2, 3, 4, 5, 6])
  .mergeMap(v => Rx.Observable.fromPromise(geocode(v)))
  .bufferCount(3)
  .mergeMap(locations => save(locations))
  .subscribe(
    () => { /* next */ },
    () => { /* error */ },
    () => console.log('complete')
  )

# output
-> % node sample.js
geocode:1
geocode:2
geocode:3
save: 1,2,3
geocode:4
geocode:5
geocode:6
save: 4,5,6
complete

Vuetify

https://vuetifyjs.com

vuetifyは、Material Component Framework for Vue.js 2 だそうです。SPAだとbootstrapはちょっと勝手が悪いので、vuetifyを使ってみました。現状使ってみて、ほとんど困ることはなかったのですが、

  • まだまだ開発途中(バージョンすぐ上がる)
  • 難しくはないが、ハマった時にソース読むしかない

1年後どうなっているかは不明ですし、まだproductionで使うには早いかな、といった印象はあります。ただ、こういったcomponent frameworkを使って開発する未来は近いという印象を受けました。概要は上記のリンクみて頂くのが早いと思います。

バックエンドの話

webpackerはどうなのか?

webpackerを使う前は、リポジトリのtopに /front  ディレクトリを作ってwebpackでビルドして、アセットパイプライン配下に放り込むスタイルでやってました。

webpackerの良いところは、railsにインテグレーションされていることだけです。悪いところは、webpacker特有のフォルダ構成になることです。

使用感としては、webpackerは開発初日に導入して以降はあまりいじることはありませんでした。また導入自体も困りませんでした。app/javascript/packs というディレクトリ構成はなんか嫌だなって思いましたが、生産性には何の関係もありませんでした。

基本的に、webpackの薄いwrapperであり、辞めたくなったらいつでも辞めれるので、オレオレ構成のwebpack railsインテグレーションするよりは、そのまんま使えば良いんじゃないか、というのが私の見解です。JS書くだけに使う分には困らないと思います。

終わりに

react nativeに対抗して、weex 作ってたりと、Vue.jsの周りのエコシステムもだいぶ充実してきているという印象でした。Backbone/Require.jsでSPA作ってた時代に比べるとだいぶツールも成熟してきており、生産性という観点でも JS FrameworkとComponent Frameworkを使って開発する未来が来てもおかしくないという印象があります。

また、Reactとの比較という点では、Vue.jsもかなり影響を受けており(勝手な主観ですが)、お互い大きな差分は減ってきているように感じます。おそらく React/Reduxで開発できる人は、Vue/Vuexでもそれほど学習コスト無く開発できると思います。

個人的にはVue.jsは開発しやすいフレームワークなので、興味を持った方は試してみては如何でしょうか。

採用もやってます! ↓