yaakaito.org

TypeScriptは引数の値によって関数定義を変えられるっぽい

TypeScript

こんにちは!うきょーです!僕は元気です! lib.d.tsを眺めていたら面白い記述があったので、ちょっと調べてみました。これができるのは、0.9.0からっぽいです。

codeplexの行パーマリンクの貼り方がよくわかんないので、申し訳ないんですが。 lib.d.ts0.9.0で開いてinterface Documentとかでサーチするとこんな感じのが出てきます。

1
2
3
4
5
6
7
8
9
10
11
12
// http://typescript.codeplex.com/SourceControl/latest#bin/lib.d.ts
interface Document extends Node, /* .... */ {
    // ...
    createElement(tagName: string): HTMLElement;
    createElement(tagName: "a"): HTMLAnchorElement;
    createElement(tagName: "abbr"): HTMLElement;
    createElement(tagName: "address"): HTMLElement;
    createElement(tagName: "area"): HTMLAreaElement;
    createElement(tagName: "article"): HTMLElement;
    createElement(tagName: "aside"): HTMLElement;
    // ...
}

おなじみのcreateElementです。オーバーロードっぽいですが、よく見ると返り値がtagNameによって違います。 どうやら、渡った値によって関数の定義が変わるっぽい。 実際に動かしてみると、確かに変わってた。

自分で書く事もできて

例えばこんな感じで書けば自分で定義したものに使う事も出来ます。

1
2
3
4
5
6
7
8
9
10
11
interface HOGE {
    f(a: string): any;
    f(a: "string"): string;
    f(a: "number"): number;
}

var hoge: HOGE = {
    f(a: any): any {
        return null;
    }
};

引数も変えられるっぽい

interface SVGSVGElementEventHandlersとかがそうなんですが、こういう感じになってる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// http://typescript.codeplex.com/SourceControl/latest#bin/lib.d.ts
interface SVGSVGElementEventHandlers {
    onresize: (ev: UIEvent) => any;
    addEventListener(type: "resize", listener: (ev: UIEvent) => void, useCapture?: boolean): void;
    onunload: (ev: Event) => any;
    addEventListener(type: "unload", listener: (ev: Event) => any, useCapture?: boolean): void;
    onscroll: (ev: UIEvent) => any;
    addEventListener(type: "scroll", listener: (ev: UIEvent) => any, useCapture?: boolean): void;
    onerror: (ev: Event) => any;
    addEventListener(type: "error", listener: (ev: Event) => any, useCapture?: boolean): void;
    onzoom: (ev: any) => any;
    addEventListener(type: "zoom", listener: (ev: any) => any, useCapture?: boolean): void;
    onabort: (ev: UIEvent) => any;
    addEventListener(type: "abort", listener: (ev: UIEvent) => any, useCapture?: boolean): void;
    addEventListener(type: string, listener: EventListener, useCapture?: boolean): void;
}

イベント名によってlistenerとしてとれる関数が変わる。 listenerinterface EventListenerで定義されていて、EventListenerが取りうる形で制限をかけることができる。 一番最後の

1
addEventListener(type: string, listener: EventListener, useCapture?: boolean): void;

listener: EventListenerじゃなくて、listener: (ev: any) => anyとかやると、全部これと解釈されるっぽい。

こっちも自分で書く事ができて、簡単に書けばこんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface FF {
    (v: string): void;
    (v: number): void;
}

interface HOGE {
    f(a: number, ff: FF): any;
    f(a: "string", ff: (v: string) => void): string;
    f(a: "number", ff: (v: number) => void): number;
}

var hoge: HOGE = {
    f(a: any, ff: FF): any {
        return null;
    }
};

使えるのはstringだけっぽい

数字とか突っ込んでみたけど、パースエラーだった。使えるのはstringだけっぽい。 特に今回みたいなイベント関連とかのために作られた仕様なんかなーという感じ。

あくまで既存のJavaScriptを安全に使う、というの貫いててすげーなと思う。

まとめ

型推論でほとんど隠蔽されるので、意識したり、使うところもほとんどないと思いますけど、 フレームワークとか作る人は覚えておくと便利かもしれません。