string型とundefind型を比較してもエラー起きないんですけど？

## はじめに
いきなりですが、以下のコードを読んで違和感を感じませんか？
ちょっとでも「ん？？😕」と思った方はこちらの記事を読んでいただけると嬉しいです🎉
```typescript
const text: string = 'example'
if (text === undefined){
    return
}
```
厳密等価演算子を使ったstring型とundefind型の比較で、コンパイルは通ったのですが違和感がありました。
簡単な例ですが、発見があったのでまとめます。

先に結論を書きます。

:::note warn
JavaScript（TypeScript）では、どんな型の変数も undefined と比較することができる
```typescript
undefined === undefined  // true
undefined === null       // false
undefined === 0          // false
undefined === ""         // false
undefined === true       // false
```
:::



## 背景
[WebSocketサーバの実装で導入しているライブラリ](https://github.com/socketio/socket.io)の型定義で誤りがあることに気がつきました。

ソースコードにコメントで記載されていますが、socketインスタンスはidプロパティを持ちます。このidプロパティはWebSocketのコネクションが開通するまではundefindであり、開通後は文字列が割り当てられます。
よって、型定義は`id: string | undefind`が正しいはずです。
実際にライブラリの最新バージョン（[v4.7.5](https://github.com/socketio/socket.io-client/blob/4.7.5/lib/socket.ts)）では、`id: string | undefind`と定義されています。

しかし、プロダクトに導入されていたv4.5.4では以下のように`id: string`と定義されていました。
```typescript:node_modules/socket.io-client/build/esm/socket.d.ts
    /**
     * A unique identifier for the session.
     *
     * @example
     * const socket = io();
     *
     * console.log(socket.id); // undefined
     *
     * socket.on("connect", () => {
     *   console.log(socket.id); // "G5p5..."
     * });
     */
    id: string;
    /**
```
要件として、コネクションが開通したら実行したい処理があったのでこのidプロパティを使って条件分岐することを検討しました。
型の誤りを見つけましたが、①実装上は問題なさそうなこと、②一気にライブラリのバージョンを上げるのはリスクがあることから、今回はこのままの型定義で条件分岐を実装することになりました。

## 実装
idプロパティはWebSocketのコネクションが開通するまでは`undefind`であり、開通後は文字列が割り当てられます。
この仕様をもとに、以下のような条件分岐を考えました。
```typescript
// コネクションが開通しているか確認
if (socket?.id === undefined) {
    return
}
    
// ...実行したい処理
```
実装自体はこれで問題なさそうです🎉
ですが、ここでものすごい違和感を感じました。

**`id: string`と定義されているのに、なぜ`undefiend`と比較してエラーが起きないの？？？**

例えば以下のように数字の`1`と比較した場合、当然コンパイルエラーが発生します。
```typescript
// 'string' 型と 'number' 型が重複していないため、この比較は意図したとおりに表示されない可能性があります。
if (socket?.id === 1) {
    return
}
```
気になってしまったので調べてみました。

## 調査
### string型とnumber型を比較したときエラーになる理由
まずは`socket?.id === 1` がエラーになる理由を考えます。
`socket.id`がstring型として宣言されているのに対し、`1`はnumber型です。
TypeScriptは**異なる型同士の比較を許可しない**ため、コンパイル時にエラーとなります。
これは直感に合っていますよね。

### string型とundefinedを比較したときコンパイルを通る理由
では`socket?.id === undefined`がエラーにならず、コンパイルを通ってしまう理由を考えます。
**JavaScript（TypeScript）では、どんな型の変数も undefined と比較することができます。**

ほんとか？？？？やってみた。
```typescript
undefined === undefined  // true
undefined === null       // false
undefined === 0          // false
undefined === ""         // false
undefined === true       // false
```
まじでエラーでないやん。
根拠となるようなドキュメントは探せなかったのですが、動作を見る限りは本当みたいです。

この仕様であれば、当然`socket?.id === undefined`のようにstring型とundefinedを比較したときにエラーは出ません。

### 自分なりに解釈してみる
仕様なので突っ込んでも仕方ないのですが、理由を生成AIで調査してみました。
> TypeScriptの型システムでは、undefined はほとんどの型と互換性があります。これは、変数が初期化されていない状態や、オプショナルな値を表現するためです。

要は、「JavaScriptでは値が代入されていない場合もundefinedだから、TypeScriptでnullチェックとかするときにundefinedと比較するのを許可しなかったらみんな困るよな？？」　的なニュアンスかなと解釈しました。

ここら辺、もうちょっと深く知ってみたい…

## 余談
ライブラリのidの型定義が間違っていたと記載しましたが、実際は`strictNullChecks`がtrueの場合はエラーになるが正しそうです。
こちらのコード、`strictNullChecks`が`false`の場合はエラーは出ません。（個人的にはとても気持ち悪い😥）
```typescript:strictNullChecksがfalseの場合
const example: string = undefined; // OK
```
```typescript:strictNullChecksがtrueの場合
const example: string = undefined; // NG
// error TS2322: Type 'undefined' is not assignable to type 'string'.

const example: string | undefined = undefined // OK
```
普段の実装では、`strictNullChecks`は`true`なので`undefined`を取りうるのに型定義に`undefined`が含まれないことにもとても違和感を感じました。

https://typescriptbook.jp/reference/tsconfig/strictnullchecks#null%E3%81%A8undefined%E3%81%8C%E4%BB%A3%E5%85%A5%E3%81%A7%E3%81%8D%E3%82%8B%E5%8D%B1%E9%99%BA%E6%80%A7

## 終わりに
`undefined`の扱い、いまだによく分かっていない気がします…難しい🥺
簡単な例ですが、違和感を持ったので調べてみたら発見がありました💡