[TypeScript] 型修飾子についての理解を深めよう！

### はじめに
今回は **型修飾子** についてまとめております。

### 【目次】
[まずは基本的な型宣言をマスターしよう！](https://qiita.com/PenPe/items/c189a611b3407b7ceef9)
[オブジェクト型の型宣言をマスターしよう！](https://qiita.com/PenPe/items/440683cc6e5e4ac0ca32)
[関数の型定義をマスターしよう！](https://qiita.com/PenPe/items/bff109c6f83f46d191a7)
[配列をマスターしよう！](https://qiita.com/PenPe/items/7e056eec2cce76a745a9)
[インターフェースをマスターしよう！](https://qiita.com/PenPe/items/f9314aeccb2f23cac382)
[クラスを使いこなそう！](https://qiita.com/PenPe/items/c8e6289f386c4db23a6f)
[型修飾子についての理解を深めよう！](https://qiita.com/PenPe/items/7af57861e93830fe9a79) ←🈁
[ジェネリックをマスターしよう！](https://qiita.com/PenPe/items/fde6a342e746e265d907)
[独自の構文拡張をマスターしよう！](https://qiita.com/PenPe/items/c79cdb5bc4a68a1dfd7f)

---

### トップ型
全てのデータを割当できるデータ型です。
- `any`
- `unknown`

#### any
指定することでTypeScriptによる型チェックが行われなくなります。
型チェックの恩恵が失われてしまうので、**なるべく利用しない**ようにしましょう。

```typescript
function fn(input: number) {
     // NG: Property 'toUpperCase' does not exist on type 'number'.
     input.toUpperCase()
}

function anyFn(input: any) {
     // OK
     input.toUpperCase()
}
```

#### unknown
全てのオブジェクトを割り当てることが可能
`any`との違いは `unknown`型のメンバーに直接アクセスすることが禁止されているということです。

サンプルコードを見ていきましょう.
```typescript
function fn(input: unknown) {
     // NG: 'input' is of type 'unknown'.
     input.toUpperCase();
}
```

input`に対して、`toUpperCase()`を実行した際に型エラーが発生しています。

`typeof`, `instanceof`などで対象の型を絞り込むことでメンバーへのアクセスが可能です。
```typescript
function fn(input: unknown) {
     if(typeof input === "string") {
          input.toUpperCase();
     }
}
```

上記理由から、`any`よりも`unknown`を優先的に利用しましょう。

---

### ユーザー定義型ガード
TypeScriptでは、引数が特定のデータ型を返すか関数のための**ユーザー定義型ガード**が用意されています。

以下のサンプルコードを見ていきましょう。
```typescript
function isString(input :unknown) {
    return typeof input === "string"
}

function toUpperCaseIsString(input: string | number) {
    if(isString(input)) {
        // NG: Property 'toUpperCase' does not exist on type 'string | number'.
        console.log(input.toUpperCase())
    }
}
```

`input.toUpperCase()`で型エラーが発生しています。

我々開発者は`isString(input :unknown)`が、**引数が`string`型であれば`true`** を返すことをすぐ認識できますが、TypeScriptはそうではありません。

---

以下のように関数パラメータの後に引数の戻り値を指定する必要があります。
これで型エラーが解消されます。

```typescript
function isString(input :unknown): input is string {
    return typeof input === "string"
}

function toUpperCaseIsString(input: string | number) {
    if(isString(input)) {
        // OK
        console.log(input.toUpperCase())
    }
}
```

---

### keyof
`keyof`はオブジェクトのキーの合併型を作成します。

以下のサンプルコードでは、`key`が`first`, `second`以外の値を取りうることができるため、型エラーが発生します。

```typescript
interface ScoreObj {
    first: number
    second: number
}

function getScore(score: ScoreObj, key: string){
    //NG: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'ScoreObj'.
    console.log(score[key])
}
```

---

`keyof`を使用することで型エラーが解消されます。
```typescript
interface ScoreObj {
    first: number
    second: number
}

function getScore(score: ScoreObj, key: keyof ScoreObj){
    console.log(score[key])
}
```

この場合、`key`は`ScoreObj`キーの合併型（`"first" | "second"`）と評価されます。

---

### typeof
`typeof`は与えられた値の型を返します。

```typescript
const hasNameAge = {
    name: "taro",
    age: 30
}

let person: typeof hasNameAge;
```

上記の宣言は、以下のように宣言と同意義です。
コードがスッキリしますね。
```typescript
let person: {
    name: string;
    age: number;
}
```

#### typeof + keyof
`typeof` と `keyof` を活用することで特定のオブジェクトのキーの合併型を簡単に定義できます。

```typescript
let sampleObj = {
     value1: 100,
     value2: 500,
}

function getObjectValue(key: keyof typeof sampleObj) {
     console.log(sampleObj[key]);
}

//OK
getObjectValue("value1")
getObjectValue("value2")

//NG: Argument of type '"value3"' is not assignable to parameter of type '"value1" | "value2"'.
getObjectValue("value3")
```

### 型キャスト
**型キャスト** を利用することでTypeScriptによる型解釈を上書きすることが出来ます。

#### ユースケース
基本的に型キャストの利用は推奨されませんが、`JSON.parse`のようにトップ型（`any`）を返すメソッドがあります。
戻り値のデータ型を予め定義できる場合は、以下のように型キャストを使用します。

```typescript
const data = `["hoge", "foo"]`;

// OK: any
let anyData = JSON.parse(data);

// OK: string[]
let stringArg = JSON.parse(data) as string[];

// OK: [string, string]
let StringTuple = JSON.parse(data) as [string, string];
```

---

### 非nullアサーション
変数の値として `null, undefined`があり得るものの、実際のデータはそうでないことが確証できることがあります。

データから `null, undefined`を取り除く「`!`」がTypeScriptには用意されています。

```typescript
const sampleMap = new Map([
    [1, "first"],
    [2, "second"]
])

sampleMap.get(2)!.toUpperCase();
```

`sampleMap.get(2)`は `string | undefined`ですが、
`sampleMap.get(2)!`として実行することで値が`string`型として上書きされています。

そのため、型エラーが発生することなく、`toUpperCase()` を実行できます。

---

### const
TypeScriptには `const`アサーションが用意されており、指定した値を不変として扱います。
constアサーションはデータ型ごとに異なる振る舞いをします。

- 配列
    - 配列を`readonly` のタプル型として扱う
- リテラル
    - 一般的なプリミティブ型ではなく、リテラル型として扱う
- オブジェクトプロパティ
    - `readonly` として扱う

サンプルコードを見ていきましょう。
#### 配列
```typescript
// let args: string[]
let args = ["value1", "value2"];

// let argsAsConst: readonly ["value1", "value2"]
let argsAsConst = ["value1", "value2"] as const;
```

#### リテラル
```typescript
// let one: number
let one = 1;

// let oneAsConst: 1
let oneAsConst = 1 as const;
```

#### オブジェクト
```typescript
const sampleObj = {
    name: "Taro",
    age: 25
}

/*
const sampleObj: {
    name: string;
    age: number;
}
*/

const sampleObjAsConst = {
    name: "Taro",
    age: 25
} as const

/*
const sampleObjAsConst: {
    readonly name: "Taro";
    readonly age: 25;
}
*/
```

特定のライブラリなどで、フィールドが特定のリテラル型を求める場合があります。
その際は `as const`キーワードを使用するようにしましょう。

---

### まとめ
以上です〜
次の記事ではジェネリック型についてまとめる予定です。