• 作成日:

TypeScriptでinterfaceを使う

この記事の目次

TypeScriptでinterfaceを使う

interfaceはオブジェクトとClassにのみ型の指定ができます。interfaceに似たものにtypeエイリアスがありますが、typeエイリアスはすべてに型指定ができます。

使い方は以下。

interface Students {
  name: string;
  age: number;
}

const students: Students = {
  name: '山田',
  age: 30,
};

interfaceでメソッドの型を指定する

interface Students {
  name: string;
  age: number;
//hello: () => void; こっちの書き方でもOK
  hello(): void;
}

const students: Students = {
  name: '山田',
  age: 30,
  hello() {
    console.log(`私の名前は${students.name}です。年齢は${students.age}歳です`);
  },
};

students.hello();

4行目と5行目にある通り、メソッドの型を定義するときは2種類の書き方があることを覚えておきましょう。

Classに対してinterfaceの型を適用する

Classに対してinterfaceで定義した型を指定するためには、implementsを使用します。

interface Students {
  name: string;
  age: number;
//hello: () => void; こっちの書き方でもOK
  hello(): void;
}
class studensGlup implements Students {
  constructor(public name: string, public age: number, public height:number) {}
  hello() {
    console.log(`私の名前は${this.name}です。年齢は${this.age}歳です`);
  }
}

interfaceに定義した型は少なくともClass内で満たす必要があります。つまり、nameやage、helloメソッドのほかに何かプロパティを追加することは可能です。(例としてheightを追加してみました)

ちなみにinterfaceではなく、typeエイリアスでもimpementsを使ってもClassに型を指定できます。

1つのClassに対して、複数のinterfaceを適用する

implementsを使うことで複数のinterfaceをClassに適用させることができます。

interface Students {
  name: string;
  age: number;
  hello(): void;
}

interface Adress {
  postNumber: number;
  adress: string;
}

class StudensGlup implements Students, Adress {
  constructor(public name: string, public age: number, public postNumber: number, public adress: string) {}
  hello() {
    console.log(`私の名前は${this.name}です。年齢は${this.age}歳です`);
  }
}

ClassのstudensGlupにinterfaceで定義したStudentsとAdressの2つを定義したので、constructorにはさらにpostNumberとadressを追加する必要があります。

かならずinterfaceに定義した型がすべて必要なのか?

例えば以下の形だと当然interfaceに定義されていない型があるのでエラーになります。

/*-- エラーになる例 --*/

interface Students {
  name: string;
  age: number;
  hello(): void;
}

const students: Students = {
  name: '山田',
  age: 30,
  specializedField: 'Math', //interface Studentsに定義されていないのでエラー
  hello() {
    console.log(`私の名前は${students.name}です。年齢は${students.age}歳です`);
  },
};

ただ以下のように、一度変数と通すことでエラーは表示されなくなります。

interface Students {
  name: string;
  age: number;
  hello(): void;
}

const students = {
  name: '山田',
  age: 30,
  specializedField: 'Math',
  hello() {
    console.log(`私の名前は${students.name}です。年齢は${students.age}歳です`);
  },
};

const scholl: Students = students; //エラーは表示されない

変数scollは最低でもinterfaceに定義されたStudentsの条件さえ満たせていれば問題ないということ。もちろん、scholl.specializedFieldではアクセスできません。(Studentsの型しか持っていないため)

interfaceのreadonly修飾子を指定して、後から書き込めないようにする

オブジェクトは定義した後でも、プロパティの値を変更することができます。

interface Students {
  name: string;
  age: number;
  hello(): void;
}

const students: Students = {
  name: '山田',
  age: 30,
  hello() {
    console.log(`私の名前は${students.name}です。年齢は${students.age}歳です`);
  },
};

students.name = '鈴木'; //値を変更することができる。

後から変更させたくない場合は、interfaceの型にreadonly修飾子をつけます。

interface Students {
  readonly name: string;
  readonly age: number;
  hello(): void;
}

const students: Students = {
  name: '山田',
  age: 30,
  hello() {
    console.log(`私の名前は${students.name}です。年齢は${students.age}歳です`);
  },
};

students.name = '鈴木'; //エラーになる
students.age = 20; //エラーになる

Classにreadonly修飾子があるinterfaceを指定した場合は?

Classの場合は、interfaceにreadonly修飾子があっても無視されます。

interface Students {
  readonly name: string;
  readonly age: number;
  hello(): void;
}

class StudensGlup implements Students {
  constructor(public name: string, public age: number, public height: number) {}
  hello() {
    console.log(`私の名前は${this.name}です。年齢は${this.age}歳です`);
  }
}

const studentsGlup = new StudensGlup('山田', 30, 3220024);
studentsGlup.name = '鈴木'; //ここで変更可能

8行目のcontructorで、nameやageのプロパティを初期化するときにpublicを指定しているため、こちらが優先されて適用されます。その結果、15行目でnameプロパティが変更できています。

もちろん、constructorでnameやageに対してreadonlyを指定すれば外部から値を変更することはできなくなります。

interfaceをextendsで継承する

interfaceを他のinterfaceに継承させる場合はextendsを使います。

interface Adress {
  postNumber: number;
  adress: string;
}
interface Students extends Adress {
  name: string;
  age: number;
  hello(): void;
}

const students: Students = {
  postNumber: 3330015,
  adress: '東京',
  name: '山田',
  age: 30,
  hello() {
    console.log(`私の名前は${students.name}です。年齢は${students.age}歳です`);
  },
};

Studentsのinterfaceに、Adressのinterfaceをextendsで継承しています。interfaceのStudensを指定された変数studentsは、あらたにpostNumberとadressを追加する必要があります。(12、13行目)

interfaceを複数継承できるのか

interfaceは複数継承できます。

interface SpecializedField {
  specializedField: string;
}
interface Adress {
  postNumber: number;
  adress: string;
}
interface Students extends Adress, SpecializedField {
  name: string;
  age: number;
  hello(): void;
}

typeエイリアスをinterfaceに継承できるのか

typeエイリアスからinterfaceに継承できます。

type SpecializedField = {
  specializedField: string;
};
type Adress = {
  postNumber: number;
  adress: string;
};
interface Students extends Adress, SpecializedField {
  name: string;
  age: number;
  hello(): void;
}

継承元が同じプロパティを持っているとき、上書きされるのか

条件によっては上書きされます。

interface Adress {
  name: string;
  postNumber: number;
  adress: string;
}
interface Students extends Adress{
  name: any;
  age: number;
  //hello(): void;
  hello: () => void;
}

interfaceのStudentsにあるnameにはanyがあり、interfaceのAdressにあるnameにはstringがあります。anyには何でも入るため、nameはany型として上書きされます。

interface Adress {
  name: number;
  postNumber: number;
  adress: string;
}
interface Students extends Adress { //nameのstringに、Adressのnameにあるnumberが割り当てられないのでエラー
  name: string;
  age: number;
  hello(): void;
}

interfaceのStudentsにあるnameにstringを指定して、interfaceのAdressにあるnameにはnumberがある場合は、stringにnumberを割り当てることができないため、エラーになります。

interfaceで関数の型を指定する

関数の型についてinterfaceで指定することもできます。

/* typeエイリアスの場合はこちら
type NumberAdd = (n1: number, n2: number) => number;
*/

interface NumberAdd {
  (n1: number, n2: number): number;
}

const numberAdd: NumberAdd = (num1: number, num2: number) => {
  return num1 + num2;
};

numberAdd(11, 20);

あっても無くても良いプロパティやメソッドは「?」で指定できる

以下のようにnameプロパティやhelloメソッドに対して「?」を指定することで、あっても無くても良い状態にできます。

interface Students {
  name?: string; //もしnameがあるなら…
  age: number;
  hello?(): void; //もしhelloメソッドがあるなら…
}

const students: Students = {
  //nameプロパティやhelloメソッドがなくてもOK
  age: 30,
};

また、関数が受け取るパラメーターに対しても「?」は使えます。注意点は、「?」がついたパラメーターは「?」がついていないパラメーターより後に書かないとエラーになります。

const numberAdd = (num1?: number, num2?: number) => {
  if (num1 && num2) {
    return num1 + num2;
  }
};

具体的にどのように型推論されているか見てみると以下です。

num1やnum2にはundefinedも設定されていて、返す型にもundefinedが設定されているのがわかります。実際にパラメーターに「?」を使う場合はif文で条件分岐して使うようにしましょう。

関数のパラメーターに初期値を設定する

「?」のようにあるか無いかではなく、パラメーターに初期値を設定することもできます。これはTypeScriptの機能ではなく、JavaScriptに備わっている機能です。

const numberAdd = (num1 = 1, num2 = 3) => { //初期値としてnum1には1を、num2には3を代入している
  return num1 + num2;
};
console.log(numberAdd()); //4と表示。 引数に何も設定していないと、num1とnum2にある初期値が入る
console.log(numberAdd(90, 200));//290と表示。 引数があればその値が適用される

パラメーターに初期値を設定した場合は、パラメーターに「?」や型の指定があるとエラーになるので注意しましょう