Typescript 型についてもう少し詳しく TypeScript & Angular 勉強会 #2

## はじめに
- [前回](https://qiita.com/dairappa/items/f55fe1e7e210f28960b8) はちょっと詰め込みすぎたので、その振り返りともういくつかの重要な型についての話をします
- サンプルコードは基本的には[Typescript Playground](https://www.staging-typescript.org/play)で実行しています。（オプションはデフォルト）

---

## 前回のおさらい
- Typescriptでどのように型が定義されるか
    - [型アノテーション](https://qiita.com/dairappa/items/f55fe1e7e210f28960b8#%E5%9E%8B%E3%82%A2%E3%83%8E%E3%83%86%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3) と [型推論](https://qiita.com/dairappa/items/f55fe1e7e210f28960b8#%E5%9E%8B%E6%8E%A8%E8%AB%96) [^1]

- Typescriptの基礎型
    - [`any`](https://qiita.com/dairappa/items/f55fe1e7e210f28960b8#any)と[`unknown`](https://qiita.com/dairappa/items/f55fe1e7e210f28960b8#unknown)
        - `any` はTypescriptで避けるべきものの筆頭です。コンパイルエラーを通すためだけに `any` を使わないように
    - [空を表す基礎型: `null` と `undefined`](https://qiita.com/dairappa/items/f55fe1e7e210f28960b8#null-%E3%81%A8-undefined)
    - [無を表す基礎型: `void`と`never`](https://qiita.com/dairappa/items/f55fe1e7e210f28960b8#void-%E3%81%A8-never)
    - [オブジェクト型](https://qiita.com/dairappa/items/f55fe1e7e210f28960b8#%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E5%9E%8B)
    - [その他のJavascriptと共通の型](https://qiita.com/dairappa/items/f55fe1e7e210f28960b8#%E3%81%9D%E3%81%AE%E4%BB%96%E3%81%AE%E5%9F%BA%E7%A4%8E%E5%9E%8B)

- Typescript特有[^2]の型表現
    - [リテラル型](https://qiita.com/dairappa/items/f55fe1e7e210f28960b8#%E3%83%AA%E3%83%86%E3%83%A9%E3%83%AB%E5%9E%8B)
    - [型エイリアスの定義](https://qiita.com/dairappa/items/f55fe1e7e210f28960b8#%E5%9E%8B%E3%82%A8%E3%82%A4%E3%83%AA%E3%82%A2%E3%82%B9)
    - [合併型と交差型](https://qiita.com/dairappa/items/f55fe1e7e210f28960b8#%E5%90%88%E4%BD%B5%E5%9E%8Bunion)

---
### 補足1: オブジェクト型の省略可能メンバ
- オブジェクトを定義するとき、必ずしも必要ではない（あったりなかったりする）メンバがある場合、`?`でそれを表現できます。
- 代入されなかったオブジェクトは`undefined`となります（`null`ではないことに注意）

```ts

type Person = {
  firstName: string,
  middleName?: string,
  familyName: string
}

let japanese:Person = {firstName: "Taro", familyName: "Yamada"}
console.log(japanese.middleName) //undefined

```

---
### 補足2: 列挙型（`enum`)
- JavaやC#でおなじみの列挙型もあります
    - Javaほどの表現力はなく、C#っぽい感じ
- 特に何もしなければ、暗黙的に`number`型となる

```ts
enum Suit {
  Heart,
  Club,
  Diamond,
  Spade
}

let cardOfHeart: Suit = Suit.Heart
```

- `string`型の指定も可能

```ts
enum SuitString {
  Heart = "Heart",
  Club = "Club",
  Diamond = "Diamond",
  Spade = "Spade"
}
```

- まぜこぜもOK

```ts
enum Color {
  Red = '#c10000',
  Blue = '#007ac1',
  Pink = 0xc10050, //16進数
  White = 255
}
```

---
### ところで "typescript enum" で検索してみましょう

---
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447106/5581f0cc-77f8-5cbb-7e07-a7c65292d347.png)

---
### あとプログラミングTypescriptの注釈も見ましょう

---
> 列挙型の安全な使用には落とし穴が伴うため、**列挙型の使用は控えることをお勧めします。** TypeScript には、ほかにもよい表現方法がたくさんあります。 それでも、共同作業者が列挙型の使用を主張し、彼らの考えを変える手立てがない場合は、 彼らが外出している間にいくつかの TSLint ルールを忍者のように忍び込ませ、数値によるア クセスと const でない enum について警告が出るようにしてください。


---
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447106/16a43ed9-bd15-e7d9-12a1-87a7546b131c.png)



---
### 列挙体は危険な型
- 数値型の列挙体は数字ならなんでも代入できる
- `const` ではない列挙体はインデックスアクセスで存在しない要素にアクセスできる

```ts
enum Suit {
  Heart,
  Club,
  Diamond,
  Spade
}

let cardOfHeart: Suit = Suit.Heart
cardOfHeart = 1 //!!!
let suitInt:number = cardOfHeart

let club = Suit[1] //OK
let invalid = Suit[5] //!!!
console.log(invalid) //undefined
```

---
### 安全に列挙体を使うために
- 1. 文字列型を必ず利用する
- 2. const enum にして範囲外アクセスを避ける

```ts

const enum SuitString {
  Heart = "Heart",
  Club = "Club",
  Diamond = "Diamond",
  Spade = "Spade"
}

let cardOfDiamond: SuitString = SuitString.Diamond
cardOfDiamond = "Joker" //Type '"Joker"' is not assignable to type 'SuitString'.(2322)
cardOfDiamond = SuitString[2] //A const enum member can only be accessed using a string literal.(2476)
```

---
### そもそもTypescriptに列挙体は必要か？
- Union型で同様に安全な定義ができる

```ts

type SuitUnion = "Heart" | "Club" | "Diamond" | "Spade"
let spade: SuitUnion = "Spade"
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447106/9eedd013-6c93-6e50-10c8-e6ea4237975d.png)
※ 補完も効きます

- Union型で実現できないことがしたければ使ってもいいが、おそらく限定的
    - 要素の列挙[^3]
    - ビットフラグ的使い方


---
### 前回の課題おさらい①
- 以下のコードはエラーになります。なぜエラーになるかを説明してください。また、エラーにならないように修正してください。

```ts

function SomeOrNull(flag: boolean, value: string) {
  return flag ? value : null
}

let value = SomeOrNull(true, "Hoge")
console.log(value.length) // error!!
```

#### 参考回答
関数 `SomeOrNull` はUnion型 `string | null` を返す関数である。
したがって変数`value`の型は `string | null` 型であるため、このままでは `string` 型のプロパティである `length` を利用できないため、エラーとなる。
修正には、`typeof`演算子により、特定スコープ内でのみ`value`の型を`string`と推論できるようにする必要がある。

```ts

function SomeOrNull(flag: boolean, value: string) {
  return flag ? value : null
}

let value = SomeOrNull(true, "Hoge")

if(typeof value == 'string'){
  console.log(value.length)
}

```

#### 別解（推奨はしませんが、こういう逃げ道もあるということです）
```ts

function SomeOrNull(flag: boolean, value: string) {
  return flag ? value : null
}

let value:any = SomeOrNull(true, "Hoge")
console.log(value.length)

```


---
### 前回の課題おさらい②
- 交差型や合併型がうまく使えそうな事例を一つ考え、実際に型定義をしてみてください

#### 参考回答
##### 合併型（Union）
関数のレスポンスを、例外ではなく戻り値として表現する。

```ts

type Error = {
  errorCode: number,
  message: string,
  causedBy?: Error
}
type MaybeString = "string" | Error

function SomeOrError(flag: boolean, value: string) : MaybeString {
  return flag ? value : {errorCode: 1, "flag is not true"}
}
```

- 他の言語で`Optional`, `Maybe`, `Either` と呼ばれるもの（の基礎の基礎的な定義）です。




##### 交差型（Intersection）
- 軽量なクラス拡張として利用できる。

```ts

type Person = {
  firstName: string,
  middleName?: string,
  familyName: string
}

type Address = {
  prefecture: string,
  city: string
}

type Questionnaire = {
  question: string,
  answer: string,
  respondent: Person & Address
}

let qa: Questionnaire = {
  question: "好きな言語は？",
  answer: "typescript",
  respondent: {
    firstName: "山田",
    familyName: "太郎",
    prefecture: "Osaka",
    city: "Osaka"
  }
}
```
---
### 演習問題
以下のコードをenumではなくUnionを使う形に書き換えてください。

```ts
enum Tiles {
  None = 0,
  AllSimples = 1,
  AllRuns = 1 << 1,
  Reach = 1 << 2,
  SelfDraw = 1 << 3,
  FirstTurnWin = 1 << 4
}

function printTile(tiles: Tiles):string {
  let result = ""

  if(tiles & Tiles.Reach){
    result += "リーチ"
  }

  if(tiles & Tiles.FirstTurnWin){
    result += "一発"
  }

  if(tiles & Tiles.SelfDraw){
    result += "ツモ"
  }

  if(tiles & Tiles.AllSimples){
    result += "断么九"
  }

  if(tiles & Tiles.AllRuns){
    result += "平和"
  }
  

  return result
}

let tile = Tiles.Reach | Tiles.SelfDraw

console.log(printTile(tile)) //リーチツモ
```
#### ヒント
以下のような定義をすることで、Union型の要素一覧を持つことができる。（使わなくてもいいです）

```ts

const permissions = ['read', 'write', 'execute'] as const;
type Permission = typeof permissions[number]; // 'read' | 'write' | 'execute'
```

参考: https://www.kabuku.co.jp/developers/good-bye-typescript-enum


---



[^1]: 個人的には、型を最初に設計して明示しておきたいので、一時変数以外は型アノテーションを書きます
[^2]: もちろんHaskellやScalaなどの言語には先行して存在するので「特有」は言いすぎですが、JavaやC#などと比べると、という意味では特有と言っていいのかなと
[^3]: 定義を工夫することで、ほぼ同様のことは実現できる→ https://www.kabuku.co.jp/developers/good-bye-typescript-enum