Vue3でコンポーネントをexportしたらTypeScriptでエラーが発生する

# はじめに

Vue3を手が空いているときに書くようにして学習を最近しているのですが、Vue3に変わってComposition APIというものが増えていました

そこで今回は詰まった箇所があるので共有します。
なんとなくで書きながら学ぼうとするとふとしたところで結構時間を取られてしまいます。またTypeScript関係のエラーだったのでエラー文からは想像がつきませんでした、、

# 問題

以下の画面を作成しています

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/c5f23025-b05d-1f38-c4ad-0002c7d052b4.png)

ここでこの画面のレイアウトはコンポーネントとしており、`App.vue`でコンポーネントをインポートして表示しようとしています

```vue:Login.vue
<script setup lang="ts">
import { defineComponent, ref } from "vue";

export default defineComponent({
  name: "Login",
  setup() {
    const name = ref("");
    const roomNumber = ref();

    const changeName = (e: Event) => {
      if (e.target instanceof HTMLInputElement) {
        console.log(e.target.value);
        name.value = e.target.value;
      }
    };

    const changeRoomNumber = (e: Event) => {
      if (e.target instanceof HTMLInputElement) {
        console.log(e.target.value);
        roomNumber.value = e.target.value;
      }
    };
    return {
      name,
      roomNumber,
      changeName,
      changeRoomNumber,
    };
  },
});
</script>

<template>
  <div>
    <div class="container">
      <h1>チャットに参加する</h1>
    </div>
    <v-row class="container">
      <v-col cols="12">
        <v-form ref="form" lazy-validation>
          <v-text-field
            v-model="name"
            :value="name"
            v-on:input="changeName"
            label="お名前"
            required
            :rules="[(v) => !!v || 'お名前を入力してください']"
          />
        </v-form>
      </v-col>
    </v-row>
    <v-row class="container">
      <v-col cols="12">
        <v-form ref="form" lazy-validation>
          <v-text-field
            v-model="roomNumber"
            :value="roomNumber"
            v-on:input="changeRoomNumber"
            label="ルーム番号"
            required
            :rules="[
              (v) => !!v || 'ルーム番号を入力してください',
              (v) =>
                /^\d{1}$|^\d{2}$/.test(v) ||
                'ルーム番号は2桁以内の数字を入力してください',
            ]"
          />
        </v-form>
      </v-col>
    </v-row>
    <v-row class="container">
      <v-col cols="12">
        <v-btn color="info" block>ルームに参加</v-btn>
      </v-col>
    </v-row>
  </div>
</template>

<style>
.container {
  display: flex;
  justify-content: center;
}
</style>
```

このようにコンポーネントを作成したのですが、なぜかVSCode上で`export`の部分にエラーが表示されます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/b0af9b35-91b8-c1e1-0536-4b0fb2edefcc.png)

エラー内容は以下になります (わけがわからない)

```
class __VLS_setup.default
enum __VLS_setup.default
module __VLS_setup.default
(enum member) __VLS_setup.default
(enum member) __VLS_setup.default: DefineComponent<{}, {
    name: Ref<string>;
    roomNumber: Ref<any>;
    changeName: (e: Event) => void;
    changeRoomNumber: (e: Event) => void;
}, {}, {}, {}, ComponentOptionsMixin, ... 5 more ..., {}>
既定のエクスポートは、ファイルまたはモジュールの宣言のトップレベルにある必要があります。ts(1258)
```

ネットを調べたのですが、まったくそれらしい解決策が見つけられませんでした

# 解決方法

問題は`scriptタグ`に書かれている`setup`というメソッドでした

https://v3.ja.vuejs.org/api/sfc-script-setup.html#%E5%9F%BA%E6%9C%AC%E3%81%AE%E6%A7%8B%E6%96%87

このsetupを使うことで`export default defineComponet`の代わりを務めているようなので`setup`を消すことでエラーが消えました

しかし、`setup`という便利なシンタックスシュガーがあるのでもっとシンプルに以下のように書くことにしました

```vue:Login.tsx
<script setup lang="ts">
import { defineComponent, ref } from "vue";

const name = ref("");
const roomNumber = ref();

const changeName = (e: Event) => {
  if (e.target instanceof HTMLInputElement) {
    console.log(e.target.value);
    name.value = e.target.value;
  }
};

const changeRoomNumber = (e: Event) => {
  if (e.target instanceof HTMLInputElement) {
    console.log(e.target.value);
    roomNumber.value = e.target.value;
  }
};
</script>

<template>
  <div>
    <div class="container">
      <h1>チャットに参加する</h1>
    </div>
    <v-row class="container">
      <v-col cols="12">
        <v-form ref="form" lazy-validation>
          <v-text-field
            v-model="name"
            :value="name"
            v-on:input="changeName"
            label="お名前"
            required
            :rules="[(v) => !!v || 'お名前を入力してください']"
          />
        </v-form>
      </v-col>
    </v-row>
    <v-row class="container">
      <v-col cols="12">
        <v-form ref="form" lazy-validation>
          <v-text-field
            v-model="roomNumber"
            :value="roomNumber"
            v-on:input="changeRoomNumber"
            label="ルーム番号"
            required
            :rules="[
              (v) => !!v || 'ルーム番号を入力してください',
              (v) =>
                /^\d{1}$|^\d{2}$/.test(v) ||
                'ルーム番号は2桁以内の数字を入力してください',
            ]"
          />
        </v-form>
      </v-col>
    </v-row>
    <v-row class="container">
      <v-col cols="12">
        <v-btn color="info" block>ルームに参加</v-btn>
      </v-col>
    </v-row>
  </div>
</template>

<style>
.container {
  display: flex;
  justify-content: center;
}
</style>
```

```vue:App.vue
<script setup lang="ts">
import Login from "./components/Login.vue";
</script>

<template>
  <div className="App">
    <Login />
  </div>
</template>

<style>
.App {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}
</style>
```

returnで返す必要もないので大助かりです。これならVue3でも楽でいいなと思いました

# おわりに

今回はVue3を書いたときにはまったことについてまとめました
他のJavaScriptフレームワーク経験者がなんとなくでVue3を書くと案外はまってしまう落とし穴なのかなと思いました

# 参考

https://v3.ja.vuejs.org/api/sfc-script-setup.html#%E5%9F%BA%E6%9C%AC%E3%81%AE%E6%A7%8B%E6%96%87