ES2019で追加されたflatMap()の使いどころ

## はじめに

ES2019のアップデートで配列のメソッドflatMap()が追加されました。

はじめてflatMap()を見たときは「これはどんなときに使うのだろう」と思いました。
私のようにflatMap()をいつ使うのかピンと来ていない方も多いと思います。

しかし調べていくうちに色々使える事がわかったので、flatMap()の使いどころをご紹介します。

## flat()とは

まず事前知識として同じくES2019で追加されたflat()について説明します。
flat()は配列のメソッドで、自身の配列の次元を1つ下げたものを返します。

```js:2次元配列を1次元配列に変換
const example1 = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
];

example1.flat();
// 実行結果：[1, 2, 3, 4, 5, 6, 7, 8, 9]

const example2 = [
  1, 2, 3,
  [4, 5, 6],
  [[7], [8], [9]],
];

example2.flat();
// 実行結果：[1, 2, 3, 4, 5, 6, [7], [8], [9]]

const example3 = [
  1, 2, 3,
  4, 5, 6,
  7, 8, 9,
];

example3.flat();
// 実行結果：[1, 2, 3, 4, 5, 6, 7, 8, 9]
// 1次元配列の場合はそのまま
```

## flatMap()メソッドとは

flatMap()はmap()とflat()を合成したメソッドです。
`array.flatMap(callback)`は`array.map(callback).flat()`と同じ意味になります。

```js
const array = [1, 2, 3];
const callback = element => [element];

array.flatMap(callback);
// 実行結果：[1, 2, 3]
// 1. 関数callbackで配列の要素を配列にラップする
//    [1, 2, 3] => [[1], [2], [3]]
// 2. 次元を1つ下げる
//    [[1], [2], [3]] => [1, 2, 3]
```

また、以下のようにmap(), flat(), flatMap()を呼び出すと同じ意味になります。

```js
const array = [1, 2, 3];

// すべて同じ結果になる
array.map(element => element);
array.map(element => [element]).flat();
array.flatMap(element => [element])
```

## flatMap()でできること

map()を利用すると配列の要素の値を変更することができました。
一方で配列の要素を増やしたり、減らしたりするような配列の構造を変える操作はできませんでした。

この配列の構造を変える操作をしたいときにflatMap()が役立ちます。
callback関数の戻り値を工夫することで要素を増やしたり減らしたりすることができます。
具体的にどうやったら実現できるか見ていきます。

### 配列の要素を増やす

callback関数の戻り値に大きさが2以上の配列を返すと、呼び出し元の配列よりも長さが大きい配列を作ることができます。

下のコード例では配列の要素を2つに複製しています。

```js:配列の要素を２つに複製する
const array = [1, 2, 3]
const callback = element => [element, element]

array.flatMap(callback);
// 実行結果:[1, 1, 2, 2, 3, 3]
// 1. 関数callbackの処理
//    [1, 2, 3] => [[1, 1], [2, 2], [3, 3]]
// 2. 次元を1つ下げる
//    [[1, 1], [2, 2], [3, 3]] => [1, 1, 2, 2, 3, 3]
```

### 要素を削除する

要素を削除したい場合はcallback関数が空配列[]を返すようにします。

下のコード例では引数が奇数ならば空配列を返す関数をcallback関数にすることで、奇数の要素を配列から削除することを実現しています。

```js:奇数の要素を削除する
const array = [1, 2, 3]
const callback = element => element % 2 == 0 ? [] : [element]

array.flatMap(callback);
// [2]
// 1. 関数callbackの処理
//    [1, 2, 3] => [[], [2], []]
// 2. 次元を1つ下げる
//    [[], [2], []] => [2]
```

### 要素の次元を下げる

要素の次元を下げたい場合はcallback関数が引数をそのまま返すことで実現できます。

```js:要素の次元を1つ下げる
const array = [1, [2], [[3]]];
const callback = element => element;

array.flatMap(callback);
// [1, 2, [3]]
// 1. 関数callbackの処理
//    [1, [2], [[3]]] => [1, [2], [[3]]]
// 2. 次元を1つ下げる
//    [1, [2], [[3]]] => [1, 2, [3]]
```

## flatMap()で複雑な処理 

flatMap()を使うことで配列に複雑な処理行いたいときでも、callback関数の中だけで完結できるようになります。

下の例は[MDNのサイト](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap)から引用したflatMap()のコード例です。

```js:
const a = [5, 4, -3, 20, 17, -33, -4, 18]
//         |\  \  x   |  | \   x   x   |
//        [4,1, 4,   20, 16, 1,       18]

a.flatMap((n) =>
  (n < 0) ?      [] :
  (n % 2 == 0) ? [n] :
                 [n-1, 1]
)
```

上の例をflatMap()を使わないで書く場合は、filter()とreduce()を駆使して以下のように書くことができます。
ですがflatMap()を使ったほうがロジックがわかりやすく、可読性の高いコードになっています。

```js
a.filter(n => !(n < 0)).reduce((pre, n) => {
  if(n % 2 == 0) {
    return [...pre, n]
  }
  return [...pre, n - 1, 1]
}, [])
```

## まとめ

- flatMap()はmap()とflat()の合成メソッド
- flatMap()を使うと配列の要素を増やしたり減らしたりできる
- 配列に複雑の処理を行いたいときはflatMap()を使ってシンプルに書ける
- flatMap()はmap()の上位互換