Interfaceの使い方(型注釈, implements)

TypeScriptにおけるInterfaceの活用方法について確認します。TypeScriptのInterfaceは、オブジェクト指向としての利用だけでなく、型注釈としても活用することができます。

Interfaceの利用用途

他の言語で Interface と聞くと、implements で実装して利用するものというイメージがありますが、TypeScriptでは 型注釈 としても利用されます。

  • 型注釈としての利用
    • implements を必要としない
    • TypeScriptは、Structural Subtyping(構造的部分型)という考え方を採用
      • 型の構造と互換性があるかをチェック
        • インタフェースが要求するプロパティが存在し、要求された型を持つことだけをチェック
  • オブジェクト指向としての利用
    • implements による実装を強制
    • Interfaceでは、定義だけ記述
    • extendsによる多重継承はできないが、複数のインタフェースを実装することは可能

型注釈

型注釈 > 関数の引数

interface Xxx {
    a: string;
    b: number;
    c: string[];
}

function echo(x: Xxx) {
    console.log(x.a);
    console.log(x.b);
    console.log(x.c);
}

// OK
const obj1 = {a: 'aaa', b: 11, c: ['a', 'b']};
echo(obj1);

// OK
// 順序は関係ない
const obj2 = {c: ['a', 'b'], a: 'aaa', b: 11};
echo(obj2);

// OK
// 余計なプロパティが存在していても良い
const obj3 = {a: 'aaa', b: 11, c: ['a', 'b'], d: 'hhh'};
echo(obj3);

// NG
// 直接引数指定を行うとき、余計なプロパティが存在する場合はNG
// Object literal may only specify known properties, and 'd' does not exist in type 'Xxx'.
// echo({a: 'aaa', b: 11, c: ['a', 'b'], d: 'hhh'});

// NG
// プロパティが不足している
// const obj4 = {a: 'aaa', b: 11};
// echo(obj4);

// NG
// プロパティの型が異なる
// const obj5 = {a: 11, b: 11, c: ['a', 'b']};
// echo(obj5);

型注釈 > 関数型(Function)

interface TestFunc {
    (aaa: number, bbb: string): boolean;
}

let funcA: TestFunc;
funcA = (aaa: number, bbb: string): boolean => {
    console.log(bbb);
    return aaa > 0
};

// パラメーター名が異なっていてもOK
let funcB: TestFunc;
funcB = (ccc: number, ddd: string): boolean => {
    console.log(ddd);
    return ccc > 0
};

// パラメーターが不足していてもOK
let funcC: TestFunc;
funcC = (): boolean => {
    return true
};

// 1番目のパラメーターの型が異なるのでNG
// let funcD: TestFunc;
// funcD = (bbb: string, aaa: string): boolean => {
//     return true
// };

// 戻り値の型が異なるのでNG
// let funcE: TestFunc;
// funcE = (aaa: number, bbb: string) => {
//     return 1
// };

プロパティ修飾子
( ?|省略可能, readonly|読込専用 )

interface Yyy {
    a?: string;
    b?: string;
    readonly c: string;
}

function echo(y: Yyy) {
    let parameter = {a: 'aaa', b: 'bbb', c: ''};

    if (y.a) {
        parameter.a = y.a
    }

    if (y.b) {
        parameter.b = y.b
    }

    parameter.c = y.c;


    // readonlyのためNG
    // Cannot assign to 'c' because it is a constant or a read-only property.
    // y.c = 'ccc';

    console.log(parameter);
}


// OK
echo({a: 'AAA', b: 'BBB', c: 'ccc'});   // { a: 'AAA', b: 'BBB', c: 'ccc' }

// OK
// a, bは省略可能項目
echo({c: 'ccc'});                       // { a: 'aaa', b: 'bbb', c: 'ccc' }

オブジェクト指向としての利用

implements|インタフェースを実装

interface でクラスのパブリックなメンバを定義します。
implements で定義した interface を実装します。

interface InterfaceA {
    sampleProperty: string

    sampleFunction(x: number): boolean
}

class ClassA implements InterfaceA {
    sampleProperty: string = 'hello';

    sampleFunction(x: number): boolean {
        return x > 0
    }
}

const classA = new ClassA();
console.log(classA.sampleProperty);         // hello
console.log(classA.sampleFunction(100));    // true

extends|インタフェースを継承

interfaceextends で継承することができます。

interface InterfaceA {
    sampleProperty: string
}

interface InterfaceB extends InterfaceA {
    sampleFunction(x: number): boolean
}

class ClassB implements InterfaceB {
    sampleProperty: string = 'hello';

    sampleFunction(x: number): boolean {
        return x > 0
    }
}

const classB = new ClassB();
console.log(classB.sampleProperty);         // hello
console.log(classB.sampleFunction(100));    // true

複数インタフェースを実装

複数のインタフェースを指定して、実装できます。

interface InterfaceA {
    sampleProperty: string
}

interface InterfaceB {
    sampleFunction(x: number): boolean
}

class MyClass implements InterfaceA, InterfaceB {
    sampleProperty: string = 'hello';

    sampleFunction(x: number): boolean {
        return x > 0
    }
}

const myClass = new MyClass();
console.log(myClass.sampleProperty);         // hello
console.log(myClass.sampleFunction(100));    // true

参考