【TIPS】TypeScriptの型定義を思い出す

同じ日に複数の言語を使って開発作業をすることはよくあります。
その時に、どう書けるのか分からなくなることが多く、都度調べつつ作業をすることが多いです。
その調べる時間を削減するために、個人的なメモのような形でまとめていきたいと思います。


# 触れない内容
1. [index.d.ts](https://typescript-jp.gitbook.io/deep-dive/type-system/intro/d.ts)
1. [@types](https://typescript-jp.gitbook.io/deep-dive/type-system/types)
1. [interface が良いか、type が良いか](https://typescriptbook.jp/reference/object-oriented/interface/interface-vs-type-alias)
1. JavaScript の書き方 や 基礎知識
1. 細かいロジックや説明



# 参考サイト
* https://www.typescriptlang.org/docs/
    - https://www.typescriptlang.org/ja/docs/
* https://www.typescriptlang.org/cheatsheets



# 基本的な使い方
```typescript
let 変数名: 型;
let 配列変数名: 型[];

class クラス名 {
  変数名: 型;
}

const func = (引数名: 引数型): 戻り値型 => {
  return 戻り値;
};
```

## 処理の IN/OUT を制限して内部で利用する
```typescript
function hello(name: string, helloAfterAction: (name: string) => number) {
  console.log(`hello ${name}`);
  helloAfterAction(name);
}

// interfaceのように定義する
type HogeInterface = (hoge: string) => number;
type FugaInterface = {
  (hoge: string): number;
};
function hogee(name; string, hogeAction: HogeInterface) {
  console.log(`hogee ${name}`);
  hogeAction(name);
}
```


## A or B
ユニオン型(Union type)
```typescript
let hoge: A | B;
```

### 配列の中身が A or B
```typescript
let hoge: (A | B)[];
```


## A or 値なし(未定義)
オプション(Optional)
```typescript
class Hoge {
  hoge?: A;
}

class Fuga {
  hoge: A | undefined;
}

// A or undefined だけど、明示的な値定義を強制する
class Piyo {
  hoge?: A | undefined;
}
```

### デフォルト値設定
```typescript
function hello(name?: string) {
  name ??= 'ほげ';
}

function hello(name: string = "ほげ") {
}
```

### 値がない状態(null or undefined)でも安全に利用する
オプショナルチェーン(Optional Chain)
Null合体演算子
```typescript
let hoge: undefined;

let typeVal: string | undefined = hoge?.title?.type;
let typeNumber: number = typeVal?.indexOf("ほ") ?? 0;
```


## A であり B であるもの
交差型・インターセクション型(intersection type)
```typescript
type Hoge = A & B;

function extend<A, B>(first: A, second: B): A & B {
  return { ...first, ...second };
}
```

※ 判別できない型が出来上がった場合、 never型として扱われる


## 特定の型が利用時に決まる
総称型(Generics)
```typescript
function hello<T>(arg: T): string {
  return `hello ${arg}!`;
}

class Hoge<T> {
  private name: string;
  private hoge: T;

  constructor(name: string, hoge: T) {
    this.name = name;
    this.hoge = hoge;
  }
}

// 利用時
hello<string>("hoge");
new Hoge<number>("hoge", 1);
```



# 型の定義方法

## 型を作る
```typescript
type Hoge = {
  name: string;
};
```


## 実体を持つ型を作る
```typescript
class Hoge {
  hoge: string;
}
```


## 振舞いを強制できる型を作る
```typescript
interface Hoge {
  hoge: string;
}
```



# 利用できる値を制限する

## "hoge" or "fuga" しか受け付けない
リテラル型
```typescript
const hello = (name: "hoge" | "fuga") => {
  console.log(`hello ${name}`);
};
```
※ string , number , boolean が実際に利用できる値として制限できる


## 決められた値 しか受け付けない
Enum
```typescript
enum Username {
  Hoge = "hoge",
  Fuga = "fuga",
}
namespace Username {
  /** Username.hello(Username.Hoge); ができるようになる */
  export function hello(name: Username): string {
    return `hello ${name}`;
  }
}
```

※ Enum のようなinterface

```typescript
interface ValueType;
const Enum = {
  key: {
    ...
  } as ValueType,
} as const;
type Enum = typeof Enum[keyof typeof Enum];
```

※ Enumのように使え、文字列でも定義できるようにする
```typescript
function stringToEnum<T extends string>(o: T[]): { [K in T]: K } {
  return o.reduce((accumulator, currentValue) => {
    accumulator[currentValue] = currentValue;
    return accumulator;
  }, Object.create(null));
}

const Hoge = stringToEnum(["hoge", "fuga"]);
type Hoge = keyof typeof Hoge;
```

※ https://basarat.gitbook.io/typescript/type-system/literal-types#string-based-enums を引用


## 同じ型を複数受け付ける
可変長引数・残余引数(rest parameter)
```typescript
function hello(name: string, ...greetedName: string[]) {
}
```



# 型判定方法

## プリミティブレベルでの確認
```typescript
let isString: boolean = typeof "" === 'string';
```

|型|判定用文字列|
|:--|:----------|
|文字列|'string'|
|真偽値|'boolean'|
|数値|'number'|
|undefined|'undefined'|
|null|'object'|
|BigInt|'bigint'|
|関数|'function'|
|Symbol|'symbol'|
|その他|'object'|


## ざっくり継承関係での確認
```typescript
let val: Hoge = ...;
let isHoge: boolean = val instanceof Hoge;
let isNotHoge: boolean = !(val instanceof Hoge);
```


## プロパティの存在確認
```typescript
let hasName: boolean = 'name' in val;
```


## チェック処理を挟んだ後の型を確定させて利用する
```typescript
class Hoge {
  static isType(arg: any): arg as Hoge {
    let isNotNullObj: boolean = (arg === 'object' && arg !== null);
    return isNotNullObj &&
      typeof (arg as Hoge).name === 'string';
  };
}

let val = ...;
if (Hoge.isType(val)) {
  // このスコープ内では、val を Hoge として扱うことができる
}
```




# データのやり取りを行うための型

## class で定義する
```typescript
class Hoge {
  hoge: string;

  constructor(init?: Partial<Hoge>) {
    Object.assign(this, init);
  }
}

(() => {
  let hoge: Hoge = new Hoge({
    hoge: "fuga"
  });
})();
```


## interface で定義する
```typescript
interface Hoge {
  hoge: string;
}
(() => {
  let hoge: Hoge = {
    hoge: "fuga"
  };
})();
```


## 使い捨て前提で定義する
インライン型アノテーション(Inline Type Annotation)
```typescript
(() => {
  let hoge: {
    hoge: string;
  } = {
    hoge: "fuga"
  };
})();
```


## 戻り値の型を複数にする(配列活用)
タプル(Tuple)
```typescript
let [id, name, type]: [string, number, string] = ((val: string): [string, number, string] => {
  return [0, "hoge", "fuga"];
})("");
```



## 戻り値の型を複数にする
分割代入(destructring)
```typescript
let {id, name, type} = ((val: string): { id: number, name: string, type: string } => {
  return {
    id: 0,
    name: "hoge",
    type: "fuga",
  };
})("");
```

# 最後に

ある程度経験を積んでいくと基本の部分を忘れている場面は多々あります。簡単に調べることは可能ですが、できる限りその時間を短縮していきたいですね。

新しい記法が増えていく可能性もあるため、今回まとめた内容では足りていない部分があるかもしれません。
不足や他にも良い定義方法がありましたら教えていただけますと幸いです。