プログラミングTypeScript

これはオライリーより出版されている[「プログラミングTYpeScript」](https://www.oreilly.co.jp/books/9784873119045/)を自分なりにまとめたメモになります。

```typescript
// 型が何であるかを明示的にTypeScriptに伝えるためにはアノテーションを利用する。
// アノテーションは「値：型」という形式をとる。

// vscodeのデフォルトのフォーマッタにPrettierを設定して、各種言語の保存時に自動整形が走るようにしている。
const vscodeSetting = {
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "[javascript]": {
    "editor.formatOnSave": true,
  },
  "[javascriptreact]": {
    "editor.formatOnSave": true,
  },
  "[json]": {
    "editor.formatOnSave": true,
  },
  "[typescriptreact]": {
    "editor.formatOnSave": true,
  },
  "[typescript]": {
    "editor.formatOnSave": true,
  },
};

// tsconfig：どのファイルをコンパイルすべきか、コンパイル結果をどのディレクトリに格納するかなどをTypeScriptプロジェクトが定義する場所になる。
const tsconfig = {
  compilerOptions: {
    // コードを実行する環境にどのAPIが存在しているとTSCが想定すべきか
    lib: ["es2015"],
    // TSCがコードをどのモジュールシステムにコンパイルすべきか
    module: "commonjs",
    // 生成するJSコードをTSCがどのフォルダーに格納すべきか
    outDir: "dist",
    sourceMap: true,
    // 不正なコードをチェックするとき、できるだけ厳格にチェックする
    strict: true,
    // TSCがコードをどのJSバージョンにコンパイルすべきか
    target: "es2020",
  },
  // TSC(TypeScript Compiler)がTypeScriptファイルを見つけるためにどのフォルダーを探すべきか
  include: ["src"],
};

// eslint：作成するコードに対して望むスタイルの規約を成文化する。
// 次のコマンドで設定ファイルを作成できる。： npm install eslint --save-dev
// 詳細は以下リンク参照：https://maku.blog/p/xz9iry9/

// tsファイルの実行方法
// npm install --save-dev ts-node
// ./node_modules/.bin/ts-node memo.ts

/* 
第3章：型について
*/
const bool: boolean = true; // boolean型
const num: number = 100; //number型
const str: string = "hoge"; //string型
const obj: { a: number; b: string } = {
  a: 101,
  b: "101",
}; // オブジェクトリテラル表記

// インデックスシグネチャ
const indexSignature: {
  a?: string; // オプションパラメーター：引数が省略可能となる
  [b: number]: boolean;
  /* インデックスシグネチャ：[key:T]：Uという構文について、
        このオブジェクトは、型Tのすべてのkeyは型Uの値を持たなければならない*/
  readonly c: string;
} = {
  a: "hoge",
  1: true,
  2: false,
  3: false,
  c: "dog",
};

// 型エイリアス：ある型を指し示す型エイリアスを宣言することが可能
type Age = number;
type Person = {
  name: string;
  age: Age;
};

// 合併(union)(|)・交差(&)
type Cat = { name: string; purrs: boolean };
type Dog = { name: string; barks: boolean; wags: boolean };
type CatOrDogOrBoth = Cat | Dog;
type CatAndDog = Cat & Dog;

const superDog: CatOrDogOrBoth = {
  name: "dog",
  purrs: true,
  barks: false,
  wags: false,
};

const superCat: CatAndDog = {
  name: "cat",
  purrs: true,
  barks: false,
  wags: false,
};

// 配列
const arr1: string[] = ["a", "b"];
const arr2: (number | string)[] = [1, "a", "b"];

// タプル(配列の派生であり、固定長の配列を型付けするための方法)
const a: [string, string, number] = ["malcolm", "gladwell", 1963];
const list: [number, boolean, ...string[]] = [1, false, "a", "b", "c"];

// 列挙型(大文字で始まる単数形)
const enum Language {
  English = 0,
  Spanish = 1,
  Russian = 2,
}
console.log(Language.English);

/* 
第4章：関数
*/
const addNum = (a: number, b: number): number => {
  return a + b;
};
console.log(addNum(1, 100));

// デフォルトパラメーター
type Context = {
  appId?: string;
  userId?: string;
};
const log = (message: string, context: Context = {}) => {
  let time = new Date().toISOString();
  console.log(time, message, context.userId);
};
log("User clicked on a button", { userId: "da763be" });

// レストパラメーター：パラメーターを配列として受け取ることができる
// 注意事項：1. 最大で1つのレストパラメーターを持つことができ、
//          2. そのパラメーターは関数の最後のパラメーターでなければならない
const sumVariadicSage = (...numbers: number[]): number => {
  console.log(numbers);
  return numbers.reduce((total, n) => total + n, 0);
};
console.log(sumVariadicSage(1, 2, 3));

// 呼び出しシグネチャ：どのような関数かを表現する型定義
// 上記sumVariadicSageの型は以下のように表現できる。
// (...numbers:number[])=>number
// 呼び出しシグネチャと型エイリアスを結びつけると以下のように表示できる。
type SumVariadicSage = (...numbers: number[]) => number;

type Log = (message: string, ...userId: string[]) => void;
const logAlias: Log = (message, ...userId) => {
  const time = new Date().toISOString();
  console.log(time, message, userId);
};
logAlias("hoge", "1", "2", "3");

// オーバーロードされた関数：複数の呼び出しシグネチャを持つ関数
type Hello = {
  (person: string): void;
  (persons: string[]): void;
};

// ポリモーフィズム
// ジェネリック型パラメーター：複数の場所で型レベルの制約を強制するために使われるプレースホルダーの型
// 山括弧(<>)はジェネリック型パラメータを宣言する方法。
// 第1引数：配列、第2引数：関数
// ①
type Filter = {
  <T>(array: T[], f: (item: T) => boolean): T[];
  <U>(array: U, f: (item: U) => string): U;
};
let filter: Filter = (array, f) => {
  return [];
};
// ②
function chooseRandomly<T>(v1: T, v2: T): T {
  return Math.random() <= 0.5 ? v1 : v2;
}
console.log(chooseRandomly<string>("勝ち", "負け"));

type Filter1<T, U> = {
  (array: T[], f: (item: T) => U): U[];
  // (array: U[], f: (item: U) => boolean): U[]; //個別に設定することができない
};
let filter1: Filter1<number, string> = (array, f) => {
  return [];
};

/* 
第6章：高度な型
*/
// constアサーション
let c = { x: 3 } as const;
// {x:number}として定義されるが、constアサーションを利用し、
// {readonly x:3}と指定し、TypeScriptにできるだけ狭く型を推論させる。

// オブジェクト型のルックアップ型：キーを指定して型を取得することが可能
type APIResponse = {
  user: {
    userId: string;
    friendList: {
      count: number;
      friends: {
        firstName: string;
        lastName: string;
      }[];
    };
  };
};
type FriendList = APIResponse["user"]["friendList"];

// keyof演算子：
// ・オブジェクトのすべてのキー(プロパティ)を文字列リテラル型の合併(|)として取得できる。
// ・型のみにしか利用できない。
type ResponseKeys = keyof APIResponse; // 'user'
type UserKeys = keyof APIResponse["user"]; //'userId' | 'friendList'

// マップ型(Mapped type)：オブジェクトのキーの型と値の型をマッピングする。
type SystemSupportLanguage = "en" | "fr" | "it" | "es";
type Butterfly = {
  [key in SystemSupportLanguage]: string;
};
const butterflies: Butterfly = {
  en: "Butterfly",
  fr: "Papillon",
  it: "Farfalla",
  es: "Mariposa",
  // de: "Schmetterling", // deが利用できなくなる
};

// typeof演算子：
// ・宣言している変数の型を取得できる。
// ・変数のみにしか利用できない。
const objType = {
  1: "a",
  2: "b",
  3: "c",
  4: 1,
};
type objType = typeof objType;

// keyofとtypeofの併用
type SomeObj = {
  [key: string]: string;
};

const someObj: SomeObj = {
  foo: "FOO",
  bar: "BAR",
  baz: "BAZ",
};
// プロパティを文字列リテラル型の合併(|)として表現する
type obj1 = keyof typeof someObj;
/*
1. typeof someObj：
  type obj1 = {
    foo: string;
    bar: string;
    baz: string;
  }
2. keyof typeof someObj：
  type obj1 = "foo" | "bar" | "baz"
*/
// プロパティの型を文字列リテラル型の合併(|)として表現する
type someObjType = typeof someObj[keyof typeof someObj];
for (const key in someObj) {
  console.log(key as any, someObj[key as keyof typeof someObj]);
}
for (const key in someObj) {
  console.log(key as any, someObj[key]);
}
for (const [key, value] of Object.entries(someObj)) {
  console.log(key, value);
}
/*
someObj[key]のみだとエラーとなるため、someObj[key as keyof typeof someObj]が必要
1. keyof typeof someObj：
  key as "foo" | "bar" | "baz"
2. someObj[key as keyof typeof someObj]
  someObj[foo as "foo" | "bar" | "baz"]
  someObj[bar as "foo" | "bar" | "baz"]
  someObj[baz as "foo" | "bar" | "baz"]
*/

type Account = {
  id: number;
  isEmployee: boolean;
  notes: string[];
};
// すべてのフィールドをnull許容にする
type Hoge = keyof Account;
type NullableAccount = {
  [K in keyof Account]: Account[K] | null;
};
/*
1. keyof Accountで"id"|"isEmployee"|"notes"が取得できる
2. [K in "id"|"isEmployee"|"notes"] : Account[K]
3. type NullableAccount ={
  id : Account["id"] | null
  isEmployee : Account["isEmployee"] | null
  notes : Account["notes"] | null
}
↓となる
type NullableAccount = {
    id: number | null;
    isEmployee: boolean | null;
    notes: string[] | null;
}
*/

// ユーザー定義型ガード
// function isStringError(a: unknown): boolean {
//   return typeof a === "string";
// }
// a is string：booleanがtrueである場合は、isStringに渡された引数がstringであることを示す
function isString(a: unknown): a is string {
  return typeof a === "string";
}
function parseInt(input: string | number) {
  let formattedInput: string;
  if (isString(input)) {
    // isStringErrorの場合、inputはstring|numberであり、TypeScriptはisStringがbooloeanを返すだけしかわかっておらず
    // if文のどっちにもくる可能性があるため、ここでエラーを出すようになっている
    formattedInput = input.toUpperCase();
    console.log(formattedInput);
  } else {
    console.log(input);
  }
}
parseInt("a");
parseInt(1);

// 非nullアサーション
// null | undefinedでないことを宣言する
// const notNull = document.getElementById("id")!.getElementsByClassName;

// 明確な割り当てアサーション
// userIdを読み取るときに値が確実に割り当てられること
let userId!: string;

/* 
第7章：エラー処理
*/
// 例外を返す
class InvalidDateFormatError extends RangeError {}
class DateIsInTheFuturuError extends RangeError {}
function isValid(date: Date) {
  return (
    Object.prototype.toString.call(date) === "[object Date]" &&
    !Number.isNaN(date.getTime())
  );
}
function parse(
  birthday: string
): Date | InvalidDateFormatError | DateIsInTheFuturuError {
  let date = new Date(birthday);
  if (!isValid(date)) {
    return new InvalidDateFormatError("Enter a date in the form YYYY/MM/DD");
  }
  if (date.getTime() > Date.now()) {
    return new DateIsInTheFuturuError("Are you a timeload");
  }
  return date;
}
let result = parse("hoge");
if (result instanceof InvalidDateFormatError) {
  console.error(result.message);
} else if (result instanceof DateIsInTheFuturuError) {
  console.error(result.message);
} else {
  console.log("Date is", result.toISOString());
}
```