TypeScriptのジェネリクスでinstanceof Tしたい

April 14, 2022      

やりたいこと

いろんなものが入った配列から何番目かの要素を選んで,それが期待した型なら受け取る,という操作を考えよう. 気持ち的にはジェネリクスを使って次のように書きたいのだが,これは上手くいかない.TypeScriptがコンパイルされると,ジェネリクスの記述はなくなってしまうので,ランタイムの型チェックでTと表すことはできないようだ.

function get<T>(id:number, arr:Array<any>) : T {
    const m = arr[id]
    if (m instanceof T){
        return m
    }
    else {
        throw Error('unexpected type')
    }
}

コンストラクト関数

解決方法としては,以下のようにコンストラクタ関数を書く,という方法がある.

function get<T>(id:number, arr:Array<any>, cls: new (...args: any) => T) : T {
    const m = arr[id]
    if (m instanceof cls){
        return m
    }
    else {
        throw Error('unexpected type')
    }
}

慣れていないと少し分かりにくい記述なので,分解して考えてみよう. (...args: any) => Tの部分は任意の個数だけany引数をとってTを返すような関数を表している. この部分の頭についているnewはconstruct signatureと呼ばれ,この関数がコンストラクタ関数として使えることを表している. つまりclsは,何かしらの引数をとってTを返すようなコンストラクタ関数になっており,instanceofを使うことで配列arrから取り出したmがコンストラクタ関数clsのインスタンスになっているかどうかをランタイムにチェックできる.

使用例としては以下のような感じになる.

class Foo {
    id: number;
    constructor(id: number) {
        this.id = id;
    }
}

class Bar {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
}

const arr = [new Foo(1), new Bar('bar')]
console.log(get(0, arr, Foo))