MicroAd Developers Blog

マイクロアドのエンジニアブログです。インフラ、開発、分析について発信していきます。

初めてのTypeScript・Composition APIでつまづいた事とその解決方法

こんにちは。 マイクロアドでWebエンジニアをしている木田です。

今回は、新たに着手した画面開発の業務においてつまづいたこと、その解決方法についてお話しします。

はじめに

本記事では、以下の言語・フレームワークが利用可能な状態を前提に話を進めます。

  • TypeScript (v3.7.3)
  • Vue.js (v2.6.10)
    • Composition API (v0.3.4)

※過去の記事に、Vue3に向けてComposition APIを導入した話もありますのでこちらもぜひご覧ください。

■外部モジュールのimport文が全てコンパイルエラーとなる

つまづいたこと

まずは環境構築の時のお話です。 GitHubのリポジトリからcloneしてきてVSCodeで.vueファイルを開いた時にそれは起こりました。

なんと外部モジュールをimportしている箇所が全て真っ赤になっているのです。

シンタックスエラーは以下の内容でした。

モジュール xxx が見つかりません。ts(2307)

VSCodeで見たときはこんな感じになります。

ググってみると、tsconfigpathsの相対パス設定が間違っているのが原因という記事がいくつかありましたが、間違っているようには見えませんでした。

加えて、以下の点が私を混乱させました。

  • VSCode上ではシンタックスエラーとなっているが、サーバを起動してみると正常に画面が表示される。
  • .tsファイルのimport文はエラーになっていない。
  • 先に開発していた先輩数人に聞くと、同じ事象になっている人もいればなっていない人もいる。

解決方法

tsconfigに以下の設定を追加したら解決しました。

{
    "include": [
    "./src/**/*.ts",
    "./src/**/*.vue" // ←これを追加
  ]
}

原因はVolarというVSCodeのプラグインのバージョンの違いによるものでした。 Volarの古いバージョンではこの事象は起こらなかったので先輩の環境では起こらなかったようです。

以下のissueを参考にしたのでこちらもご覧ください。 https://github.com/johnsoncodehk/volar/issues/894#issuecomment-1024317577

■Ref型の変数を渡したつもりが、動かしてみるとアンラップされている

つまづいたこと

Composition APIのメリットの1つとして、リアクティブ性を保ったままのロジック分割が容易になった点が挙げられます。

私が触った画面でもロジック部分は分離しており、〇〇_model.tsというcomposition関数を記述するtsファイルを作成して、そこで実装した関数を.vueファイル側でimportして使用してます。

今回つまづいたことは、イベント発火のタイミングで関数を呼び出して、リアクティブ変数の値を変更したいと思った時に起こりました。

実際のコードではありませんが、以下のようなコードを書きました。

hoge.vue

<template>
  <section>
    <h1>カウンター</h1>
    <button @click="addCounter(countRef)">クリックして増加</button>
    {{ countRef }}
  </section>
</template>

<script lang="ts">
import { createComponent, ref } from '@vue/composition-api';
import { hogeModel } from '@/hogeDir/model/hoge_model';

export default createComponent({
  setup () {

    const countRef = ref(0);
    const { addCounter } = hogeModel();

    return {
      countRef,
      addCounter
    };
  }
});
</script>

hoge_model.ts

import { Ref } from '@vue/composition-api';

export const hogeModel = () => {
  const addCounter = (countRef: Ref<number>): void => {
    countRef.value++;
  };

  return {
    addCounter
  };
};

上記は、ボタンをクリックする度にリアクティブ変数がインクリメントされて画面に表示される、Vueのリアクティブの説明でよく出てくる例です。

IDE上で見た時にはどこにもコンパイルエラーは出ていないのですが、実際動かしてみると以下のエラーがconsoleに表示されます。

vue.esm.js?a026:628 [Vue warn]: Error in v-on handler: "TypeError: Cannot create property 'value' on number '0'"
TypeError: Cannot create property 'value' on number '0'

解決方法

解決方法としては、リアクティブな変数はsetup関数内で渡してあげるを意識することです。

ref関数を使用したリアクティブな変数はsetup関数でreturnするとリアクティブが取れてしまう(アンラップされる)ので注意が必要です。

これは、公式ドキュメントにも書いてあります。

ref がレンダーコンテキスト(render context - setup() によって返されるオブジェクト)のプロパティとして返されていてテンプレート内でアクセスされる場合、自動的に内部の値に浅くアンラップ(ref でラップされた値を取り出す)されます。

.vue, .tsファイルともに文法的に間違ったことは書いていないのでIDE上のエラーは出なかったみたいです。 TypeScriptは実行時には普通のJavaScriptのコードになります。よって、addCounter関数にnumber型を渡してしまい、number型にvalueプロパティを新規作成しようとしているので ダメヨ と怒られてしまっています。

実際に動かしてみて初めて気づくエラーです。

先程の例だと、composition関数(hogeModel)の引数にリアクティブな変数を渡してあげることで、リアクティブな状態を維持したままaddCounter関数でインクリメントできます。

hoge.vue

<template>
  <section>
    <h1>カウンター</h1>
    <button @click="addCounter()">クリックして増加</button>
    <!-- ↑でリアクティブな変数を渡すのをやめる -->
    {{ countRef }}
  </section>
</template>

<script lang="ts">
import { createComponent, ref } from '@vue/composition-api';
import { hogeModel } from '@/hogeDir/model/hoge_model';

export default createComponent({
  setup () {

    const countRef = ref(0);
    const { addCounter } = hogeModel(countRef); // ここでリアクティブな変数を渡す

    return {
      countRef,
      addCounter
    };
  }
});
</script>

hoge_model.ts

import { Ref } from '@vue/composition-api';

export const hogeModel = (countRef: Ref<number>) => {
  const addCounter = (): void => {
    countRef.value++;
  };

  return {
    addCounter
  };
};

おわりに

Composition APIとTypeScriptを初めて触った時につまづいたことを書いてみました。 同じような境遇の方のお役に立てれば幸いです。