知識ゼロから始めるTypeScript 〜応用編〜

## はじめに

[クラス編](https://qiita.com/yukiji/items/3db06601ece7f080b0d0)の続きです。
知識ゼロから始めるTypeScriptシリーズのラストパート応用編です。

予習はこちらから:point_down:
[知識ゼロから始めるTypeScript 〜基本編〜](https://qiita.com/yukiji/items/52ea9106e254c1a12dea)
[知識ゼロから始めるTypeScript 〜関数編〜](https://qiita.com/yukiji/items/614ff63ade4072731750)
[知識ゼロから始めるTypeScript 〜クラス編〜]
(https://qiita.com/yukiji/items/3db06601ece7f080b0d0)

様々な応用パターンを見ていきましょう。

## 型の互換性

- 型の互換性は常にコンパイラーによって評価されている
- 型の互換性が無いと警告で教えてくれる
- 型の互換性があると型が上書きされる

`any型`に`string型`を代入してみる

```ts
let fooCompatible: any
let barCompatible: string = 'TypeScript'

console.log(typeof fooCompatible) // undefined

fooCompatible = barCompatible // 代入できる any型はstring型に互換性がある

console.log(typeof fooCompatible) // string
```

`string型`に`number型`を代入してみる

そうすると互換性が無いのでエラーが起きる

```ts
let fooInCompatible: string
let barInCompatible: number = 1

fooInCompatible = barInCompatible // エラー string型にnumber型は互換性が無い
```

## ジェネリクス型

- 抽象的な型宣言
- `引き数の型`と`戻り値`の型が一致している必要がある
- 扱う型が違うだけで同じ処理をしている関数を使い回す事で、型の実装コストが削減できる

こんな感じでやってることは同じなのに扱う型だけ違う時に大活躍

```ts
// 型だけ違うけどやってることはほとんど一緒な関数
const echo = (arg: number): number => {
   return arg
}

const echo = (arg: string): string => {
   return arg
}
```

上記の関数をまとめてジェネリクスで定義してみる
型定義は慣習的に`T`とすることが一般的らしい
関数の引き数の前に`<T>`を定義する
関数を実行するときは`<>`内に実行したい型を定義する

```ts
// ジェネリクスで定義
const echo = <T>(arg: T): T => {
  return arg
}

// 実行
console.log(echo<number>(100)) // 100
console.log(echo<string>('Hello')) // Hello
console.log(echo<boolean>(true)) // true
```

クラスの場合はクラス名の後ろにジェネリクス型を定義する

```ts
class Mirror<T> {
  constructor(public value: T) {}

  echo(): T {
    return this.value
  }
}

console.log(new Mirror<string>('Hello').echo()) // Hello
console.log(new Mirror<number>(123).echo()) // 123
```

## 型アサーション

- 型の互換性がある時に、手動で型を変換させることができる
- 互換性がある場合のみに限定される

これはコードを見た方が分かりやすいかも

以下の様に`any型`で定義された変数`name`を`lengthメソッド`を使用して、変数`length`に代入する場合、本来であれば変数`length`は`number型`である必要があるのに`any型`で定義されてしまう。

```ts
let name: any = 'foo'

let length = name.length

length = 'foo' // 文字列を代入してもlengthはany型
```

厳密な型定義がTypeScriptの醍醐味なのでこれは避けたい。

これを実現するのが型アサーション
変数を`()`で括り`as`の後ろに型を手動で定義する
こうすることで変数`length`は本来あるべき姿(`number型`)になる

```ts
let length = (name as string).length

length = 'foo' // lengthはnumber型なのでコンパイルエラーになる
```

ちなみ下記の書き方は非推奨

JSXの記法と似ているのが理由らしい

```ts
// この書き方は非推奨
let length = (<string>name).length

length = 'foo' // lengthはnumber型なのでコンパイルエラーになる
```

## constアサーション

- 設定した値の再代入しかできない
- データの値を書き換えないことをコンパイラーに伝える

```ts
let nickName = 'foo' as const // as constを定義

nickName = 'buzz' // コンパイルエラー 
```

これ普通に`const`で定義すればよくない？と思いつつ

オブジェクトに対しても設定できるのが便利そう
オブジェクトの場合は`readonly`修飾子が設定される
どれだけネストされていても`readonly`になる

```ts
let profile = {
  name: 'foo',
  height: 175
} as const

// readonlyなのでエラーが発生する
profile.name = 'foo'
profile.height = 175
```

## Nullable Types

- 不確定な状態を持ちうる型
- ある値を設定したい時に、必ずしも設定したい値は確定しているとは限らない
- とりあえずまだわからないという状態を設定しておきたい

TypeScriptにはコンパイルオプションがある
コンパイルオプションは`tsconfig.json`で管理している

`tsconfig.json`の`"strictNullChecks"`を`false`に設定すると、どんな変数にも`null`を許容できる状態になる

```ts:tsconfig.json
{
  "compilerOptions": {
  ~ 省略 ~
  "strictNullChecks": false
}
```

```ts
let profile: { name: string; age: number } = {
  name: 'foo',
  age: null //nullでもエラーにならない
}
```

しかし、これは秩序のないコードになってしまう

このような場合は`union型`を使う

```ts
let profile: { name: string; age: number | null } = {
  name: 'foo',
  age: null
}
```

`tscofig.json`でグローバルな設定をするのではなく、`union型`を使用して局所的に対応する

## インデックスシグネチャ

オブジェクトにプロパティを新規で追加する度に型を定義する手間を防ぐ

基本的な書き方は`[index: string]: 型`


```ts
interface Profile {
  // オブジェクトのバリューには以下の型のいずれかを許容する
  [index: string ]: string | number | boolean
}

// 空のオブジェクトを設定
let profile: Profile = {}

profile.name = 'buzz'
profile.age = 30
profile.nationality = 'Japan'

console.log(profile) // { name: 'buzz', age: 30, nationality: 'Japan' }
```

## まとめ

応用の使い所がまだわからないけど、完璧より前進:runner_tone3: