【TypeScript】「関数定義の ...arg」と「関数呼び出しの ...arg」は違うんや？（可変長引数、スプレッド構文、オプショナル引数、コールバック関数）

# 1.はじめに
前回の記事は、関数の種類について記事を書きました。

https://qiita.com/daishiman/items/e497640caf68100454b9

今回は、関数の引数についてまとめました。

実務では、Laravel を使用していますが、今回の記事のレスト構文やスプレッド構文は全く使っていません…

なので、関数の引数にレスト構文やスプレッド構文を使うのは少し混乱しましたが、なんとか理解することができましたのでアウトプットとして記事を投稿しました。

基礎的な内容となっています。

@uhyo さんの「プロを目指す人のための TypeScript 入門」を元にキャッチアップしました。

間違って解釈している所ありましたら、ご指摘いただけますと幸いです。 



# 2.目次
[1.はじめに](#1はじめに)  
[2.目次](#2目次)  
[3.この記事でわかること](#3この記事でわかること)  
[4.環境](#4環境)  
[5.可変長引数の宣言](#5可変長引数の宣言)  
　[5.1.可変長引数の使い方](#51可変長引数の使い方)  
　[5.2.レスト引数と通常の引数と併用](#52レスト引数と通常の引数と併用)  
[6.関数呼び出し時に使うスプレッド構文](#6関数呼び出し時に使うスプレッド構文)  
　[6.1スプレッド構文の使い方](#61スプレッド構文の使い方)  
[7.オプショナル引数の設定](#7オプショナル引数の設定)  
　[7.1デフォルト値を設定しないオプショナル引数](#71デフォルト値を設定しないオプショナル引数)  
　[7.2デフォルト値を設定するオプショナル引数](#72デフォルト値を設定するオプショナル引数)  
　[7.3オプショナル引数の配置](#73オプショナル引数の配置)  
[8.コールバック関数](#8コールバック関数)  
　[8.1コールバック関数の使い方](#81コールバック関数の使い方)  
[9.おわりに](#9おわりに)  
[10.参考](#10参考)  


# 3.この記事でわかること

:::note info
本記事は、関数の便利な引数の使い方（基礎）の記事となります。

- 可変長引数の宣言方法
- 関数呼び出し時に使うスプレッド構文
- オプショナル引数の設定
- 関数の引数に関数を用いるコールバック関数

について理解ができます。

:::


このようなコードを理解することができます。

```ts
// 可変長引数を用いた関数を宣言
const giveLikeList = (...articles: string[]): string[] => {
    let result: Array<string> = [];
    for (const article of articles) {
        result.push(`${article}にいいね！する`);
    }
    return result;
};

const articles: string[] = ['この記事', '先週投稿した記事', '１ヶ月前に投稿した記事'];

// スプレッド構文を用いて関数の呼び出し
console.log(giveLikeList(...articles)); 

// オプショナル引数
const giveLikeList = (article: string, isLike?: boolean): string => {
    if (isLike) {
        return `${article}にいいね！する`;
    } else {
        return `${article}にいいね！しない`;
    }
};

// コールバック関数
const likeArticles = articles.map((article: Article): string => {
    return `${article.author} の ${article.title} にいいね！する`;
});
```

※ 全てのサンプルコードは、アロー関数を用いて記述しています。



# 4.環境


- TypeScript: 4.7.4
- Node.js: 16.15.1



# 5.可変長引数の宣言

では最初に、可変長引数について振れていきます。

可変長引数とは、**関数が任意の数の引数を受け取れるようにすること** です。

**レスト構文** を使うことで可変長引数を実現できます。

レスト構文の記事を以前書いたのでご参考にどうぞです。

https://qiita.com/daishiman/items/e0d9e2e16382231f6200

レスト引数構文と次の項で出てくるスプレッド構文は似ていますが挙動は異なります。

ざっくり説明すると、

- レスト構文: 複数の要素を集約して１つにまとめること
- スプレッド構文: 関数の呼び出しや、配列リテラル、オブジェクトリテラルに対して展開するのに使う

です。


本項では、このレスト引数構文を用いて、引数を一つにまとめます。



## 5.1.可変長引数の使い方

可変長引数（レスト引数構文）は、`...引数: 型` と記述して使います。

（レスト構文を引数に使うので、レスト構文引数と表現しています。同じ意味として捉えて頂ければ構いません。）

では、さっそく使っていきます。

```ts:
// 可変長引数（レスト引数構文）を使用
const giveLikeList = (...articles: string[]): string[] => {
    let result: Array<string> = [];
    for (const article of articles) {
        result.push(`${article}にいいね！する`);
    }
    return result;
};

// 実行結果：[ 'この記事にいいね！する', '先週投稿したに記事にいいね！する', '１ヶ月前に投稿したに記事にいいね！する' ]
console.log(giveLikeList('この記事', '先週投稿した記事', '１ヶ月前に投稿した記事'));

// 実行結果：[]
console.log(giveLikeList());
```

まずは、ざっくりと関数の説明をします。  
「それぞれの引数 `article` にいいね！するという文字列を返す」関数となっています。  
※ 特に、関数には意味はありません

`(...articles: string[])` この部分がレスト引数構文です。

レスト構文は、複数の要素を集約して１つにまとめるでしたよね。

このレスト引数に何がはいるかと言うと、  
`giveLikeList` 関数を使用する際に使う引数`(’この記事', '先週投稿した記事', '１ヶ月前に投稿した記事')`が `...articles`に代入されます。

```ts
giveLikeList('この記事', '先週投稿した記事', '１ヶ月前に投稿した記事')
```

この部分ですね。

今回は、３つの引数を `...articles` に代入していることになります。（3つ未満でも３つを超えても構いません）

`(...articles: string[])`に戻って細かく見てみます。

引数の型が、文字列の配列型となっています。

レスト引数は任意の数の引数を受け取れるので、配列の文字列型となっています。

もし引数の宣言を、配列にしないと

```ts:
// 引数を配列にしない
const giveLikeList = (...articles: string): string[] => {
    let result: Array<string> = [];
    for (const article of articles) {
        result.push(`${article}にいいね！する`);
    }
    return result;
};
```

コンパイルエラーとなります。

:::note alert

エラー内容

A rest parameter must be of an array type.  
レスト引数は配列型でなければならない。

:::

レスト引数構文は、要素を1つ1つ記述しなくていいので便利ですね。




## 5.2.レスト引数と通常の引数と併用

レスト引数は通常の引数と併用して使用できます。

```ts
// レスト引数と通常の引数を併用
const giveLikeList = (first: string, ...articles: string[]): string[] => {
    let result: Array<string> = [];
    result.push(`まずは、${first} にいいね！する`);
    for (const article of articles) {
        result.push(`その後、${article}にいいね！する`);
    }
    return result;
};

// 実行結果：[ 'この記事にいいね！する', '先週投稿したに記事にいいね！する', '１ヶ月前に投稿したに記事にいいね！する' ]
console.log(giveLikeList('この記事', '先週投稿した記事', '１ヶ月前に投稿した記事'));

// Expected at least 1 arguments, but got 0.
console.log(giveLikeList());
```

`(first: string, ...articles: string[])` このように、通常の引数とレスト引数を使用できます。

これは、関数呼び出し時の引数の
- 0 要素目が `first` に代入
- 0 要素目以降の残りの要素が `...article` に代入

されています。  
今回は、通常の引数は一つですが、**複数** 用いることも可能です。

注意しないといけないポイントが2つあります。

まず、1つ目が関数の呼び出し時の引数です。  

[5.1.可変長引数の使い方](#51可変長引数の使い方) では関数呼び出し時に **空** の引数でもエラーは出ませんでしが、

```ts:
// 実行結果：[]
console.log(giveLikeList());
```

今回の、通所の引数とレスト引数構文を組み合わせたものは、**空だとエラーとなります**。

```ts:
// Expected at least 1 arguments, but got 0.
console.log(giveLikeList());
```

レスト引数構文は、任意の数の引数を一つにまとめて配列とします。  
なので、レスト引数構文の中身が空でも、空配列となります。


通所の引数は、空では使えないので、

:::note alert 

エラー内容

Expected at least 1 arguments, but got 0.  
最低一つの引数が必要だが、与えられたのは引数が０こ

:::

というエラーが出てしまいました。


2つ目が、レスト引数構文の記述位置です。  

レスト引数構文は引数リストの最後に記述しないといけません。

どういうことかといいますと、

```ts
// レスト引数構文の後ろに通常の引数を記述
const giveLikeList = (...articles: string[], end: string): string[] => {
    let result: Array<string> = [];
    for (const article of articles) {
        result.push(`${article}にいいね！する`);
    }
    return result;
};
```

なぜできないかと言うと、レスト引数は、複数の要素を１つにまとめます。  
`...article` に呼び出しの引数全ての要素が代入されてしまい、`end` には何も代入することができないからです。


このように、通常の引数とレスト引数構文と組み合わせるときには、  
注意点がありますので、使用する際は気をつけないとです。



# 6.関数呼び出し時に使うスプレッド構文

前項では、可変長引数（レスト引数構文）でしたが、  
次は、関数の呼び出し時に使用するスプレッド構文についてです。

以前スプレッド構文の記事も書いているので、ご参考までにどうぞです。

https://qiita.com/daishiman/items/42ba60a223cadbd98104

スプレッド構文は、関数の呼び出しや、配列リテラル、オブジェクトリテラルに対して展開するのに使うんでしたね。

これを踏まえて見ていきます。

## 6.1.スプレッド構文の使い方

スプレッド構文は `...式` という構文で使用します。

では早速使ってみます。

使用する関数は、先程使ったものになります。

```ts:
const giveLikeList = (...articles: string[]): string[] => {
    let result: Array<string> = [];
    for (const article of articles) {
        result.push(`${article}にいいね！する`);
    }
    return result;
};

const articles: string[] = ['この記事', '先週投稿した記事', '１ヶ月前に投稿した記事'];

// 関数の呼び出しにスプレッド構文を使用
// 実行結果：[ 'この記事にいいね！する', '先週投稿したに記事にいいね！する', '１ヶ月前に投稿したに記事にいいね！する' ]
console.log(giveLikeList(...articles)); 
```

関数の呼び出しに使用するスプレッド構文は、`giveLikeList(...articles)` のように記述します。

`articles` の中に `['この記事', '先週投稿した記事', '１ヶ月前に投稿した記事’]` が代入されており、  
`...articles` で展開していることになります。

（スプレッド構文は展開、レスト構文はまとめるでしたね。）

したがって今回であれば、  

```ts:
giveLikeList(...articles);
```

は、

```ts:
giveLikeList('この記事', '先週投稿した記事', '１ヶ月前に投稿した記事`);
```

と同じことになります。

今回は、引数が３つと明確になっているので、あまり必要性が感じれれないかもしれませんが、  
**もっと多くの要素をもっている場合** や **何が入っているかわからないとき** などは非常に便利に使用できます。

スプレッド構文は、レスト引数構文とは異なり、引数リストのどこでもしようすることができます。


```ts:
/**
 * 実行結果
 * [
 *   'この記事にいいね！する',
 *   '先週投稿した記事にいいね！する',
 *   '１ヶ月前に投稿した記事にいいね！する',
 *   '最初に投稿した記事にいいね！する',
 *   'この記事にいいね！する',
 *   '先週投稿した記事にいいね！する',
 *   '１ヶ月前に投稿した記事にいいね！する'
 * ]
 */
console.log(giveLikeList(...articles, '最初に投稿した記事’, ...articles));
```

このように使用することもできます。

今回の例のように可変長引数とスプレッド構文を併用して使用します。

理由は、**配列の要素が何個あるか不明**なことが多いためです。


**関数宣言で可変長引数を使用せず** に、関数をスプレッド構文で呼び出すときには注意が必要です。


```ts:
// レスト引数構文を使わないアロー関数
const giveLikeList = (article1: string, article2: string, article3: string): string[] => {
    let result: Array<string> = [];
    for (const article of articles) {
        result.push(`${article}にいいね！する`);
    }
    return result;
};

const articles: string[] = ['この記事', '先週投稿した記事', '１ヶ月前に投稿した記事'];
console.log(giveLikeList(...articles));
```

このときエラー生じます。

:::note alert 

エラー内容

A spread argument must either have a tuple type or be passed to a rest parameter.  
スプレッド引数はタプル型であるか、レスト引数に渡される必要があります。

:::

アロー関数の引数が 3 つ、関数呼び出しの引数が 3 つ（スプレッド構文の中身が 3 つ）ですが、  
関数の呼び出しの引数が 3 つとは限らないためコンパイルエラーが発生しました。

これを回避するために、

```ts:
const giveLikeList = (article1: string, article2: string, article3: string): string[] => {
    let result: Array<string> = [];
    for (const article of articles) {
        result.push(`${article}にいいね！する`);
    }
    return result;
};

// 変数の要素数を指定
const articles: [string, string, string] = ['この記事', '先週投稿した記事', '１ヶ月前に投稿した記事'];
console.log(giveLikeList(...articles));
```

変数 `articles` の要素が 3 つであることを型で定義することでこのエラーを回避することができます。

複数の引数を簡単に展開できるのでスプレッド構文便利！！！


# 7.オプショナル引数の設定

次はオプショナル引数の設定です。

まず、オプショナル引数とは何かですが…  
**あってもいいしなくても良い引数** のことです。

オプショナル引数は、デフォルト値を設定 **しない** か **する** かで記述が異なります。


## 7.1.デフォルト値を設定しないオプショナル引数

まずはデフォルト値を設定しない時です。

デフォルト値を設定しないオプショナル引数は、`引数名?: 型` と記述し、下記のように使用します。

```ts:
// デフォルト値を設定しないオプショナル引数
const giveLikeList = (article: string, isLike?: boolean): string => {
    if (isLike) {
        return `${article}にいいね！する`;
    } else {
        return `${article}にいいね！しない`;
    }
};

console.log(giveLikeList('この記事'));        // この記事にいいね！しない
console.log(giveLikeList('この記事', true));  // この記事にいいね！する
console.log(giveLikeList('この記事', false)); // この記事にいいね！しない
```

ここでは、`isLike` の判定で、`article` にいいねするかしないかを決めるような関数に変えています。

` isLike?: boolean` の部分がデフォルト値を設定しないオプショナル引数です。

これは、関数を呼び出す時に引数を省略すると `undefined` 型が入ります。

`undefined` 型は条件分岐では、`false` に変換されるため、  
  
```ts
giveLikeList('この記事')
```

は、

```ts
giveLikeList('この記事’, undefined)
```

となり、

```ts
giveLikeList('この記事’, false)
```

ということになります。

なので、「この記事にいいね！しない」と返ってきます （← いいねしないはやめてくださいね）


## 7.2.デフォルト値を設定するオプショナル引数

次にデフォルト値を設定するときです。

`変数名: 型 = 式` と記述し、下記のように使用します。

```ts:
// デフォルト値を設定するオプショナル引数
const giveLikeList = (article: string, isLike: boolean = false): string => {
    if (isLike) {
        return `${article}にいいね！する`;
    } else {
        return `${article}にいいね！しない`;
    }
};

console.log(giveLikeList('この記事'));        // この記事にいいね！しない
console.log(giveLikeList('この記事', true));  // この記事にいいね！する
console.log(giveLikeList('この記事', false)); // この記事にいいね！しない
```

関数は先程と同じ内容です。

`isLike: boolean = false` の部分がデフォルト値を設定するオプショナル引数です。

これは、引数を省略するとデフォルト値の `false` を使用することになります。

```ts
giveLikeList('この記事')
```

は、

```ts
giveLikeList('この記事’, false)
```

となります。


## 7.3.オプショナル引数の配置

オプショナルな引数よりもあとに、普通の引数を置くことはできません。

どういうことかといいますと、

```ts
// デフォルト値を設定しないオプショナル引数
const giveLikeList = (isLike?: boolean, article: string): string => {
    if (isLike) {
        return `${article}にいいね！する`;
    } else {
        return `${article}にいいね！しない`;
    }
};
```

:::note alert

エラー内容

A required parameter cannot follow an optional parameter.  
必須引数は、オプショナル引数の後に続くことはできません。

:::

このように、デフォルト値を設定しないオプショナル引数は、コンパイルエラーで弾かれてしまいます。

では、次にデフォルト値を設定するオプショナル引数で見てみると、

```ts
// デフォルト値を設定するオプショナル引数
const giveLikeList = (isLike: boolean = false, article: string): string => {
    if (isLike) {
        return `${article}にいいね！する`;
    } else {
        return `${article}にいいね！しない`;
    }
};

console.log(giveLikeList( 'この記事'));       // Expected 2 arguments, but got 1.
console.log(giveLikeList(true, 'この記事'));  // この記事にいいね！する
console.log(giveLikeList(false, 'この記事')); // この記事にいいね！しない
```

:::note alert

エラー内容

Expected 2 arguments, but got 1.  
2つの引数を期待したが、1つになった。

:::

とアロー関数部分ではコンパイルエラーとなりませんが、関数を呼び出す時にエラーとなります。

以下は、私の解釈です。間違っていたらご指摘ください。、  
エラー内容は異なりますが、オプショナル引数を省略したかどうかが判断できないためエラーが生じました。  
（私の解釈です。間違っていたらご指摘ください。）


したがって、オプショナル引数とレスト引数とでは、併用することができません。

オプショナル引数の後に普通の引数は書けない。  
レスト引数の後ろに引数は書けない。

これらの条件より、併用することができません。



# 8．コールバック関数

関数の引数について記述してきました。  
最後に、関数の引数に関数を渡すコールバック関数に振れておきます。



## 8.1.コールバック関数の使い方

`map` 関数を例にして見ていきます。

```ts:
type Article = {
    author: string;
    title: string;
};

const giveLike = (article: Article): string => {
    return `${article.author} の ${article.title} にいいね！する`;
};

const articles: Article[] = [
    {author: 'daishiman', title: 'この記事'},
    {author: 'manju', title: 'あの記事'}
];

// 関数の引数に関数を使用（コールバック関数）
const likeArticles: string[] = articles.map(giveLike);
console.log(likeArticles);  // [ 'daishiman の この記事 にいいね！する', 'manju の あの記事 にいいね！する' ]
```

`map` 関数の引数に `giveLike` 関数を入れています。

```ts
const likeArticles: string[] = articles.map(giveLike);
```

`map` 関数の引数として使う `giveLIke` 関数自体は、  
引数 `article` を渡すことで `${article.author} の ${article.title} にいいね！する` という文字列を返します。

`map` 関数は、  

> 呼び出し元の配列内のすべての要素に対して提供された関数を呼び出した結果が入力され た新しい配列を作成します。

[MDN の Array.prototype.map() 参照](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)

したがって、

```ts
articles.map(giveLike);
```

は、`articles` の各要素を `giveLike` 関数に繰り返し適用して新しい配列を返します。

なので、

```ts
[
  'daishiman の この記事 にいいね！する',
  'manju の あの記事 にいいね！する'
]
```

を返します。

`giveLike` 関数の返り値の型は、`string` 型で、それを繰り返し適用して配列に返すので `articles.map(giveLike);` は`stirng[]` 型となります。


ちなみに、この`giveLike` 関数を直接 `map` 関数の引数に渡すこともできます。

```ts
const likeArticles = articles.map((article: Article): string => {
    return `${article.author} の ${article.title} にいいね！する`;
});
```

今回は `map` 関数を用いましたが、便利な関数はまだまだあるので、調べながら使っていきたいと思います。


# 9．おわりに

関数の引数としてレスト構文、スプレッド構文を使用することで、楽に便利に関数を使用することができます。

いくつか注意点等はありますが、TypeScript を使っていく上で必要不可欠なのでどんどん使いながら慣れていきたいと思います。


併せて他の記事も読んでいただけると嬉しいです🙇‍♂️

https://qiita.com/daishiman/items/a0573f2ae7f0e76d1895

https://qiita.com/daishiman/items/a8385926917b833f5e2f

https://qiita.com/daishiman/items/5acf803f252b82f970fe

https://qiita.com/daishiman/items/d382279a2a6cae3ea1aa

最後まで読んでいただきありがとうございました。




# 10．参考


[書籍：プロを目指す人のためのTypeScript入門　鈴木僚太［著］](https://twitter.com/uhyo_/status/1517158176365641729?s=20&t=5jZBSZVbLrAsgkCUruO7Ig) @uhyo
[【JavaScript】「スプレッド構文」と「レスト構文」について](https://qiita.com/kamijodev/items/89543ca267ac9ad51529)
[MDN の Array.prototype.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)