✍️

【TypeScript】入門してみたまとめ ✍🏻

2022/12/02に公開

TypeScriptとは

Javascriptにコンパイルされる静的型システムがついたJavascriptの上位集合。

Javascriptにコンパイル
TypeScriptPlaygroundで試すとわかるが、左側のエディタでtypescriptを書くと、

const hello: string =  'hello'

右側のエディタにjsにコンパイルされたものが表示される

const hello = 'hello';

静的型システム

TypescriptがJavascriptにコンパイルされる際に型検査を行うシステム。

Javascriptの上位集合

現行のJavaScriptで記述されたプログラムはすべて、TypeScriptのプログラムとして実行が可能。

はじめに

NodejsでTypescriptインストール

npm install -g typescriptとターミナルに入力しインストール。
権限エラーが表示された場合、
sudo npm install -g typescript

Javascriptにコンパイル

Typescriptをインストールできたらtscというコマンドが使用できるようになり、tsc Typescriptのファイル名と入力すると、コンパイルされたjsファイルが自動生成される。

例)
ts拡張子のtypescriptファイルを作成しts記述

const hello: string = "hello";
console.log(hello);

ターミナルでtsc index.tsと入力するとコンパイルされjsファイルが生成。

var hello = "hello";
console.log(hello);

Typescriptを使うべき理由

  1. 型を定義していく仕様上、半自動的にドキュメントのようなコードが書ける。
  2. コンパイル時にエラー表示してくれる型付きLinterとしての側面がある。
  3. ES5へのコンパイラ
    デフォルトだとES3にコンパイルされる。
    tsc index.ts --target ES6などと、コンパイルターゲットも指定できる。


Typescriptの型はこう書く!

一般的には、変数、関数の仮引数、関数の戻り値など値に対して型をつける。

boolean型、number型、string型

下記のように記述することでそれぞれの型を宣言できる。

const hasValue: boolean = true;
const count: number = 10; //整数も浮動小数点もマイナスも同じ
const single: string = "hello";

型注釈と型推論

下記のように: booleanなどの型を記述する書き方を型注釈という。

const hasValue: boolean = true;

この型注釈を省力すると右の値から推測をして自動的に型を決めてくれる。
それを型推論という。

let value = 3.333;

vscodeではTypescriptが内蔵されていて、変数をhoverすると型を確認できる。
下記のように型推論によって、valueはnumber型になっている。
型が異なる値を代入しようとするとエラー表示してくれる。
スクリーンショット 2022-11-17 9.28.48.png
スクリーンショット 2022-11-17 9.29.16.png

オブジェクトに型をつける

普通に書くと型推論によって、プロパティごとに型を決めてくれる。

const person = {
  name: "jack",
  age: 26,
};

スクリーンショット 2022-11-17 9.49.17.png

型注釈で書いた場合

const person : {
  name: string; // セミコロンであることに注意
  age: number;
} = {
  name: "jack",
  age: 26,
};

配列に型をつけるArray型

型推論で書く

const fruits = ["apple", "banana", "grape"];

hoverしてみる
スクリーンショット 2022-11-18 9.02.43.png

型注釈で書く

const fruits: string[] = ["apple", "banana", "grape"];

異なる型をpushしてみるとちゃんとエラー表示する
スクリーンショット 2022-11-18 9.05.01.png

異なる型を配列に格納したい場合

型推論で書くと、ユニオン型になり、二つの型を代入できるようになる。

ユニオン型の型注釈は、2つ以上の型をパイプ記号(|)で繋げて書きます。
サバイバルTypescript:ユニオン

const fruits = ["apple", "banana", "grape", 1];

スクリーンショット 2022-11-18 9.09.07.png

型注釈の場合、上記ユニオン型以外にany型でも書ける。

TypeScriptのany型は、どんな型でも代入を許す型です。プリミティブ型であれオブジェクトであれ何を代入してもエラーになりません。

サバイバルTypescript:any

const fruits: any[] = ["apple", "banana", 1];

Tuple型

決まった内容の配列を作るときなどに使用するタプル型。
配列の強化版のようなイメージ。

タプル型は、複数の値を保持することのできる型です。 [] 記号を使うところも配列によく似ていますが、それぞれの位置の要素の型を明示しておくことができます。

Typescriptのタプル型を定義する

タプル型を型注釈で書く。

const books: [string, number, boolean] = ['business', 1500, false]

異なる型を代入しようとしたり、指定した要素数以上に追加したりするとエラー表示。
※pushで追加した場合、エラー表示されない...その追加した要素に参照しようとするとエラーが出るが、途中経過までは見ないらしい🤔

スクリーンショット 2022-11-18 9.44.32.png
スクリーンショット 2022-11-18 9.43.34.png
スクリーンショット 2022-11-18 9.46.02.png

Enum型

特定のまとまったグループのみを受け入れる型。

Enum(列挙)型とは名前付きの定数を定義するためのもの。
定義にはenumを使用し、定数名はすべて大文字とする。

【TypeScript】列挙型

例えば、coffee.sizeには特定の4つの文字列しか受け入れたく無い場合。

enum CoffeeSize  {
  SHORT = 'SHORT',
  TALL = 'TALL',
  GRANDE = 'GRANDE',
  VENTI = 'VENTI'
}

const coffee = {
  hot: true,
  size: CoffeeSize.TALL
}

coffee.sizeの型を確認するとCoffeeSizeの列挙型になっていることがわかり、この列挙型とは異なる型を受け入れなくなる。

スクリーンショット 2022-11-19 15.18.41.png

従って、代入する場合は下記のように、CoffeeSize列挙型からしかできない。

coffee.size = CoffeeSize.SHORT

any型

すでに挙げている通り、なんでも許容する型。

TypeScriptのany型は、どんな型でも代入を許す型です。プリミティブ型であれオブジェクトであれ何を代入してもエラーになりません。

jsの世界に戻るようなものなので、正しくtsの型の恩恵を受けたいなら基本的にany型は使わない。

any型、型注釈

let anything: any =  true;

どんな値を代入してもエラーにはならない

anything =  'hello';
anything = [];
anything = {};
anything.aaaa = 'aaaa';

型定義してあったものにも代入できてしまう..なので基本any型は使用しない🤨

let sample = 'sample'; // 型推論により、string型
sample = anything

Union型

複数の型を受け入れる型。or演算子のように|で受け入れたい型を繋げて記述する。

let unionType: number | string = 10;
unionType = 'hello';
unionType = 1

配列のUnion型の書き方。
下記のように書けば、number型、string型のみ受け入れる配列になる。

let unionTypes: (number | string)[] = [ 20, 'hello' ]

Literal型

特定の決まった値のみを扱う型。
下記の場合、appleのリテラル型となり、apple以外は入れられなくなる。

let apple: 'apple' = 'apple'

string型を代入してもエラーになる。
スクリーンショット 2022-11-19 18.15.59.png

数値の場合も同様に、下記の変数numberは1以外を代入できない

let number: 1 = 1
number = 0 // エラー

型推論で書く場合、constを使用した時点でリテラル型になる。

const apple = 'apple' // 型推論により、リテラル型になる

特定の値しか受け入れたく無い場合、このliteral型とunion型を組み合わせることで、enum型と似たようなやり方もできる。
下記の場合だと3つの文字列しか受け付けない変数になる。

let clothSize: 'small' | 'medium' |  'large' = 'small'

enum型との使い分けとすると、基本的にenum型の方が扱いやすい場合が多いので、enum型を使用し、逆に受け入れる値が少ない場合などは、literal型とunion型を組み合わせたやり方でもいいかもしれない。

typeエイリアスとは
型を変数のように扱える記述の仕方。
type 変数名 = 値という記述。letやconstと似ている。

type ClothSize = "small" | "medium" | "large"
let clothSize: ClothSize = "small";

関数に型を適応させる方法

関数は仮引数と、戻り値に型を適応させる。
戻り値の型は()の後に記述する。
戻り値に応じた型推論もできるが基本的に戻り値の型も型注釈したほうがいい。
仮引数の型は型推論できないので、必ず型注釈する。

// 関数宣言
function add(num1: number, num2: number): number {
  return num1 + num2;
}

// アロー関数1
const doubleNumber =  (num1: number): number =>  num1 * 2

// アロー関数2(関数を格納している変数に型注釈した記述)
const doubleNumber: (num1: number) => number = num1 =>  num1 * 2

void型

何も返さない型。下記は型推論で書いた例。
スクリーンショット 2022-11-22 9.19.06.png

callback関数に型を適応

cb: (num: number) => number部分でコールバック関数の型注釈。

function doubleAndHandle(num: number, cb: (num: number) => number): void {
  const doubleNum = cb(num * 2);
  console.log(doubleNum);
}
doubleAndHandle(20, (doubleNum) => {
  return doubleNum;
});

unknown型を使って、柔軟でany型より厳しい型を定義する方法

unknown型はany型のように、どんな型の値も代入できるが、any型と違って、
どんな値にも入れられるわけではない。
下記の例では
text = unknownInput;部分でエラーが出ていることがわかる。
スクリーンショット 2022-11-22 20.33.18.png

never型

決して何も返さない型
例えばnew Error()ではundefinedも何も返さないので、never型を使用する

function error(message: string): never {
  throw new Error(message);
}
console.log(error("This is an error"));


コンパイラを使う方法

watchモードで自動コンパイル

ターミナルで下記入力するとwatchモードになり、保存時に自動コンパイルされる。
tsc index.ts -w

ファイルを一気にコンパイル

スペースで続けてファイル名を指定すると複数のコンパイルができる。
tsc index.ts text.ts

プロジェクトルートにtsconfig.jsonを設置することでtscと入力するとtsconfig.jsonに設定されている内容に沿って、処理される。
デフォルトでは全てのtsファイルがコンパイルされる。

tsc --init → tsconfig.jsonが生成
tsc → 全tsファイルコンパイル
tsc -w → watchモード

includeとexcludeを使ってコンパイルファイルを指定

tsconfig.jsonのcompilerOptionsプロパティの後などにincludeやexcludeプロパティを記載して、コンパイル対象を指定できる。
ワイルドカードが使用できる。

"include": [] → コンパイルするファイルの指定。
"exclude": [] → コンパイルしないファイルの指定。

node_modulesはデフォルトではexcludeされているが、excludeを記述した場合は、node_modulesも明記しないとコンパイルされてしまうので、注意。

{
    "compilerOptions": {},
    "exclude": [
        "test.ts",
        "*.spec.ts",
        "tmp/compiler.ts"
        "**/compiler.ts"
        "node_modules"
    ]
}

filesプロパティもあり、includeと似ているが、こちらはワイルドカードが使用できないし、excludeより優先されるという違いがある。
デフォルトではincludefilesも記述されていないので、全てのtsファイルがコンパイルされる。

tsconfig.jsonのcompilerOptionsプロパティの解説

参考:
tsconfig.jsonの全オプションを理解する

オプション 説明
target コンパイルターゲットの指定。es5などと記述する
lib tsが用意する型の定義記述。指定されない場合はtargetに応じて自動的に判断されるので、基本的にこちらは記述しなくてok
allowJs js拡張子をコンパイル対象にするかどうか。outDirなどと組み合わせて、コンパイルしたものを別のフォルダに格納したりする必要がある。
checkJs 単体ではtrueにできず、allowJsと一緒に使う。jsファイルもエラーチェックするかどうか。
jsx reactjsの時に使用。
declaration これをtrueにすると、コンパイルしたtsファイルの中でexportしているもの全ての型定義ファイルをファイルごとに作成する。
declarationMap declarationオプションと併用するオプション。これをtrueにすると、型定義のmapファイルが作成される。
sourceMap trueにして、コンパイルするとmapファイルが生成される。ブラウザでコードを見た際にtsファイルが見れるようになる。
outDir tsがコンパイルされた際に生成するjsファイルの出力先を指定できる。"outDir": "./dist"のように記述するとdistディレクトリが生成され、その配下にコンパイルされたjsが生成される。
rootDir コンパイル結果をoutDirで出力する際に、どのディレクトリ配下のディレクトリ構造で出力するかを指定する。
romoveComments コメントを消すかどうか。trueにすると消える。
noEmit 何も出力しない。tsのチェックだけを行なって何も出力しない。
downlevelIteration targetがes3とes5の時のみ使用できる。反復処理の際に正しく動作しなかったらtrueにしてみたりする。
noEmitOnError trueにするとtsエラーが起こった際にjsファイルを生成しないようになる。
strict このオプション自体は特定の機能を有効にするものではなく、このオプションをtrueにすると、下記のnoImplicitAny ~ alwaysStrictの7つのオプションが全てtrueになる。
noImplicitAny 暗黙的にanyになる値をエラーにする。stricttrueにした上で、任意のルールを一つずつfalseにすることが可能。
strictNullChecks Nullableな値に対してオプションの呼び出しを行う記述をエラーにする。
strictFunctionTypes 関数代入時の引数の型チェックにおいて、TypeScriptのデフォルトはBivariantlyな挙動だが、このオプションをtrueにするとContravariantlyに型チェックが走るようになる。
strictBindCallApply bind, call, applyを使用する際に、より厳密に型チェックが行われるようになる。
strictPropertyInitialization クラスを使用するときに使うもの。
noImplicitThis 使われているthisの型が暗黙的にanyになる場合にエラーにする。
alwaysStrict "use strict";を必ず全てのファイルの先頭行に付与する。

コードの品質を保つ設定

オプション 説明
noUnusedLocals 宣言されたが使用されていない変数が存在する場合にコンパイルエラーにする。デフォルト値はfalse。とりあえずtrueにしておく。
noUnusedParameters 関数の作成時、定義しているのに中身のコードで使用されない場合にコンパイルエラーにする。これもとりあえずtrueにしておいて良さそう。
noImplicitReturns 暗黙的なreturnをエラーにするかどうか。関数内で、条件分岐の条件によって明示的なreturnがされないルートがある場合、コンパイルエラーになる。
noFallthroughCasesInSwitch switch文を使用。fallthroughなcaseのうち、1行以上処理が存在しているにも関わらず脱出処理(breakやreturn)が無いものにエラーを吐く。trueにしておくと良さそう。


TypescriptではClassをこう使う!

オブジェクト指向プログラミング(OOP)とは

  1. OOPとは現実世界の物に見立ててプログラムする方法
  2. どのようにアプリケーションを作るかという方法の一つ
  3. 人間にとってわかりやすくロジックを分割する方法の一つ

オブジェクトを作る方法はオブジェクトリテラルとClassの二つある

オブジェクトリテラル
{
  name: {
    firstName: 'Peter',
    lastName: 'Quill'
  },
  age: 36,
  gender: 'male'
}
Class
  1. オブジェクトの設計図
  2. Claddから作られたオブジェクトはインスタンスと呼ばれる
  3. 似たようなオブジェクトを複数作成する時に便利

Classを定義してオブジェクト作成する方法

class Person {
  name: string; // フィールド
  constructor(initName: string) { // 初期処理
    this.name = initName;
  }
}
const quill = new Person("Quill");
console.log(quill); // → Person { name: 'Quill' }

Classにメソッドを追加する方法

class Person {
  name: string;
  constructor(initName: string) {
    this.name = initName;
  }
  // メソッド追加
  greeting() {
    console.log(`Hello! My name is ${this.name}`);
  }
}
const quill = new Person("Quill");
quill.greeting(); // → Hello! My name is Quill

thisの使用には注意。
Tsは直接的な記述だったら面倒を見てくれるが間接的なものまでは見てくれない。
上記のquillインスタンスがある状態で、下記サンプル記述して確認。

const anotherQuill = {
  anotherGreeting1: quill.greeting, // nameはないのにエラー表示されない
  anotherGreeting2() {
    console.log(`Hello! My name is ${this.name}`); // 直接的に記述した場合はエラー表示してくれる
  }
};
anotherQuill.anotherGreeting1(); // → Hello! My name is undefined

スクリーンショット 2022-11-23 17.10.08.png

anotherGreeting1の型情報を確認するとvoidのみ。
スクリーンショット 2022-11-23 17.17.21.png

Tsにメソッド内でthisを使用しているかどうか伝える方法

メソッドの第一引数にthis: {プロパティ: 型}を追記すると型情報が追加される。
calss Personのgreetingメソッドに追記

 greeting(this: { name: string }) {
    console.log(`Hello! My name is ${this.name}`);
  }

anotherGreeting1の型情報が追加されて、anotherQuill.anotherGreeting1();のメソッド実行部分でもエラー表示されるようになったことがわかる。
このようにすることで間接的な状態でもメソッドの中でthisが使用されているかどうかと、そのthisの情報を確認、エラー表示してくれるようになる。
スクリーンショット 2022-11-23 17.21.58.png

Classは型として使用できる

TsではClassを作った場合、そのClassが作り出すインスタンスを表す型も同時に作っている。
greeting(this: { name: string })部分にPerson型を指定して、より厳格で安全なコードになる。

greeting(this: Person) {
    console.log(`Hello! My name is ${this.name}`);
  }

public修飾子とprivate修飾子でアクセス制限

classのフィールドやメソッドにはpublic修飾子とprivate修飾子というものが使用できる。
接頭辞としてつけることで使用でき、アクセス制限をかけられる。
接頭辞を何もつけないとデフォルトでは、public修飾子になり、どこからでもアクセス可能。

例)
class Personにageフィールドを追加。

class Person {
  name: string;
  age: number; // → 追加。接頭辞をつけていないので、public修飾子。
  constructor(initName: string, initAge: number) { // → 追加
    this.name = initName;
    this.age = initAge; // → 追加
  }
  greeting(this: Person) {
    console.log(`Hello! My name is ${this.name}. I am ${this.age} years old.`);
  }
}
const quill = new Person("Quill", 38);
quill.age = 50; // → classの外側からアクセスでき、変更可能。

quill.age = 50;のようにclassの外側からいくらでもアクセスできる。
private修飾子をフィールド名につけることで、class内でしかアクセスできなくなる。
メソッドも同様にprivate修飾子をつけることで外側で使用できなくなる。

class Person {
  name: string;
  private age: number; // → private修飾子追加
  constructor(initName: string, initAge: number) {
    this.name = initName;
    this.age = initAge;
  }
  greeting(this: Person) {
    console.log(`Hello! My name is ${this.name}. I am ${this.age} years old.`);
  }
}
const quill = new Person("Quill", 38);
quill.age = 50; // → エラー表示される。

初期化処理の省略

constructor()のパラメーターで、修飾子 フィールド名: 型と記述することで
classのフィールド名やconstructor()の処理を省略できる。

// 省略していない場合
class Person {
  name: string;
  constructor(name: string) {
     this.name = initName;
  }
}

// 省略した場合
class Person {
  constructor(public name: string) {
  }
}

readonly修飾子で書き換え不可にする

public修飾子、private修飾子の他にreadonly修飾子というものがある。
読み込み専門になる修飾子。
下記の例ではprivate agereadonly修飾子も追加。
メソッドで書き換えようとするがエラー表示。
readonly修飾子を外せば正常に動作する。

class Person {
  constructor(public name: string, private readonly age: number) {}
  // メソッド追加
  incrementAge() {
    this.age = +1; // ageはreadonly修飾子がついているので、エラー
  }
  greeting(this: Person) {
    console.log(`Hello! My name is ${this.name}. I am ${this.age} years old.`);
  }
}
const quill = new Person("Quill", 38);
quill.greeting();

スクリーンショット 2022-11-25 18.17.39.png

extendsを使用して他のclassの機能を継承

class 新しいcalss名 extends 継承元class名 {}
とすることで、継承元のcalssの機能を継承している新しいclassを生成できる。

class Person {
  constructor(public name: string, private age: number) {}
  incrementAge() {
    this.age = +1;
  }
  greeting(this: Person) {
    console.log(`Hello! My name is ${this.name}. I am ${this.age} years old.`);
  }
}

class Teacher extends Person {} // extendsで継承したTeacher class

const quill = new Person("Quill", 38);
quill.greeting(); // → Hello! My name is Quill. I am 38 years old.
const tom = new Teacher("Tom", 28);
tom.greeting(); // → Hello! My name is Tom. I am 28 years old.

constructor関数を拡張したい場合

派生クラスのconstructor関数拡張にはsuper関数の呼び出しを行う必要がある。
下記の例では派生クラスTeacherに新しいプロパティとして、subjectを追加している。

class Teacher extends Person {
  constructor(name: string, age: number, public subject: string) {
    super(name, age);
  }
}

継承元のメソッドも上書き、変更したい場合は単純に新しいclassで、そのメソッドと処理を記述する。
下記の例ではgreething()を上書き。

class Teacher extends Person {
  constructor(name: string, age: number, public subject: string) {
    super(name, age);
  }
  greeting() {
    console.log(`Hello! My name is ${this.name}. I am ${this.age} years old. I teach ${this.subject}`);
  }
}

そのままだとエラーが発生!ageは継承元のclass Person内で、private修飾子がついているため、そのclassの外ではアクセス不可。
スクリーンショット 2022-11-25 18.46.28.png
継承先では使いたいけど、publicで、どこからでもアクセスはしてほしくはない。
そんな場合にprotected修飾子を使う。

protected修飾子を使用し、継承先ではアクセスできるようにする

protected修飾子でクラスとその派生クラスではアクセスできるプロパティになる。

class Person {
    // ageの修飾子をprotectedに変更
  constructor(public name: string, protected age: number) {}
  incrementAge() {
    this.age = +1;
  }
  greeting(this: Person) {
    console.log(`Hello! My name is ${this.name}. I am ${this.age} years old.`);
  }
}

class Teacher extends Person {
  constructor(name: string, age: number, public subject: string) {
    super(name, age);
  }
  greeting() {
    // 派生クラスでもthis.ageにアクセスできるようになる。
    console.log(`Hello! My name is ${this.name}. I am ${this.age} years old. I teach ${this.subject}`);
  }
}

const quill = new Person("Quill", 38);
quill.greeting();
const tom = new Teacher("Tom", 28, "Math");
tom.age = 30 // クラスや派生クラスの外ではアクセス不可でこちらはエラーになる。
tom.greeting();

getterとsetterを作る

getter
何か取得した時に処理を発火したい場合に使用

setter
何か代入した時に処理を発火したい場合に使用

例)
subjectの値を取得(get)して空文字だったらエラー処理したい。

class Teacher extends Person {
  // get追加
  get subject() {
    if (!this._subject) {
      throw Error("There is no subject.");
    }
    return this._subject;
  }
  constructor(name: string, age: number, private _subject: string) { // private _subjectに変更
    super(name, age);
  }
  greeting() {
    console.log(`Hello! My name is ${this.name}. I am ${this.age} years old. I teach ${this.subject}`);
  }
}

const tom = new Teacher("Tom", 28, "");
console.log(tom.subject); // → Error!空文字じゃなかったらthis._subjectの値が返ってくる。

例)
subjectの値を代入(set)して空文字だったらエラー処理したい。

class Teacher extends Person {
  get subject() {
    if (!this._subject) {
      throw Error("There is no subject.");
    }
    return this._subject;
  }
  // set追加。getterと同じ名前でも可。
  set subject(value) {
    if (!this._subject) {
      throw Error("There is no subject.");
    }
    this._subject = value;
  }
  constructor(name: string, age: number, private _subject: string) {
    super(name, age);
  }
  greeting() {
    console.log(`Hello! My name is ${this.name}. I am ${this.age} years old. I teach ${this.subject}`);
  }
}

const tom = new Teacher("Tom", 28, "Math");
tom.greeting();
tom.subject = ""; // setterはこの代入した時に発火
console.log(tom.subject); // → 空文字なのでError!

staticを使用してインスタンスを生成せず、Classを使用

staticプロパティ/メソッドを使うと、newしてクラスのインスタンスを作らずとも、クラスのプロパティ、メソッドを使える。

// staticメソッドの例 
Math.random()

例)
Person Classにstaticプロパティ、メソッド追加。
const quill = new Person("Quill", 38);などのインスタンス生成は削除。

class Person {
  static species = "Homo sapiens"; // staticプロパティ追加
  static isAdult(age: number) { // staticメソッド追加
    if (age > 17) return true;
    return false;
  }
  constructor(public name: string, protected age: number) {}
  incrementAge() {
    this.age = +1;
  }
  greeting(this: Person) {
    console.log(`Hello! My name is ${this.name}. I am ${this.age} years old.`);
  }
}

class Teacher extends Person {
// 省略
}

// インスタンスは生成していないのにstaticプロパティ、メソッドにはアクセス可能。
console.log(Person.species); // → Homo sapiens
console.log(Person.isAdult(14));  // → false

// 派生クラスも同様。
console.log(Teacher.species);  // → Homo sapiens
console.log(Teacher.isAdult(30));  // → true

staticプロパティ/メソッドはインスタンスを生成しないためthisを使用できない。
thisはインスタンスを指すため。
thisは使用せず直接、クラス名.プロパティ名のようにアクセスする。
例)Person.species

abstractクラスを使用して継承にのみ使用できるクラスを作成

abstract = 抽象的な

abstractが指定され、インスタンス化できない、継承元としてのみ使用するクラスを抽象クラスという。

abstractは抽象クラスを作成する時に宣言します。抽象クラスとは直接インスタンス化(new)することができず、必ずスーパークラスとして利用することを保証するものです。

サバイバルTypeScript:抽象クラス

クラスの前にabstract修飾子をつけることでそのクラスは抽象クラスだと明示でき、abstractメソッドabstractプロパティを使用できる。
つまりabstractクラスでしかabstractメソッド、プロパティは使用できない。

例)
Personクラスを抽象クラス化して、greeting()の中で派生クラスによって処理が異なるメソッドを実行する。

// Personクラスにabstract修飾子を追加して抽象クラス化
abstract class Person {
  static species = "Homo sapiens";
  static isAdult(age: number) {
    if (age > 17) return true;
    return false;
  }
  constructor(public name: string, protected age: number) {}
  incrementAge() {
    this.age = +1;
  }
  greeting(this: Person) {
    console.log(`Hello! My name is ${this.name}. I am ${this.age} years old.`);
    // 派生クラスによって処理が異なるメソッドexplainJobを実行。
    this.explainJob();
  }
  // abstractメソッドを定義。これによって派生クラスで、explainJob()があることを保証する。
  abstract explainJob(): void;
}

class Teacher extends Person {
// abstractメソッド、プロパティは必ず派生クラスで記述しなければならない。
  explainJob() {
    console.log(`I am a teacher and I teach ${this.subject}.`);
  }
  get subject() {
    if (!this._subject) {
      throw Error("There is no subject.");
    }
    return this._subject;
  }
  set subject(value) {
    if (!this._subject) {
      throw Error("There is no subject.");
    }
    this._subject = value;
  }
  constructor(name: string, age: number, private _subject: string) {
    super(name, age);
  }
}

const tom = new Teacher("Tom", 28, "Math");
tom.greeting(); // → Hello! My name is Tom. I am 28 years old.I am a teacher and I teach Math.

Personクラスをabstract化しないまま、クラス内でabstractを使用したり、Personクラス内のabstractメソッドに処理を書こうとしたり、派生クラスで、abstractメソッドを書き忘れたりするとエラーが出る。
スクリーンショット 2022-11-26 10.16.27.png
スクリーンショット 2022-11-26 10.18.14.png
スクリーンショット 2022-11-26 10.19.15.png


interfaceの使い方

interfaceとはオブジェクトのみのtypeエイリアス。

インターフェースはクラスが実装すべきフィールドやメソッドを定義した型

サバイバルTypescript:インターフェース

typeエイリアスとほとんど変わらないが、オブジェクトにのみinterfaceは使える。
interfaceがオブジェクトのみを扱うのでわかりやすいというメリットがあるらしい。

// typeエイリアス
type Human = {
  name: string;
  age: number;
};

const human: Human = {
  name: "Quill",
  age: 38,
};
let developer: Human;
// interface
interface Human {
  name: string;
  age: number;
};

const human: Human = {
  name: "Quill",
  age: 38,
};
let developer: Human;

interfaceにメソッドを追加する方法

例1)

interface Human {
  name: string;
  age: number;
  greeting: (message: string) => void; // 追加
}

const human: Human = {
  name: "Quill",
  age: 38,
  greeting(message: string) { // 追加
    console.log(message);
  },
};

例2)

interface Human {
  name: string;
  age: number;
  greeting(message: string): void; // 変更
}

const human: Human = {
  name: "Quill",
  age: 38,
  greeting(message: string) {
    console.log(message);
  },
};

implementsを使用して、クラスに対してinterfaceの条件を適応させる方法

class クラス名 interface インターフェース名 {}とクラスを作成することで指定したinterfaceの条件を満たすクラスを作成する

interface Human {
  name: string;
  age: number;
  greeting(message: string): void;
}

class Developer implements Human {
  constructor(public name: string, public age: number) {}
  greeting(message: string): void {
    console.log(message);
  }
}

interfaceを満たせていないとエラー表示
スクリーンショット 2022-11-26 13.33.37.png

implementsは複数指定でき、そこに定義されているものは全て実装する

class Developer implements Human, test, sample { // 複数のimplements
  constructor(public name: string, public age: number) {}
  greeting(message: string): void {
    console.log(message);
  }
}

「?」を使ってあってもなくてもいいプロパティやパラメーターの作成

プロパティやパラメーター、メソッドの後に?をつけるとあってもなくても良いものになる。

interface Nameable {
  name: string;
  nickName?: string; // プロパティの後に?をつけるとあってもなくてもいいプロパティになる
}

const nameble: Nameable = {
  name: 'Quill'
}

Discussion