TypeScript Handbook の Advanced Types をちょっとだけ掘り下げる - その2 Nullable types

# はじめに

本記事は [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/basic-types.html) の [Advanced Types](https://www.typescriptlang.org/docs/handbook/advanced-types.html) に書かれているものをベースに、説明されている内容をこういう場合はどうなるのかといったことを付け加えて *ちょっとだけ* 掘り下げます。完全な翻訳ではなく、若干元の事例を改変しています。
今回は **Nullable types** について掘り下げます。

その1 Type Guards and Differentiating Types は [こちら](https://qiita.com/kobanyan/items/7a38ed5abad71cb8e93d)
その3 Type Aliases は [こちら](https://qiita.com/kobanyan/items/58134cac1f92e31595c2)
その4 String Literal Types / Numeric Literal Types は [こちら](https://qiita.com/kobanyan/items/190b5bb926fbeeeaebe3)
その5 Discriminated Unions は [こちら](https://qiita.com/kobanyan/items/ca56df27de50ec267995)
その6 Index types は [こちら](https://qiita.com/kobanyan/items/dcdf12bbd8b793fe5bd7)
その7 Mapped types は [こちら](https://qiita.com/kobanyan/items/2fdd6b0ed7411ff9980e)

# Nullable types

**デフォルト** の TypeScript では `null` と `undefined` は何に対しても代入可能になっています。

```typescript
let s = 'foo';
s = null;      // エラーなし
s = undefined; // エラーなし

function f(s: string) {
  // ...
}

f(null);      // エラーなし
f(undefined); // エラーなし
```

これは TypeScript が \<Type\> と宣言もしくは推測されたものを自動で `<Type> | null | undefined` として扱うようにしているためです。

宣言もしくは推測された型のみに限定し、 null および undefined を許容しないようにするには [コンパイラオプション](https://www.typescriptlang.org/docs/handbook/compiler-options.html) の `--strictNullChecks` を有効にします。


```typescript
let s = 'foo';
s = null;      // エラー
s = undefined; // エラー

function f(s: string) {
  // ...
}

f(null);      // エラー
f(undefined); // エラー
```

このオプションが有効な状態で null や undefined を許容したい場合には、許容したい内容を宣言する必要があります。

```typescript
let s: string | null | undefined = 'foo';
s = null;      // エラーなし
s = undefined; // エラーなし

function f(s: string | null | undefined) {
  // ...
}

f(null);      // エラーなし
f(undefined); // エラーなし
```

null と undefined は別物なので、 `string | null` と `string | undefined` も別物になります。

```typescript
let s: string | null = 'foo';
s = null;      // エラーなし
s = undefined; // コンパイルエラー

function f(s: string | undefined) {
  // ...
}

f(null);      // コンパイルエラー
f(undefined); // エラーなし
```

## Optional parameters and properties

オプショナルなパラメータやプロパティについての扱いは、デフォルトの場合、以下のようになります。

```typescript
function f(x: string, y?: string) {
  return `${x}:${y}`;
}
f('1', '2');       // '1:2' が戻る。
f('1', null);      // y は 'string | null | undefined' なので問題なし。 '1:null' が戻る。
f('1', undefined); // y は 'string | null | undefined' なので問題なし。 '1:undefined' が戻る。
f('1');            // 引数の値なしで呼び出された場合、 y は undefined として扱われ '1:undefined` が戻る。

interface I {
  a?: number;
}
let i: I = {};   // この時の i.a は undefined
i.a = 12;        // 'number | null | undefined' なので問題なし。
i.a = null;      // 'number | null | undefined' なので問題なし。
i.a = undefined; // 'number | null | undefined' なので問題なし。
```

`--strictNullCheck` を有効にすると、オプショナルなパラメータの型には `| undefined` が自動で足され以下のようになります。

```typescript
function f(x: string, y?: string) {
  return `${x}:${y}`;
}
f('1', '2');       // '1:2' が戻る。
f('1', null);      // コンパイルエラー。 y は 'string | undefined'
f('1', undefined); // y は 'string | null | undefined' なので問題なし。 '1:undefined' が戻る。
f('1');            // 引数の値なしで呼び出された場合、 y は undefined として扱われ '1:undefined` が戻る。

interface I {
  a?: number;
}
let i: I = {};   // この時の i.a は undefined
i.a = 12;        // 'number | null | undefined' なので問題なし。
i.a = null;      // コンパイルエラー。 i.a は 'number | undefined'
i.a = undefined; // 'number | null | undefined' なので問題なし。
```

## Type guards and type assertions

null もしくは undefined を許容している場合に null もしくは undefined を除外するには JavaScript と同様に以下のように書きます。

```typescript
function f(sn: string | null | undefined): string {
  if (sn === null || sn === undefined) {
    return 'default';
  } else {
    return sn;
  }
}
```

以下のように書けばもっと簡潔に書けます。

```typescript
function f(sn: string | null | undefined): string {
  return sn || 'default';
}
```

`--strictNullCheck` が有効な場合、 null もしくは undefined の可能性を除外できない状態の変数のプロパティにアクセスしようとするとエラーが発生します。

```typescript
function broken(name: string | null | undefined): string {
  function postfix(epithet: string) {
    return name.charAt(0) + '.  the ' + epithet; // コンパイラが name が null もしくは undefined になる可能性があるというエラーを出す。
  }
  name = name || 'Bob';
  return postfix('great');
}
```

この例では、 `name = name || 'Bob';` という一行があるので、 `name` の値が null もしくは undefined になることは現実的にはありませんが、エラーになります。(この理由は後述。)
こういう場合には、コンパイラにこの変数が null もしくは undefined になることはないということを教えてやる必要があります。やり方は変数の後に `!` をつけてやることです。

```typescript
function fixed(name: string | null): string {
  function postfix(epithet: string) {
    return name!.charAt(0) + '.  the ' + epithet; // ok
  }
  name = name || 'Bob';
  return postfix('great');
}
```

さて、前述の例でエラーが出る理由は、コンパイラがネストした関数のすべての呼び出しを追跡できないために null もしくは undefined の可能性を除外できないからです。

```typescript
function broken(name: string | null | undefined): string {
  function postfix(epithet: string) {
    return name.charAt(0) + '.  the ' + epithet;
  }
  name = name || 'Bob';
  name = undefined;
  return postfix('great');
}
```

順番を変えたところでエラーが出ることに変わりません。

```typescript
function broken(name: string | null | undefined): string {
  name = name || 'Bob';
  function postfix(epithet: string) {
    return name.charAt(0) + '.  the ' + epithet; // 相変わらずエラー
  }
  return postfix('great');
}
```

ただし、即時実行関数の場合はネストされていても判断できます。

```typescript
function iife(name: string | null | undefined) {
  name = name || "Bob";
  (function () {
    return name.charAt(0) + '.  the great'; // エラーなし
  })();
  name = undefined;
  (function () {
    return name.charAt(0) + '.  the great'; // エラー
  })();
}
```

なお、 `!` による null もしくは undefined の除外はコンパイラによるチェック自体をさせなくするようなものなので、実行時エラーの可能性が高まります。

```typescript
function broken(name: string | null | undefined): string {
  function postfix(epithet: string) {
    return name!.charAt(0) + '.  the ' + epithet; // コンパイルエラーは出なくなったが、実行時エラー
  }
  name = name || 'Bob';
  name = undefined;
  return postfix('great');
}
```

## まとめ

`--strictNullCheck` を使うことで、ガード処理を書かずに予期しない null や undefined な変数のプロパティへのアクセスを防ぐことができる。
コンパイラが null もしくは undefined の可能性があると言っている場合でも、 `!` を変数の後ろに付けることでエラーを回避することが可能。ただし、実行時例外の可能性が高まるので慎重に使う必要あり。