MyDocs

# TypeScriptの勉強する

型いろいろ

discriminated unions

意味のある共通のプロパティをもたせて判別に使う。

interface Bird {
  flySpped: number;
  type: "bird";
}

interface Horse {
  runSpeed: number;
  type: "horse";
}

type Animal = Bird | Horse;

function animalSpeed(animal: Animal) {
  switch (animal.type) {
    case "bird":
      console.log(animal.flySpped);
      break;
    case "horse":
      console.log(animal.runSpeed);
  }
}

型キャスト

文字列を数値に変換する場合、 + を変数の前につけるだけでよい。

const a = "1";
const b = "2";

const result = +a + +b;
console.log(result); // 3

前に <> で型を書くか、後ろに as を付けて型キャストできる。 祖運剤が確定している場合は ! を付けてエラーを回避できる。

const input1 = <HTMLInputElement> document.getElementById("num")!;
const input2 = document.getElementById("num")! as HTMLInputElement;

インデックス型

interface ErrorInterface {
  [prop: string]: string;
}

const errorMessage: ErrorInterface = {
  email: "hoge",
  name: "foo",
  message: "fuga",
};

console.log(errorMessage); // { email: "hoge", name: "foo", message: "fuga" }

関数オーバーロード

受け取る型と返す型のパターンを関数の直前に記述する。

type Input = string | number;

function add(a: number, b: number): number;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: string, b: string): string;
function add(a: Input, b: Input) {
  if (typeof a === "string" || typeof b === "string") {
    return a.toString() + b.toString();
  }
  return a + b;
}

console.log(add(1, 2));
console.log(add("1", 2));

オプショナルチェイン

? を使ってオブジェクトに安全にアクセスする。

const fetched = {
  id: "id1",
  name: "hoge",
  job: {
    title: "Dev",
    desc: "developer",
  },
};

console.log(fetched && fetched.job && fetched.job.title);
console.log(fetched?.job?.title);

null 合体演算子

nullundefined のときのみ判定できる。

let input = "";
const inputData = input ?? "Default";
console.log(inputData);

class

class Person {
  id: string;
  name: string;

  constructor(id: string, n: string) {
    this.id = id;
    this.name = n;
  }
  printData() {
    console.log(`ID: ${this.id}; Name: ${this.name}`);
  }
}

const person = new Person("user1", "hoge");
person.printData(); // ID: user1; Name: hoge

フィールドとコンストラクタはまとめる事ができる。やり方は constractor にアクセス修飾子を書くだけ。 引数の名前はフィールド名にしないといけない。

class Person {
  constructor(public id: string, public name: string) {
  }
  printData() {
    console.log(`ID: ${this.id}; Name: ${this.name}`);
  }
}

const person = new Person("user1", "hoge");
person.printData(); // ID: user1; Name: hoge

private は外部からアクセスできない。 readonly は初期化後に変更ができない。 また protected はサブクラスからのみアクセス可能。

class Person {
  constructor(private readonly id: string, public name: string) {
  }
  printData() {
    console.log(`ID: ${this.id}; Name: ${this.name}`);
  }
}

const person = new Person("user1", "hoge");
person.printData(); // ID: user1; Name: hoge

getter & setter

() は不要。プロパティのように実行する。

class Person {
  private _report: string;

  constructor(private readonly id: string, public name: string) {
    this._report = "";
  }

  get report() {
    return this._report;
  }

  set report(input: string) {
    this._report = input;
  }

  printData() {
    console.log(`ID: ${this.id}; Name: ${this.name}`);
  }
}

const person = new Person("user1", "hoge");
person.printData(); // ID: user1; Name: hoge

person.report = "report1";
console.log(person.report); // report1

static メソッド & static プロパティ

static メソッドや static プロパティには this でアクセスできない ( インスタンスからアクセスできない )。

class Person {
  static year = 2021;
}

console.log(Person.year); // 2021

抽象メソッド

抽象メソッドは抽象クラス内でのみ使える。抽象メソッドは関数の構造のみを定義しておく。 抽象クラスからはインスタンスを作れない。継承したサブクラスからはインスタンスを作れる。

abstract class Product {
  constructor(protected readonly id: string, public name: string) {
  }
  abstract describe(): void;
}

class Product1 extends Product {
  constructor(id: string, name: string) {
    super(id, name);
  }

  describe(): void {
    console.log(`ID: ${this.id}; Name: ${this.name}`);
  }
}

const product1 = new Product1("1", "hoge");
product1.describe(); // ID: 1; Name: hoge

シングルトンパターン

オブジェクトを 1 つしか作らせたくない場合に使う。

class Person {
  private static instance: Person;

  static getInstance() {
    if (Person.instance) {
      return this.instance;
    }
    this.instance = new Person();
    return this.instance;
  }
}

const person = new Person();

interface

オブジェクトがどんな形であるか定義する。 interface とカスタムタイプの使い分けは、オブジェクトの構造を記述するときは interface を使う。 カスタムタイプは union 型など様々な型を定義できる。 interface を使えばオブジェクトの構造を定義したいという意図を明確にできる。 また interface は readonly 継承もできる。

interface Named {
  readonly name: string
}

interface Greetable extends Named {
  hello(phrase: string): void
}

implements

implements を使って実装する。 抽象クラスとの違いは、 interface では値や実装を持たない。抽象クラスは値や実装を混在させる事ができる。

interface PersonInterface {
  id: string;
  name: string;
  describe(): void;
}

class Person implements PersonInterface {
  id: string;
  name: string;

  constructor(id: string, n: string) {
    this.id = id;
    this.name = n;
  }

  describe(): void {
    console.log(`ID: ${this.id}; Name: ${this.name}`);
  }
}

const person = new Person("1", "hoge");
person.describe(); // ID: 1; Name: hoge

ジェネリクス

関数の後ろに <> を付けて表現する。

function mergeObject(objA: object, objB: object) {
  return Object.assign(objA, objB);
}

console.log(mergeObject({ id: "1" }, { name: "hoge" })); // { id: "1", name: "hoge" }
function mergeObject<T>(objA: T, objB: T) {
  return Object.assign(objA, objB);
}

console.log(mergeObject({ id: "1" }, { name: "hoge" })); // { id: "1", name: "hoge" }

制約をつける

extends を使う。

function mergeObject<T extends object, U extends object>(objA: T, objB: U) {
  return Object.assign(objA, objB);
}

console.log(mergeObject({ id: "1" }, { name: "hoge" })); // { id: "1", name: "hoge" }

keyof

keyof を使うことでオブジェクトのキーの制約を持たせる。

function addConvert<T extends object, U extends keyof T>(obj: T, key: U) {
  return "value: " + obj[key];
}

console.log(addConvert({ id: "1", name: "hoge" }, "name")); // value: hoge

ジェネリクスクラス

クラスの後ろに <> を付けて表現する。

class DataStore<T extends string | number> {
  private data: T[] = [];

  addItem(item: T) {
    this.data.push(item);
  }
  removeItem(item: T) {
    if (this.data.indexOf(item) === -1) {
      return;
    }
    this.data.splice(this.data.indexOf(item), 1);
  }
  getItems() {
    return [...this.data];
  }
}

const stringDate = new DataStore<string>();
const numberData = new DataStore<number>();
stringDate.addItem("data1");
stringDate.addItem("data2");
numberData.addItem(1);
numberData.addItem(2);
stringDate.removeItem("data2");
numberData.removeItem(2);
console.log(stringDate.getItems()); // [ "data1" ]
console.log(numberData.getItems()); // [ 1 ]

Partial

一時的に別の型に切り替えることができる。 Partial で最終的にキャストされる型を指定する。 return するときはキャストが必要。

interface Todo {
  title: string;
  desc: string;
  date: Date;
}

function createTodo(
  title: string,
  desc: string,
  date: Date,
): Todo {
  let todo: Partial<Todo> = {};
  todo.title = title;
  todo.desc = desc;
  todo.date = date;
  return todo as Todo;
}