【TS】TypeScriptの個人メモ

# any型
どんな値でも受け入れる型。
any型に代入したオブジェクトのプロパティ、メソッドは使用できる。
```typescript
const anyStr: any = "apple";

// 5
console.log(anyStr.length);
```
any型のオブジェクトはどんな型にも代入できる。
any型は**TypeScriptが型チェックを放棄した型**だからできてしまう
```typescript
const anyStr: any = "apple";

const bool: boolean = anyStr;

// apple string
console.log(bool, typeof bool);
```

# unknown型
どんな値でも受け入れる型。any型と違うのは、unknown型に代入したオブジェクトのプロパティ、メソッドはどちらも**使用できない**こと。
```typescript
const unknownStr: unknown = "apple";

// エラー：'unknownStr''は 'unknown' 型です。
console.log(unknownStr.length);
```

unknown型のオブジェクトは代入できない。
unknown型は**型安全なany型**と言われる
```typescript
const unknownStr: unknown = "apple";

// エラー：型 'unknown' を型 'boolean' に割り当てることはできません。
const bool: boolean = unkonwnStr;
```

### unknown型は型の絞り込みをして使うのが前提
unknown型はそのままでは使えない。typeofやinstanceofなどを条件式に含んだif文（型ガード）で型の絞り込みを行い使う。
```ts
const value: unknown = "";
// 型ガード
if (typeof value === "string") {
  // ここでは、valueをstring型として扱える
  console.log(value.toUpperCase());
}
```



# never型
never型は「値を持たない」型。
never型のオブジェクトには何も代入できない。any型でも代入できない。
ただしnever型を別の型に代入することはできる。

```typescript
// ✕：string型をnever型オブジェクトに代入できない
// エラー：Type 'string' is not assignable to type 'never'.
const nev: never = "apple";


// 〇：never型をstring型に代入することはできる。
const nev = "apple" as never;
const foo: string = nev;

// string型として使える
console.log(foo.toUpperCase());
```
never型として生まれたら最後までneverのまま。


### never型の用途
異常な関数の戻り値の型として使われる。
- 例外が必ず発生する関数の戻り値の型
- 無限ループの関数の戻り値の型
- Switch文などの網羅性のチェック

```typescript:例外が必ず発生する関数
function throwError(): never {
  throw new Error();
}
```

```typescript:無限ループの関数
function forever(): never {
  while (true) {} // 無限ループ
}
```

文字リテラルのユニオン型オブジェクトに対して、型で処理分岐する場合、分岐する型の指定が不足していたり、後から型を追加したときにエラーとして拾えるようにするために使う。

まずはnever型を使わず、網羅性が不足しているのソースコードを記載
```typescript:網羅性のチェックが不足

type Fruit = "apple" | "orange" | "banana";

const fruit: Fruit = "banana";
checkFruits(fruit);

// Fruit型の文字リテラル型ごとに処理を分岐。
// "banana"型の分岐が無く、"その他"の処理にいってしまうがエラーにならないので、
// 気づけない。
function checkFruits(fruit: Fruit) {
  switch (fruit) {
    case "apple":
      console.log("リンゴ");
      break;
    case "orange":
      console.log("オレンジ");
      break;
    default: {
      console.log("その他");
      break;
  }
}
```

そこで"banana"型の分岐が抜けていた場合にtypescriptがエラーを検知してくれるようにするため、defaultでnever型を使う。

```typescript:never型を使った網羅性
type Fruit = "apple" | "orange" | "banana";

const fruit: Fruit = "banana";
checkFruits(fruit);;

function checkFruits(fruit: Fruit) {
  switch (fruit) {
    case "apple":
      console.log("リンゴ");
      break;
    case "orange":
      console.log("オレンジ");
      break;
    default: {
      // "banana"型の分岐が無いのでエラーになる
      // エラー：型 'string' を型 'never' に割り当てることはできません
      const exhaustivenessCheck: never = fruit;
      break;
    }
  }
}
```

```typescript:完璧な状態("banana"型の分岐も追加）
type Fruit = "apple" | "orange" | "banana";

const fruit: Fruit = "banana";
checkFruits(fruit);;

function checkFruits(fruit: Fruit) {
  switch (fruit) {
    case "apple":
      console.log("リンゴ");
      break;
    case "orange":
      console.log("オレンジ");
      break;
    case "banana":
      console.log("バナナ");
      break;
    default: {
      // この時の"fruit"変数が"never型"になっている。
      const exhaustivenessCheck: never = fruit;
      break;
    }
  }
}
```


# 型ガード
if文/switch文とtypeof/instanceof/in演算子を使って、対象のオブジェクトの型を確認すること。
```typescript
const val = "apple";

// ↓ここが型ガード
if (typeof val === 'string') {

  // string型で確定なので安心してtoUpperCaseが使える
  console.log(val.toUpperCase();
} else {
  console.log('想定外の型');
}
```

# 型ガード関数
unknown型の変数に対して型ガードを行った後、そのunkonwn型の変数の型の変更まで行う。
ポイントは型ガード（型の絞り込み）を行う対象の引数がunkown型であること。

型ガードだけだと、対象の引数がunkown型の場合、後続の処理が実行できない。
```typescript:通常の型ガードのみ
const val: unknown = "apple";

if (simpleCheck(val)) {
  // 型ガードはクリア！ただし、valはunknown型のままなので、
  // toUpperCase関数が使えない（エラー：'val''は 'unknown' 型です。）
  console.log(val.toUpperCase());
}

// 対象のunkonwn型オブジェクトの値の型stringかどうかだけをチェック
function simpleCheck(x: unknown): boolean {
  return typeof x === "string";
}
```

```typescript:型ガード関数
const val: unknown = "apple";

if (typeGuardCheck(val)) {
  // 型ガードにクリアし、valもstring型に変換されているので、
  // toUpperCase関数が使える
  console.log(val.toUpperCase());
}

// 戻り値の型注釈に"x is string"を指定
function typeGuardCheck(x: unknown): x is string {
  return typeof x === "string";
}
```

型ガード関数の戻り値の型注釈である`x is string`という記述は**型述語**という。
引数xはstringであるとTypeScriptに解釈させている。

:::note info
型のチェックはreturn typeof x === "string";でやっている、ということを忘れないように。
:::

### typescriptバージョン5.5以降では型述語の注釈無しでも型ガード関数と同じ扱いになる。
```typescript:5.5以降で可能

const val: unknown = "apple";

if (check(val)) {
  // 型ガードにクリアし、valもstring型に変換されているので、
  // toUpperCase関数が使える
  console.log(val.toUpperCase());
}

// 戻り値の型注釈が無い場合、型ガード関数と同じ扱い。
function check(x: unknown) {
  return typeof x === "string";
}
```


# 型定義ファイル
拡張子が**.d.ts**で型情報がつまったファイル。

# declare
JavaScriptファイルに変数・関数・クラスを記述しただけでは、TypeScriptはその存在を知らず、Not Foundになる。
```javascript:index.js
function hello(name) {
  return "Hello, " + name;
}

// エラー：Cannot find name 'hello'.
hello("taro");
```
TypeScriptに存在知らせるために使うのが**declare(アンビエント宣言)**。
declareは**.d.ts**ファイルに記述する
```ts:index.d.ts
declare function hello(name: string): string;
```


# コンストラクトシグネチャ
コンストラクタの型を示している

```
declare const foo: new (arg: number) => unknown;
```

# タプル型
配列の各要素の型や要素数を指定できる。

```ts
type tupleType = [string, number, boolean];
const tup: tupleType = ["apple", 10, true];

// エラー：ソースには 4 個の要素がありますが、ターゲットで使用できるのは 3 個のみです。
const tup2: tupleType = ["apple", 10, true, 'banana'];
```

ただし`push`で要素の追加はできる
```ts
type tupleType = [string, number, boolean];
const tup: tupleType = ["apple", 10, true];

tup.push("banana");

// [ 'apple', 10, true, 'banana' ]
console.log(tup);

// 4
console.log(tup.length);

// 数字リテラルで追加した要素番号を指定するとエラーになる
// エラー：長さ '3' のタプル型 'tupleType' にインデックス '3' の要素がありません。
console.log(tup[3]);


// ただ、lengthを使うとエラーにならない
// banana
console.log(tup[tup.length - 1]);

```

## Classを定義すると、Classは型になる
```ts
class Person {
  name: string;

  constructor(initName: string) {
    this.name = initName;
  }

  // "Person"をtypeとして使える
  greeting(this: Pick<Person, "name">) {
    console.log(this.name);
  }
}

```


### Classの省略記述
```ts
class Person {

  // コンストラクタの引数にpublic演算子を入れると
  // publicなnameプロパティの作成と初期化までやってくれる
  constructor(public readonly name: string, private age: number) {}

  showAge() {
    console.log(this.age);
  }

  incrementAge() {
    this.age += 1;
  }

  greeting(this: Pick<Person, "name">) {
    console.log(this.name);
  }
}

const ken = new Person("KEN", 20);

```

## 型推論を残して型のチェックをする

```ts
type Person = { name: string };

// {name: string}型
const ken = { name: "KEN" } satisfies Person;

// 型注釈
// Pserson型
const bob: Person = { name: "BOB" };
```


:::note info
オブジェクトのキーに過不足が無いかチェックできる。しかも型推論で使える。
:::

```ts:satisfiesを利用
type Color = "red" | "green" | "blue";
const palette = {
  red: [255, 0, 0],
  green: "#00ff00",
  blue: "#0000ff",
} satisfies Record<Color, unknown>;

// redをnumber[]として使える
palette.red.forEach((x) => console.log(x));
```
```ts:型注釈を利用
const oldpalette: Record<Color, string|number[]> = {
  red: [255, 0, 0],
  green: "#00ff00",
  blue: "#0000ff",
};

// エラー：redがnumber[]として利用できない
oldpalette.red.forEach((x) => console.log(x));
```