TypeScript は forEach での変数の更新を認識できないようだ

# コード例

例として、「x 座標が正である点の中で、x 座標が最小の点」を求めるプログラムを書いてみた。

```typescript
interface Point {
    x: number;
    y: number;
}

const points: Point[] = [
    {x: 7, y: 5},
    {x: -1, y: 20},
    {x: 3, y: 10},
];

let selectedPoint : Point | null = null;

points.forEach((point) => {
    if (point.x > 0 && (!selectedPoint || selectedPoint.x > point.x)) {
        selectedPoint = point;
    }
});

if (selectedPoint) {
    console.log(selectedPoint.x);
}
```

このコードを [Playground (v5.3.3) に入力](https://www.typescriptlang.org/play?target=9&jsx=0&ts=5.3.3#code/JYOwLgpgTgZghgYwgAgAoHtRmQbwFDKHIAeAXMiAK4C2ARtANwFECe5VdjeAvnnguhABnbAAdM4IeQxYA2gF1kAXmSzmhHGWQB2ADTI2yAKzdd63FoC0ARn2GATAAZT5zeQDMd8tedn5TPAAbCGwhCGCESAATGXBkaQlsAB8KSkDA5VT0gPEsIQA6GHQoAFFEAAsACkrc8ABKZQA+XHNgGGQaxPziZGbHZAAyAY6AQjCI6NjklPGISIgYrp7m2rBuuob8Im3kWfnFrEzVpm3ebjqAto69ycTN8wFhdGD8wPQAc0qbhan1pl4gA)すると、以下のエラーが出てしまった。

> Property 'x' does not exist on type 'never'.

このエラーは、以下の行で発生している。

```typescript
    console.log(selectedPoint.x);
```

`forEach` 内で `selectedPoint` を更新する可能性があるにもかかわらず、上記の行の前の行

```typescript
if (selectedPoint) {
```

において、`selectedPoint` の型がなぜか `null` になってしまっており、この `if` 文の中では `never` になってしまっている。

このコードを実行してみると、

```text
[LOG]: 3 
```

と出力され、正しく求まっていることがわかる。

# 回避法

:::note warn
この章の地の文には、**個人の感想**が含まれる。
:::

## 普通の for 文を使う

`forEach` なんていうオシャレな記法はやめて、普通の `for` 文で普通に実装する。
先ほどのコードからの変更が最小限であり、素直で良い書き方である。

```typescript
interface Point {
    x: number;
    y: number;
}

const points: Point[] = [
    {x: 7, y: 5},
    {x: -1, y: 20},
    {x: 3, y: 10},
];

let selectedPoint : Point | null = null;

for (let i = 0; i < points.length; i++) {
    const point = points[i];
    if (point.x > 0 && (!selectedPoint || selectedPoint.x > point.x)) {
        selectedPoint = point;
    }
}

if (selectedPoint) {
    console.log(selectedPoint.x);
}
```

## reduce を使う

ループ内で値を更新していくということで、`reduce` を用いる。
オシャレさは維持できるが、初期値が後ろに行くことでわかりやすさが下がる可能性がある。

```typescript
interface Point {
    x: number;
    y: number;
}

const points: Point[] = [
    {x: 7, y: 5},
    {x: -1, y: 20},
    {x: 3, y: 10},
];

const selectedPoint = points.reduce((selectedPoint: Point | null, point) => {
    if (point.x > 0 && (!selectedPoint || selectedPoint.x > point.x)) {
        selectedPoint = point;
    }
    return selectedPoint;
}, null);

if (selectedPoint) {
    console.log(selectedPoint.x);
}
```

## アルゴリズムを切り替える

「x 座標が正である点の中で、x 座標が最小の点を求める」という処理を、

1. x 座標が正である点を求める
2. その中で x 座標が最小の点を求める

という処理に分割する。
オシャレさはさらに上がる。
このような分割が可能な特殊な場合向け。

```typescript
interface Point {
    x: number;
    y: number;
}

const points: Point[] = [
    {x: 7, y: 5},
    {x: -1, y: 20},
    {x: 3, y: 10},
];

const positivePoints = points.filter((point) => point.x > 0);
const selectedPoint = positivePoints.length > 0
    ? positivePoints.reduce((acc, point) => point.x < acc.x ? point : acc)
    : null;

if (selectedPoint) {
    console.log(selectedPoint.x);
}
```

## as でごまかす

`as` で**強制的に**型を指定し、エラーを**握りつぶす**。
**考えたくない人向け。**

```typescript
interface Point {
    x: number;
    y: number;
}

const points: Point[] = [
    {x: 7, y: 5},
    {x: -1, y: 20},
    {x: 3, y: 10},
];

let selectedPoint : Point | null = null;

points.forEach((point) => {
    if (point.x > 0 && (!selectedPoint || selectedPoint.x > point.x)) {
        selectedPoint = point;
    }
});

if (selectedPoint) {
    console.log((selectedPoint as Point).x);
}
```

# まとめ

以下の条件を満たす書き方をするのがいいだろう。

* `as` などの**強行突破**をなるべくしない
* まわりから怒られない (コーディング規約に違反しないなど)
* 自分がわかりやすい・書きやすい