Vue3 script setup + TypeScriptで書いてみた

Vue.js 3.2 からscript setup構文が使えるようになり、
単一ファイルコンポーネント(SFC)内で Composition API を使用している際に使える構文となります。
https://vuejs.org/api/sfc-script-setup.html

従来の Composition API における setup() 関数の内部の記述を、
純粋なJavaScript, TypeScriptのように、script直下に単純に変数や関数等を記述できます。

今回の記事ではscript setup構文における以下を簡潔に記載します。
- script setupの基本構文
- componentの読み込み
- component内のプロパティの公開方法
- Propsの書き方
- Emitの書き方

## 書き始める前に
VSCodeにおける.vue ファイルの拡張機能としてよく使われているVeturはscript setup構文には対応していません。
代わりに、Volarを使うことが推奨されています。
拡張機能を入れておきましょう。
https://marketplace.visualstudio.com/items?itemName=vue.volar

## まずはscript setupの記述をする

```vuejs:Sample.vue
<script setup lang="ts">
import { ref } from "vue";

const text = ref<string>('hello');

</script>

<template>
　　　　{{ text }}
</template>
```

従来のsetup()関数の記述の必要が無いため、非常にシンプルです。
textをバインドするためにreturnする必要もないため、従来の記法と比べて記述量が減ります。


## Componentを読み込む
```vuejs:Parent.vue
<script setup lang="ts">
import Child from "./components/Child.vue";

</script>

<template>
　　　　<Child />
</template>
```

componentsディレクトリ配下に作成したChild.vueをimportし、template内で呼び出す記述をするだけです。

## Component内のプロパティを明示的に公開する
script setupを使用したコンポーネントは、デフォルトで閉じられています。
つまり、この状態ではテンプレート参照により、親から子コンポーネントの値にアクセスする事ができません。
そこで、コンポーネントのプロパティを明示的に公開するには、defineExposeコンパイラマクロを使用します。
script setupが処理されるときにコンパイルされます。

以下にChild.vueのchildMethodを明示的に公開した場合の記述をします。

- 親componentの記載
```vuejs:Parent.vue
<script setup lang="ts">
const parentMethod = () => {
  childRef.value.childMethod() // コンソールに1と出力される
};
</script>

<template>
  <Child ref="childRef" />
  <button @click="parentMethod">子コンポーネント内のchildMethodを実行する</button>
</template>
```

- 子componentの記載
```vuejs:Child.vue
<script setup lang="ts">
const a: number = 1;
const childMethod = () => {
  console.log(a);
};

defineExpose({
  childMethod
});
</script>
```


## Propsを使う
propsを純粋なTypeScriptの構文で定義することができるようになりました。
script setupの中でpropsを宣言するには、definePropsのAPIを使用する必要があります。
definePropsは、script setup内でのみ使用可能なコンパイラマクロです。

- 親componentの記載
```vuejs:Parent.vue
<script setup lang="ts">
import { ref } from "vue";
import Child from "./components/Child.vue";

const hello = ref<string>('hello');

</script>

<template>
　　　　<Child :text="hello" />
</template>
```
子側でこれから定義するtextに対してhelloを渡します。

- 子componentの記載
```vuejs:Child.vue
<script setup lang="ts">
import { ref } from "vue";

interface Props {
 text: string;
}

const props = defineProps<Props>();
const text = ref<string>(props.text)

</script>

<template>
　　　　{{ text }} <!-- hello -->
</template>
```
props は defineProps() で定義されます。
interfaceで型定義をし、親側から渡ってきたhelloを使えるようにします。

## Emitを使う
emitもpropsと同様に、TypeScriptにより型定義をすることが可能です。
script setupの中でemitを宣言するには、defineEmitsのAPIを使用する必要があります。
defineEmitsは、script setup内でのみ使用可能なコンパイラマクロです。

- 親componentの記載
```vuejs:Parent.vue
<script setup lang="ts">
import Child from "./components/Child.vue";

const receiveText = (text: string) => {
 console.log(text) // コンソールにhelloと出力される
}

</script>

<template>
　　　　<Child @emitText="receiveText" />
</template>
```
親側ではreceiveText関数を用意し、子から渡ってくるtextをconsole.logで見れるようにしておきます。
emitTextが発火するとreceiveTextが実行されます。
emitText発火時にtextが渡るように子側で定義すれば、receiveText内でtextを使うことができます。
それではemitTextはこれから子側で定義していきます。

- 子componentの記載
```vuejs:Child.vue
<script setup lang="ts">
interface Emits {
  (e: "emitText", value: string): void;
}

const emit = defineEmits<Emits>();
const text: string = "hello"
const clickBtn = () => {
 emit("emitText", text)
}

</script>

<template>
　　　　<button @click="clickBtn">ボタン</button>
</template>
```
上記の場合、ボタンをクリックすると、clickBtnが実行され、emitTextが発火し、textを親側へ渡します。

## まとめ
まずはこの基本的な書き方ができれば、大方従来の書き方からscript setup構文に切り替える事はできるでしょう。
scirpt setup導入編として、少しでも参考にいただけますと幸いです。