Nuxt.js+TypeScriptで抽選ツールを作ってみた

thumbnail

はじめまして、2021年度新卒の木村です。
フロントエンドエンジニアとして テクノロジーセンター5G に所属しています。

今回は、Nuxt.js ならびに TypeScript を利用して、簡単な抽選ツールを作成してみた内容についてご紹介します。

Nuxt.js と TypeScript

Nuxt.js は、Vue.js ベースの JavaScript のフレームワークです。Webページ構築に有用な UI 以外の機能( Ajax やサーバーサイドレンダリングなど)をまとめて利用できる環境を提供してくれます。
Nuxt.js 公式サイト

TypeScript は、省略可能な静的型付けとクラスベースオブジェクト指向を加えた JavaScript のスーパーセット(上位互換)です。一言で言うと「型定義できる JavaScript 」。
TypeScript 公式サイト

なぜ抽選ツールを作ったのか

主な理由は、以下の2点です。

  1. 私の所属するチームで扱っている Nuxt.js および Typescript の概要を掴むため
  2. チーム内でMTGのファシリテータが偏ってしまう課題を解決するため

完成物

さっそくですが、完成した物がこちら。

gif

ランダムにユーザーを抽選できるシンプルなツールです。
デザインは近年流行ったニューモーフィズムを取り入れてみました。
少ない配色でも凹凸によって奥行きができるので、シンプルなツールでも見栄えが良くなります。

使った技術

  • Nuxt.js 2.15.7
  • TypeScript 4.2.4
  • Vue.js 2.6.14
  • Vuex 3.6.2
  • firebase 8.7.1
  • tailwindcss 2.2.4
  • sass 1.35.1

環境

  • M1 Mac
  • VSCode
  • yarn

機能

今回の抽選ツールの機能は以下とおりです。

  • ユーザーを登録・削除する
  • ユーザー情報をDBで保持する
  • 登録したユーザーからランダムに抽選する
  • 完了した人・休みの人にチェックし、抽選対象から外す
  • 抽選終了後、選ばれた人は自動で完了チェックされる
  • 全員チェックしたら一括リセットできる

作成手順

全体的な流れは以下のとおりです。

  1. 環境構築
  2. 各機能作成
  3. Vuex導入
  4. firebase導入

1. 環境構築

create-nuxt-appを利用して環境を構築しました。

create-nuxt-app

公式サイトNuxt.js 入門の記事 を参考にしました。

2. 各機能作成

前述した各種機能を実装していきます。

欠席者の対応

完了した人だけでなく、休みの人など抽選対象から除外したい場合を想定して、完了チェックとは別に除外したいユーザーを指定できるようフラグを持たせています。

コンポーネントについて

個人的に苦労したのはコンポーネント周りです。

まず、どのくらいの単位で区切れば良いのかがわかリませんでした。 そこで今回は Atomic Design を参考に切り出しました。

また、他の箇所でも使えるように汎用的に作ることです。 クリックなどのイベントは全て親へ渡し、スタイルや表示の変更は親から値を渡すようにすることで、atoms単位のコンポーネントがどこでも使えるよう心がけました。

例えばボタンコンポーネントは、予めいくつかのスタイルを定義しておき、親コンポーネントで使うときに下記のように props で必要なスタイルを渡すとともに、$emit でクリックイベントを親に渡しています。

//Button.vue
<div>
    <button
        :class="[
        colorStyle[buttonColor],
        sizeStyle[buttonSize],
        fontStyle[buttonFont],
        ]"
        @click="$emit('button-click')"
    >
        <slot></slot>
    </button>
</div>
//Input.vue
<Button button-color="gray" button-size="short" button-font="small">
    ADD
</Button>

そしてコンポーネント間のデータの受け渡しです。 例えばユーザー情報は、子から emit で親コンポーネントに値を引き渡し、さらに別の子コンポーネントに props で渡して表示しています。

component

今回はシンプルなツールなのでコンポーネントも少ないですが、こうした受け渡しが重なってくるとコードが煩雑になりメンテナンスがしづらくなります。そこで、Vuex を導入し状態管理をすることにしました。

3. Vuex 導入

Vuex のストアでユーザー情報の状態を管理します。 今回はDBも使う予定なので、actions でDBに接続した後、値を state に反映させています。

vuex

Nuxt.js は store ディレクトリにファイルを作成するだけでストアを有効化してくれるので便利ですね。今回は管理するのがユーザー情報だけなので、index.ts を作らず users.ts を直下に作りました。

Vuex ストアでデータを一元管理できるので、本当にわかりやすくなりました。コンポーネントを超えて共有される情報を管理するには、とても強力なツールだと思います。

一方で、Vuex を使用する際には、注意点もあります。
例えば、小さいレベルのコンポーネントからは Vuex を使用しない方が良いでしょう。
コンポーネントの使い回しが難しくなるのと、1画面の中で複数の箇所から Vuex のストアへの参照と変更が入り組んだ場合、処理が追いにくくなるためです。

参考:Vuexはなるべく避ける
   Vuexで何をするか、何をしないか

Vuex を利用する際は、Atom や Molecule レベルのコンポーネントの中では使わないstate を直接参照しない、などのルールを設けて運用したいと思います。

4. firebase 導入

DBとデプロイは firebase を利用します。
firebase は以前にも使ったことがありましたが、やはり firestore を使えば面倒な手間をかけずにDBが実装できますね。万歳。
デプロイも firebase の Hosting を使って行いました。

さらに、今回は社内向けツールなので、デプロイにあたり外部からのアクセスを制限するため、 Authentication を使用してログイン認証を追加します。管理者アカウントとしてユーザーを一人だけ登録し、簡単なPWを打てば誰でも使えるようにしました。
ログインしていない状態では認証後のページへ飛べないようにするため、強制的にindexへリダイレクトするmiddlewareも追加しています。

const auth = firebase.auth()

const middleware: Middleware = ({ route, redirect }) => {
  auth.onAuthStateChanged((user) => {
    if (!user && route.name !== 'index') redirect('/')
  })
}

また、Authentication の認証状態は、デフォルトではユーザーがブラウザを閉じた後でも永続的に維持されるようになっています。
今回は管理者アカウント一つだけの使用になるため、ログアウトせずにウィンドウを閉じてしまうと、他の人がアクセスした時でも前のセッションが続くことになります。
そこで、 firebase.auth().setPersistence メソッドで永続性タイプを SESSION に変更することで、ウィンドウやタブを閉じるたびにログイン状態がクリアされるようにしました。

ちなみに、Nuxt.js には nuxt/firebase というモジュールがあります。firebase をより簡単に利用できるので、興味があればご覧ください。

詰まったところ

sass の導入

sass の導入で沼りました。どうやら node のバージョン16 かつ M1チップ だと node-sass が動かないようです(2021/09 執筆時点)。node のバージョンを下げることで解決しました。

型定義

Vuex での型定義、props での型定義、firebase で扱う日時の型定義… 型推論が効かず、気づけばanyになっている型に苦しめられました。
特に Vuex は、ストアへアクセスするための便利な型がなく、今回使用した this.$store は Nuxt.js で型指定されていません。 自前の型を使うか、nuxt-typed-vuex を利用することで改善するしかないようです。

また、 TypeScript と Vuex の相性は良くなく、コンポーネントから store を呼び出したときに型安全が守られない、インテリセンスが効かないといった問題があります。
次に TypeScript で Vuex を扱う際は、Nuxt.js 公式で推奨されている vuex-module-decorators を使用したいと思います。

振り返り

さて、ここまで長々と書いてきましたが、今回のアウトプットを通して Nuxt.js と TypeScript の概要は大体掴めたかな、という感じです。 結論、Nuxt.js 便利!

  • 日本語ドキュメントが充実していてほぼ誰でも簡単に始めることができる
  • ルーティングを自分で作成する必要がない
  • SSRなどモードを選べるて柔軟なサイト設計ができる

…など、Nuxt.js を使うことで直感的にDOMの内容を操作でき、より簡単に抽選ツールを作ることができました。

また、TypeScript についても、型定義のおかげで、コンポーネント間のデータの受け渡しでもどういう型か判断でき、コードが見やすくなります。
さらに、力補完のおかげでコードを書く時間を短縮できる、エラーチェックを機械に任せることができる、など多くのメリットがありました。
一方で、Vuexとの連携は公式で推奨されているパッケージを使うなど、改善の余地があります。

最後に

以上、Nuxt.js を利用して抽選ツールを作成してみた内容の紹介でした。

最後まで読んでいただき、ありがとうございました。
どなたかの参考になれば幸いです。