TypeScript strict-boolean-expressionsのエラーとその解消方法について

## はじめに
最近、新規プロジェクトに配属されました。
if文を使って、値が`undefined`の場合の条件式を今まで通り、`!`を使って書いたところ、ESLintのエラーが発生しました。初めて見るエラーだったので、内容と解決方法について詳しく調べてみました。
- 言語：TypeScript
- ライブラリ：React

先にこの記事で伝えたいことをまとめておきます。

:::note info
- `strict-boolean-expressions`エラーは、**boolean型の値が必要な場所で、それ以外の型が指定された場合**に発生する
- **等価演算子**を使うと`null == undefined // => true`と判定される
- `x == null`と書いた場合、xが`null`か`undefined`の場合という条件を表すことができる
:::


## 問題
`startTime`は`Date | undefinrd型`をとるstateです。
`startTime===undefined`の場合は早期リターンしたかったので、条件分岐を`!`を使って以下のように書きました。
```react
const [startTime, setStartTime] = useState<Date | undefined>()

if (!startTime) {
  return 
}

```
すると`!startTime`に以下のようなESLintのエラーが発生しました。

![hooks_ts_—_narekan-web.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3537832/6f35a8cb-8ebe-de18-1fac-9895fb63ea27.png)

そして保存すると、勝手に以下のように変換され、エラーが解消されました。
```react
if (startTime == null) {
  return 
}
```
一連の流れでいくつかの疑問が浮かんだので、調べました。
## 疑問点
- このエラーの意味は何か
- なぜこれまでのプロジェクトではこのエラーが発生しなかったのか
- なぜ`startTime == null`でエラーが解消されるのか
- ソースコードにはどのように書くか

### このエラーの意味は何か
```
Unexpected nullable object value in conditional. An explicit null check is required.
```
今回発生しているESLintのエラーは`TypeScript strict-boolean-expressions`です。

https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/strict-boolean-expressions.md

公式ドキュメントによると、このエラーは、**boolean型の値が必要な場所で、それ以外の型が指定された場合**に発生します。

例えば以下の時、エラーが発生します。numがundefined型をとる可能性があるからです。
```typescript
// nullable numbers are considered unsafe by default
let num: number | undefined = 0;
if (num) {
  console.log('num is defined');
}
```
以下のように修正することでエラーは解消されます。
```typescript
// nullable values should be checked explicitly against null or undefined
let num: number | undefined = 0;
if (num != undefined) {
  console.log('num is defined');
}
```

今回発生したエラーのように、条件式に`!startTime`と書いてしまうと、startTimeが`falsy`のとき`true`と判定されます。つまり、本当は`undefined`のときだけ`true`と判定したいのに、`startTime`が0や空文字の場合にも`true`と判定されてしまうということです。

このような曖昧な評価を防ぐために`strict-boolean-expressions`は必要ということですね！

### なぜこれまでのプロジェクトではこのエラーが発生しなかったのか
以前のプロジェクトを確認したところ、ESLintの設定で`strict-boolean-expressions`がOFFになっていました。


### なぜ`startTime == null`でエラーが解消されるのか
まずは使用されている文法について考えます。

#### 等価演算子`==`
等価演算子では、**型が異なっていても同じ**と見なすことがあります。
厳密には、**値の型が異なる場合**は**型の変換**ができないか試みてから値が等しいかを比較します。
```JavaScript
// 等価演算子の場合
console.log(null == undefined); // => true
console.log(0 == "0"); // => true

// 厳密等価演算子の場合 
console.log(null === undefined); // => false
console.log(0 === "0"); // => false
```

nullとundefindが等しいというのは感覚と反していて気持ち悪いと思いました😕
厳密等価演算子はよく使いますが、等価演算子は実務で初めて使いました。実際、今回と同じ使い方以外ではあまり使わないみたいですね。

https://typescriptbook.jp/reference/values-types-variables/equality#いつとを使うのか使い分けるのか

よって、今回のように`startTime == null`と書いた場合、startTimeが`null`か`undefined`の場合という条件を表すことができます。
厳密等価演算子を使って表すと、`startTime === null || startTime === undefind`という条件になるので、前者の方が簡潔でいいですね。
```typescript
// 等価演算子の場合
if (startTime == null) {
  return 
}
// 厳密等価演算子の場合
if (startTime === null || startTime === undefind) {
  return 
}
```
### ソースコードにはどのように書くか
先輩とも相談して、今回はstartTimeがundefindの場合という条件が、より正確に示せた方がわかりやすいだろうと判断して、以下のように修正することにしました。

簡潔に書くことと、伝わりやすいかのどちらを優先するのかは、チームによっても考え方が異なりそうですね。
```typescript
const [startTime, setStartTime] = useState<Date | undefined>()

if (startTime === undefined) {
  return 
}
```

## まとめ
:::note info
- `strict-boolean-expressions`エラーは、**boolean型の値が必要な場所で、それ以外の型が指定された場合**に発生する
- **等価演算子**を使うと`null == undefined // => true`と判定される
- `x == null`と書いた場合、xが`null`か`undefined`の場合という条件を表すことができる
:::

## 終わりに
プロジェクトによって設定ルールが異なるため、ソースコードの書き方が異なることを学びました。
`strict-boolean-expressions`がONの時は、より厳密に判定条件を書く必要があるので注意していこうと思います！
また、`null==undefined =>true`となることも学びました。なかなか納得しにくいですが覚えておきます！