SPAなビンゴゲームをVue2からVue3に移行した際に行った４つの事と、負債の返済

## どんなアプリ？

イベントでチョット使えるSPA(Single Page Application)なビンゴゲーム「[Bingo SPA](https://github.com/ma2shita/bingocard-spa)」です。

ビンゴマシンとビンゴカードがワンセットになっています。
ビンゴマシンはこんな感じで動きます。
![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22754/b52c5276-a087-abb8-4ff1-059c595bf61f.gif)
カードはこのようなものです。
![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22754/349838c3-c998-5292-9188-8b6f8dd5993e.gif)

ビンゴマシンとカードは連動していません。**連動していません！**(大切な事だから2回)
そのため、カードは手元に紙で用意して、ビンゴマシンだけ使う、もしくはその逆も可能となっています。

※カードに至っては、ビンゴ判定すらありません。誰かいい感じの実装をお願いします！

ちなみに [SORACOM UG #8](https://soracomug-tokyo.connpass.com/event/71357/) で使いました。あまりにも「あたらない」とネタになっていましたが。
https://uchimanajet7.hatenablog.com/entry/2017/12/16/144824

## Vue2 → Vue3 移行で行った事

私のVueアプリケーション作成は、`<script>`を読み込むケースです。npmではありません。

その上で、Vue2からVue3への移行で行った事は4つです。
全貌は[GitHubのdiff](https://github.com/ma2shita/bingocard-spa/commit/2b7c9f028f0ba004832e5c76a0b1a61373b3f24f?branch=2b7c9f028f0ba004832e5c76a0b1a61373b3f24f&diff=unified)をご覧ください。

1. `<script>` の読み込み先を変更
2. `new Vue()` から `Vue.createApp()` へ変更
3. `data` をオブジェクトから関数へ変更
4. `el` を削除して `mount()` へ変更

ともかくVue3のアプリが起動できてしまえば、問題がある部分はエラーが発生するので、あとは潰していけば良いでしょう。ともかく起動できるところまでが移行の第1ステップです。

以下、diffです。

`<script>`の読み込み先変更

```diff
-  <script src="//jp.vuejs.org/js/vue.min.js"></script>
+  <script src="//unpkg.com/vue@next"></script>
```

`Vue.createApp()` への変更、 `data` の関数化、 `el` の削除。

```diff
-  const app1 = new Vue({
-    data: {
-      options: [],
-      latest: null,
-      results: [],
-      shuffling: false
-    },
+  const app1 = Vue.createApp({
+    data() {
+      return {
+        options: [],
+        latest: null,
+        results: [],
+        shuffling: false
+      }
+    },
```

`mount()` への変更

```diff
  window.onload = function () {
+   app1.mount('#app1');
  }
```

### これ以降の調整

エラーや意図としない挙動に関しては、[Vue2からのマイグレーションガイド](https://v3.ja.vuejs.org/guide/migration/introduction.html#%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%B9%E3%82%BF%E3%83%BC%E3%83%88)を見ましょう。

## 負債の返済

このビンゴゲームには遊んでいる最中のリロード対策として、番号確定直後やカードをタップした毎に、 [`sessionStorage()`](https://developer.mozilla.org/ja/docs/Web/API/Window/sessionStorage) をへ状態を保存しています。

リロードの際には状態を復帰する必要があるわけですが、この実装を今まで(Vue2)の時には、Vueインスタンスを作った後に `Windows.onload()` 操作していました。
この実装はVue3では動きません。

```javascript:動くけど...
const app1 = new Vue({
  data: {
    card_id: null,
    cards: []
  },
  ...
});
window.onload = function () {
  if (sessionStorage.getItem('card_id') && sessionStorage.getItem('cards')) {
    app1.card_id = sessionStorage.getItem('card_id');
    app1.cards = JSON.parse(sessionStorage.getItem('cards'));
  }
};
```

本来このような操作は `created()` もしくは `mounted()` で実装すべきだったのです。
これはVue2の頃から存在する[インスタンスのライフサイクルにおけるフックメソッド](https://jp.vuejs.org/v2/guide/instance.html#%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB%E3%83%95%E3%83%83%E3%82%AF)で、ほかには `mounted()` や `destroyed()` と、インスタンスの状態の変更に合わせて発火します。

```javascript:created()を使った実装
const app1 = new Vue({
  data: {
    card_id: null,
    cards: []
  },
  created: function() {
    if (sessionStorage.getItem('card_id') && sessionStorage.getItem('cards')) {
      this.card_id = sessionStorage.getItem('card_id');
      this.cards = JSON.parse(sessionStorage.getItem('cards'));
    }
  },
  ...
});
```

最初からライフサイクルフックメソッドで実装しておけば、Vue3への移行で困ることは無かったわけです。
これは私が知らなかったというのが原因ですが、ここから得られた知見は「**副作用を利用すると、アップグレードに弱くなる**」ということです。

[Vue2からのマイグレーションガイド](https://v3.ja.vuejs.org/guide/migration/introduction.html#%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%B9%E3%82%BF%E3%83%BC%E3%83%88)には、フレームワークが想定している破壊的変更や注意点が列挙されます。即ち、正しくトラブルが発生するため対処もできます。一方、副作用はフレームワーク側から見れば想定外なのでサポートできませんし、それをサポートしろというのは無理があります。

今回負債が返済でき、良かったです。

## まとめ

**ソフトウェアは設計思想に合わせて利用すると、アップグレードに強い。**

ビンゴゲームはMITですので、お気軽にお使いください。
現場からは以上です！

EoT