はじめに
今回は クラスについてまとめました。
【目次】
まずは基本的な型宣言をマスターしよう!
オブジェクト型の型宣言をマスターしよう!
関数の型定義をマスターしよう!
配列をマスターしよう!
インターフェースをマスターしよう!
クラスを使いこなそう! ←🈁
型修飾子についての理解を深めよう!
ジェネリックをマスターしよう!
独自の構文拡張をマスターしよう!
クラスとは...?
クラスとは、オブジェクトの雛形(設計図)を定義したものです。
本記事では、初学者向けのクラスに関する細かな解説は省略させていただきます。
クラスの宣言
TypeScriptでは以下の形式でクラスを宣言します。
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet() {
console.log("Hello");
}
}
上記サンプルコードについての解説
- クラスプロパティ
-
name
,age
-
- コンストラクタ(インスタンス作成時に実行)
constructor() {...}
- インスタンスメソッド
greet()
初期化チェック
TypeScriptでは、基本的にクラスプロパティはコンストラクタでの初期化が必要です。
※ strictPropertyInitialization
が有効な場合
初期化しない場合は以下のようなエラーが発生します。
class Person {
// NG: Property 'name' has no initializer and is not definitely assigned in the constructor.
name: string
greet() {
console.log(`Hello, My name is ${this.name}`);
}
}
未割当のクラスプロパティ
コンストラクタでクラスプロパティを初期化しない場合は、以下いずれかの対応が必要となります。
明確な割り当てアサーション をする
プロパティ名の後に !
を追加することでstrictPropertyInitialization
のチェックを無効にできます。
!
の意義ですが、プロパティを利用する前に値が必ず割り当てることを断言します。
class Person {
// OK
name!: string
greet() {
console.log(`Hello, My name is ${this.name}`);
}
}
オプションプロパティを利用する
明確な割り当てアサーションと別に、オプションプロパティを利用する方式もあります。
そのクラスプロパティが省略可能である場合は、明確な割り当てアサーション(!
)ではなく、オプションプロパティ(?
)を利用します。
class Person {
name?: string
greet() {
console.log(`Hello, My name is ${this.name}`);
}
}
読み取り専用プロパティ
インターフェースと同様、クラスプロパティもreadonly
を使用して、読み取り専用の値を定義できます。
読み取り専用のプロパティは宣言時および、コンストラクタでのみ初期化が可能です。
class Person {
readonly name: string;
constructor(name: string) {
this.name = name;
}
}
let taro = new Person("taro");
// NG: Cannot assign to 'name' because it is a read-only property.
taro.name = "saburo"
クラス型について
TypeScriptにおけるクラスの型チェックですが、
クラスを全てのメンバーを含む任意のオブジェクト型として扱います。
サンプルコードを紹介します。
以下のコードにて、TypeScriptはPerson
クラスをname
プロパティを持つオブジェクト型とみなします。
class Person {
readonly name: string;
constructor(name: string) {
this.name = name;
}
}
let taro: Person
taro = new Person("taro"); // OK
let saburo: Person;
saburo = { name: "saburo" } // OK
そのため、saburo = { name: "saburo" }
は Person
型と一致するとみなされ型エラーが発生しないということです。
この仕様はTypeScriptの型システムを扱う上での重要な概念として覚えておきましょう。
クラスとインターフェース
クラスがインターフェースに準拠していることを宣言するには、
implements
キーワードを追加し、その後にインターフェースを記載します。
以下のサンプルコードでは、Taro
クラスがStudent
インターフェースに準拠していることを表しています。
interface Student {
study(hour: number): void
}
class Taro implements Student {
study(hour: number) {
console.log(`I'll study for ${hour} hours.`)
}
}
Student
インターフェースに準拠することでTaro
クラスはstudy(hour: number): void
の実装が強制されます。
study(hour: number): void
の実装がない場合はエラーが発生します。
interface Student {
study(hour: number): void
}
// NG: Class 'Taro' incorrectly implements interface 'Student'.
class Taro implements Student {
hello() {
console.log("my name is Taro")
}
}
クラスの拡張
TypeScriptではextends
キーワードを用いることでクラスの拡張が可能です。
以下のサンプルコードでは、Taro
クラスがPerson
クラスを継承しています。
-
Taro
クラス: サブクラス -
Person
クラス: ベースクラス
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
class Taro extends Person {
hello() {
console.log("my name is Taro")
}
}
let taro = new Taro("taro", 15);
taro.hello(); // OK: "my name is Taro"
サブクラスのコンストラクタ
サブクラスは独自のコンストラクタを定義する必要はありません。
コンストラクタを持たないサブクラスは、暗黙的にベースクラスのコンストラクタを実行します。
コンストラクタをオーバーライドした場合、super
キーワードを使ってベースクラスのコンストラクタを明示的に呼び出す必要があります。
super
はコンストラクタの最初に呼び出す必要があります。
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
class Taro extends Person {
hobby: string;
constructor(name: string, age: number, hobby: string) {
super(name, age);
this.hobby = hobby;
}
hello() {
console.log(`my name is ${this.name}. my hobby is ${this.hobby}`)
}
}
let taro = new Taro("taro", 15, "baseball");
taro.hello(); // "my name is taro. my hobby is baseball"
サブクラスのメソッド, プロパティ
サブクラスでは、ベースクラスにないメソッド, プロパティを新たに宣言できます。
すでにベースクラスにあるメソッド, プロパティを再宣言(オーバーライド)することも可能です。
オーバーライドの注意点
メソッド, プロパティをオーバーライドする場合、ベースクラスの型に割当可能である必要があります。
以下のサンプルコードでは、Base
クラスでstring
型として定義されたstr
プロパティを、Sub
クラスで、number
型とでオーバーライドしようとしているため、型エラーが発生します。
class Base {
str?: string;
}
class Sub extends Base {
str: number;
constructor(str: string) {
super();
this.str = str;
}
}
メソッドも同様です。
引数、戻り値の型がベースクラスと異なる場合、オーバーライドができません。
class Base {
sampleFn(input :string[]) {
//...
}
}
class Sub extends Base {
// NG: Property 'sampleFn' in type 'Sub' is not assignable to the same property in base type 'Base'.
sampleFn(input: number[]) {
//...
}
}
抽象クラス
JavaScriptではサポートされていませんが、
TypeScriptでは、抽象クラスを作成することができます。
抽象クラスとは?
抽象クラスとは インスタンス化することができず、スーパークラスとして利用することを保証されるクラスです。
サブクラスに抽象定義したメソッドの実装を強制させます。
サンプルコード
以下の例では、抽象クラスSchool
を作成し、クラス内で抽象メソッドgetSchoolType()
を定義することで、サブクラスに実装を強制させています。
abstract class School {
readonly name: string;
constructor(name: string) {
this.name = name
}
abstract getSchoolType(): string;
}
class JuniorSchool extends School {
getSchoolType() {
return "junior high school";
}
}
class HighSchool extends School {
getSchoolType() {
return "high school";
}
}
// NG: Non-abstract class 'ElementarySchool' does not implement all abstract members of 'School'
class ElementarySchool extends School {
}
アクセス修飾子
JavaScriptでは、#
を利用することでプライベートなメンバーを定義することができました。
TypeScriptでは上記に加え、public
, private
, protected
といったアクセス修飾子がサポートされています。
public
どこからでもアクセス可能
アクセス修飾子を指定しない場合、メンバーはpublicとなります。
private
クラス内部からのみアクセス可能
protected
クラス内部 + サブクラスからのみアクセス可能
サンプルコード
privageValue
はサブクラスで参照するとエラーが発生しますね。
class Klass {
public publicValue: string;
private privageValue: string;
protected protectedValue: string;
constructor(publicValue: string, privateValue: string, protectedValue: string) {
this.publicValue = publicValue;
this.privageValue = privateValue;
this.protectedValue = protectedValue;
}
setPrivateValue(privageValue: string) {
this.privageValue = privageValue;
}
setProtectedValue(protectedValue: string) {
this.protectedValue = protectedValue;
}
}
class SubKlass extends Klass {
//NG: Property 'privageValue' is private and only accessible within class 'Klass'.
setPrivateValueBySubKlass(privageValue: string) {
this.privageValue = privageValue;
}
setProtectedValueBySubKlass(protectedValue: string) {
this.protectedValue = protectedValue;
}
}
ゲッター / セッター
JavaScriptと同様、TypeScriptでもゲッターとセッターを定義できます。
class User {
email: string;
private _password: string;
constructor(email: string, _password: string) {
this.email = email;
this._password = _password
}
get password(): string {
return this._password;
}
set password(password: string) {
if(password.length < 8) {
return;
}
this._password = password;
}
}
let taro = new User("taro@example.co.jp", "Taro1234");
// OK
taro.email;
taro.password;
// NG: Property '_password' is private and only accessible within class 'User'.
taro._password;
// Not Updated
taro.password = "Hoge"
console.log(taro.password) // => "Taro1234"
// Updated
taro.password = "Taro9876"
console.log(taro.password) // => "Taro9876"
static
JavaScriptではstatic
キーワードを指定することで静的なメンバーを作成することができます。
TypeScriptではstatic
キーワードと、readonly
, public
, private
, protected
修飾子を合わせて使うことが可能です。
class Klass {
private static readonly className:string = "Klass"
}
まとめ
以上です!
基本的な機能については本記事で網羅できていると思います。
覚えることが多いですが、がんばりましょう!
次の記事もよければご覧ください🙏