Nuxt.jsのComposition APIで学ぶ、TypeScriptの関数の型定義について

## はじめに
TypeScriptでは関数の型定義のやり方がいくつかあるため、
Compositon-APIで使いやすい方法に関してまとめてみます。

また、関数では`props`の値を取得して使うことも多いので`props`の型定義に関しても解説します。


## アロー関数
```vue:sample.vue
<script lang="ts">
import { defineComponent, ref, computed } from '@nuxtjs/composition-api';

export default defineComponent({
  setup() {
    // 引数がない場合
    const log = (): void => {
      console.log('log');
    };

    // 引数がある場合
    const bmi = (height: number, weight: number): number => {
      return weight / (height * height);
    };

    return {
      log,
      bmi,
    };
  },
});
</script>
```

## オプショナルパラメーター
`?`を使って、任意なパラメーターとすることができます

```vue:sample.vue
<script lang="ts">
import { defineComponent, ref, computed } from '@nuxtjs/composition-api';

export default defineComponent({
  setup() {
    const bmi = (height: number, weight: number, isConsole?: boolean): number | undefined => {
      const culc: number = weight / (height * height);
      if (isConsole) {
        return culc;
      }
    };

    return {
      bmi,
    };
  },
});
</script>
```

## デフォルトパラメーター
`=`を使って、デフォルトのパラメーターを設定することができます。

```vue:sample.vue
<script lang="ts">
import { defineComponent, ref, computed } from '@nuxtjs/composition-api';

export default defineComponent({
  setup() {
    const bmi = (height: number, weight: number, isConsole: boolean = false): number | undefined => {
      const culc: number = weight / (height * height);
      if (isConsole) {
        return culc;
      }
    };

    return {
      bmi,
    };
  },
});
</script>
```

## レストパラメーター
不特定多数の引数を配列として受けとってくれる構文です。

```vue:sample.vue
<script lang="ts">
import { defineComponent, ref, computed } from '@nuxtjs/composition-api';

export default defineComponent({
  setup() {
    const sum = (...values: number[]): number => {
      const reducer = (accumulator: number, currentValue: number): number =>
        accumulator + currentValue;
      return values.reduce(reducer);
    };
    const sumPrice = sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    console.log(sumPrice);
    return {
      sum,
    };
  },
});
</script>
```

## `props`を使った関数のケース
まずはイマイチな型定義のコードを見てみましょう！
このコードの問題点が分かりますか？

```vue:page.vue
<script lang="ts">
import { defineComponent, ref, computed } from '@vue/composition-api';

export default defineComponent({
  props: {
    size: {
      type: String,
      default: 'medium',
      validator: function (value: string) {
        return ['small', 'medium', 'large'].indexOf(value) !== -1;
      },
    },
    displayPagesPerPage: {
      type: Number,
      default: 5,
      validator: function (value: number) {
        return [5, 10].indexOf(value) !== -1;
      },
    },
    pages: {
      type: Array,
    },
  },
  setup(props: any) {
    // computed
    const getPageCount = computed((): number => {
      return Math.ceil(props.pages.length / props.displayPagesPerPage);
    });
    const classes = computed(() => {
      return {
        'page-container__small': props.size === 'small',
        'page-container__medium': props.size === 'medium',
        'page-container__large': props.size === 'large',
      };
    });

    return {
      // computed
      getPageCount,
      classes,
    };
  },
});
</script>
```

問題点は以下の4点です。
①`props`の`size`にはvalidatorで3つの値しか取れないが型定義は文字列であれば何でも良くなっている
②`props`の`displayPagesPerPage`にはvalidatorで2つの値しか取れないが型定義が数字であれば何でも良くなっている
③`props`の`page`の型定義が配列だけであり、この中身が文字列なのか数値なのかわからない
④`setup`の`props`の型定義が`any`となっている

それでは、それぞれ解決していきましょう。

### ①`props`の`size`にはvalidatorで3つの値しか取れないが型定義は文字列であれば何でも良くなっている

Composition-APIには、`PropType`という型関数が用意されているので、
それを使うことで型の補完が効くようになります。

```vue:page.vue
<script lang="ts">
import { defineComponent, ref, computed, PropType } from '@vue/composition-api';

// types
import { PageSize } from './types/PageInterface';

export default defineComponent({
  props: {
    size: {
      type: String as PropType<PageSize>,
      default: 'medium',
      validator: function (value: string) {
        return ['small', 'medium', 'large'].indexOf(value) !== -1;
      },
    },
  },
});
</script>
```

```typescript:types/PageInterface.ts
export type PageSize = 'small' | 'medium' | 'large';
```

### ②`props`の`displayPagesPerPage`にはvalidatorで2つの値しか取れないが型定義が数字であれば何でも良くなっている

こちらも上記と同様です。

```vue:page.vue
<script lang="ts">
import { defineComponent, ref, computed, PropType } from '@vue/composition-api';

// types
import { PageDisplayPagesPerPage } from './types/PageInterface';

export default defineComponent({
  props: {
    displayPagesPerPage: {
      type: Number as PropType<PageDisplayPagesPerPage>,
      default: 5,
      validator: function (value: number) {
        return [5, 10].indexOf(value) !== -1;
      },
    },
  },
});
</script>
```

```typescript:types/PageInterface.ts
export type PageDisplayPagesPerPage = 5 | 10;
```

### ③`props`の`page`の型定義が配列だけであり、この中身が文字列なのか数値なのかわからない
今回は、文字列と仮定しました。

```vue:page.vue
<script lang="ts">
import { defineComponent, ref, computed, PropType } from '@vue/composition-api';

// types
import { PagePages } from './types/PageInterface';

export default defineComponent({
  props: {
    pages: {
      type: Array as PropType<PagePages>,
    },
  },
});
</script>
```

```typescript:types/PageInterface.ts
export type PagePages = string[];
```

### ④`setup`の`props`の型定義が`any`となっている

```vue:page.vue
<script lang="ts">
import { defineComponent, ref, computed, PropType } from '@vue/composition-api';

// types
import { PageSize, PageDisplayPagesPerPage, PagePages, Props } from './types/PageInterface';

export default defineComponent({
  props: {
    size: {
      type: String as PropType<PageSize>,
      default: 'medium',
      validator: function (value: string) {
        return ['small', 'medium', 'large'].indexOf(value) !== -1;
      },
    },
    displayPagesPerPage: {
      type: Number as PropType<PageDisplayPagesPerPage>,
      default: 5,
      validator: function (value: number) {
        return [5, 10].indexOf(value) !== -1;
      },
    },
    pages: {
      type: Array as PropType<PagePages>,
    },
  },
  setup(props: Props, context: any) {
    // computed
    const getPageCount = computed((): number => {
      return Math.ceil(props.pages.length / props.displayPagesPerPage);
    });
    const classes = computed(() => {
      return {
        'page-container__small': props.size === 'small',
        'page-container__medium': props.size === 'medium',
        'page-container__large': props.size === 'large',
      };
    });

    return {
      // computed
      getPageCount,
      classes,
    };
  },
});
</script>
```

```typescript:types/PageInterface.ts
export type PageSize = 'small' | 'medium' | 'large';
export type PageDisplayPagesPerPage = 5 | 10;
export type PagePages = string[];

export interface Props {
  size: PageSize;
  displayPagesPerPage: PageDisplayPagesPerPage;
  pages: PagePages;
}
```