営業の枠から飛び出せ!
営業の枠から飛び出せ!
2019.05.08
#190
それいけ!フロントエンド

Vuexライクなノンコンポーネント用ステート管理ライブラリ「Vanix」をつくってみた

ヒガ

どうも、フロントエンドエンジニアのヒガです。

ここ最近の案件でNuxtを使っていたんですが、とっても素敵なフレームワークで恋に落ちちゃいそうでした。

でも、僕の対応する案件はそれらのフレームワークを使えないことも多く、フレームワークを使ったあとにそういった案件にとりかかると正直めんどうだなと感じることがあります。ですので、一部の機能だけでもいい感じにつかうことはできないかなと思いました。

目をつけたのはステート管理に関する機能です。NuxtでいうとVuexですね。公式サイトを引用するとこのように書いてあります。

Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。

(引用元:https://vuex.vuejs.org/ja/

Fluxに影響を受けたデータの流れを単一方向に制限する状態管理ですね。Vuexの場合は、データに変更を加えるとそれに紐づくコンポーネントのビューも更新されます。便利ですね。Vuex否定派の話も聞きはしますけど、ようはバランスかなって思います。まあ、それが難しいって話かもですが。僕も基本的にはコンポーネント内で完結するのが理想だと思っているので、Storeを乱用しないようにはしているつもりです。

それで何が欲しいのかというと……。フレームワークをつかわない場合でも、自由にどこからでも参照できるデータが欲しいときがあります。たとえば、メニューを開いているのか、今どのセクションを表示しているのか、非同期遷移の場合に遷移前のページの状態を保持したい、などなど。

僕の場合は、名前空間をつくり状態管理するオブジェクトをつくるか、各モジュールのインスタンスなどに保持しておいた状態をインポートして参照するなどしていました。しかしながら、どちらの方法をとっても変更の安全性や検知に関する処理が泥臭く、面倒な感じになってしまいます(まあ、僕の書き方がわるいのかもしれませんが)。

ということで、いい感じにステート管理をしようかと思いたち、せっかくならVuexと同じ感じでつかえるようにとつくってみました。もっとも、Nuxtなどのフレームワークにおいてはコンポーネントなんて概念そのものがないので、「データの流れを単一方向にする」「状態の変更を簡単に監視する」「Vuexライク」を意識した簡易的なライブラリをつくってみた次第です。

その名は「Vanix」

Vuex + Vanilla JS って感じですかね。特にフレームワークとか使わないけど、モジュールをまたいだステート管理をしたい場合につかえるライブラリです。ライブラリなんて大層なことを言ってますが、けっこう簡易的な内容なんですけどね。思想は Vuex をパクってるので自分で考えたところはあまりないですし。とりあえずGitHubに上げて、NPMにもパブリッシュしています。

Vanix

みてもらうとわかると思うのですが、コメント含めて200行にも満たない程度の内容です。つくったきっかけとしては、上述したようにフレームワークを使わない場合でもStoreってほしいなと思ってたんですよね。

そんなおり、同僚が「非同期遷移時にもStoreってあるとよいのかな?」って話をSlackに投稿したんですよ。同じように思っている人がいるんだなって知ったらつくりたくなりまして、なんとなくStoreっぽいのを数時間ほどで書いたんですよ。Vuexっぽく。そしたらだんだん物足りなく感じてきて、もうVuexのもってるメソッドや引数とか入れちゃって独立したモジュールにしてしまえ。ってなりまして、ノリでそのまま翌日にまでに作り上げました。で、ここまでできたらもう公開してしまえってノリでほんの少しだけ調整してNPMにパブリッシュしました。

つまりはノリでつくって、ノリで公開したライブラリです。でもまあ、ライブラリなんてはじめはこんな感じでつくらるもんだと思います。たぶん。

使い方

まあ、READMEに書いてあるのがすべてなんですが、この記事のかさ増しとして使い方の説明をします。

インストール

yarn でもいいんですが、僕が npm 派なので。yarnもよいやつですよ。最近はyarnの方がなんとなく人気なのを感じます。でもまあ僕はnpm派です。

$ npm install vanix --save-dev

あと、ES6以降で書いているのでトランスパイルする必要がありあす。webpackの設定だと以下のような感じかな。

module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /node_modules\/(?!(vanix)\/).*/,
      loader: 'babel-loader'
    }
  ]
}

なんでES6以降で書いているのかというと、最近はトランスパイル前提で書かれているモジュールが増えてきてると感じてるからかな。まあ、それは言い訳でめんどうだったからってのが大きいけれど。

Storeの作成

とにかく簡単に説明したいので、シンプルなサンプルにしています。

import Vanix from 'vanix'

// ステート
const state = {
  name: 'Higa',
}

// ミューテーション
const mutations = {
  setName(state, data) {
    state.name = data
  }
}

const vanix = new Vanix({ state, mutations })

export const store = vanix.create()

Vuexつかったことある人には、もう説明不要だと思います。僕が説明するより、とりあえずVuexの公式ガイドを読んでもらえると一番わかりやすい気がするのですが、それではあまりに他人まかせすぎるのでかるく説明します。

変数名 説明
state ステートオブジェクトの設定と初期化をします。
mutations 状態(state)を変更する関数を設定します。

state につくった状態を mutations で変更できるようにする。とうことですね。そして mutations 以外で state を変更することはできません。この時点で、とりあえずはデータの流れを単一方向を保てているのが分かるかと思います。

Vanixでのデータの流れ

図でみるとこんな感じですね。

状態の取得と変更

こちらも至ってシンプルに。

import { store } from './store'

// ステート
console.log(store.state.name) // Higa

// コミット
store.commit('setName', 'Hoga')

先ほどつくった store をインポートしてつかいます。

メンバ名・メソッド名 説明
state ステートオブジェクトを返します。
commit 状態(state)を変更する関数を実行します。第一引数に実行する mutations のキー、第二引数に変更する値を入れます。

store.state はただ呼び出してるだけなのですが、呼び出したメンバから直接変更を加えることはできません。ですので、変更をおこなう際は store.commit をつかいます。これはデータの流れを単一方向に保つためです。

Vanixでのデータの流れ

先ほどの図に追加してみました。分かりやすいですね。

状態の監視

これはVuexとは違っている箇所ですね。Vuexであれば算出プロパティの computed にでも入れておけばリアクティブにViewが更新されます。ただ、そんなものはありません。なので監視するためのメソッドを用意しました。僕がこのライブラリで考えた唯一の仕組みといっても過言ではありません。

import { store } from './store'

// オブザーブ
const storeObserveObj = store.observe({
  name(state) {
    console.log(state.name)
  }
})

// アンオブザーブ
store.unobserve(storeObserveObj)
メソッド名 説明
observe 設定したステートオブジェクトの状態を監視します。変更時に実行される関数の引数には store.state と同じくステートオブジェクトが入ります。また、入れたオブジェクトをそのまま返します。
unobserve store.observe につかったオブジェクトをつかって監視を解除します。

Vuexとは違うとはいえ、可能な限り近づけたいので mapState ヘルパーをつかった際の挙動に近づけました。サンプルで言う state 引数の箇所ですね。あとは、非同期遷移のことなども考えて監視の解除も用意しました。我ながら気が利いてます。

Vanixでのデータの流れ

さらに図に追加してみました。まあ、こんな感じですよ。

その他

今回はできるだけ簡単に説明したいので省きましたが、Vuexと同じ様に非同期の関数を設定する action や、それを実行する store.dispatch も用意しています。READMEにサンプルはありますが、詳しく知りたければVuexの公式ドキュメントを読むとよいでしょう。Vuexにはほかにも watch や subscribe などのメソッドもあるのですが、正直使ったことないし、現時点で使いどころがわからなかったので今回は含めていません。まあ、基本的な機能だけを含めた形になったかと思います。

つくってみて

今のところ2案件ほどで試してみて、個人的には今後も使っていこうと思えました。それに、Vuexを使ったことがあれば簡単に使いこなせるとも感じました。自画自賛です。というかVuexがすごいだけなんですけどね。こういった構成を模倣するとういことはとてもよい学びになります。「車輪の再発明」に近いものはありますが、僕は車輪の再発明が好きですので。ただし自分が興味があるもの限定ですけどね。