Document.createElement
のMDNドキュメントはこちら
Document.createElement
は、引数としてタグ名を文字列リテラルで渡すと、それに該当するタグのオブジェクトが帰ってきます。
文字列リテラル"a"
を引数として渡すと、返り値のオブジェクトはHTMLAnchorElement
に推論されます。"p"
ならばHTMLParagraphElement
に、"span"
ならばHTMLSpanElement
に、そのタグ名に相当する型に返り値型が推論されます。
const a = document.createElement("a"); // HTMLAnchorElementと推論される
const p = document.createElement("p"); // HTMLParagraphElementと推論される
const span = document.createElement("span"); // HTMLSpanElementと推論される
TypeScriptの公式サイトの「Advanced Type」の「String Literal Types」には、このメソッドがString Literal Typesとオーバーロードを用いた活用例として載っています。
TypeScript v2.1.2のib/lib.dom.d.tsより一部。
createElement(tagName: "a"): HTMLAnchorElement;
createElement(tagName: "applet"): HTMLAppletElement;
createElement(tagName: "area"): HTMLAreaElement;
~略~
createElement(tagName: "p"): HTMLParagraphElement;
createElement(tagName: "param"): HTMLParamElement;
createElement(tagName: "picture"): HTMLPictureElement;
createElement(tagName: "pre"): HTMLPreElement;
createElement(tagName: "progress"): HTMLProgressElement;
~略~
createElement(tagName: "x-ms-webview"): MSHTMLWebViewElement;
createElement(tagName: "xmp"): HTMLPreElement;
createElement(tagName: string): HTMLElement;
このように、TypeScript v2.1.2までは「String Literal Types」とオーバーロードを駆使して、型定義ファイルが作成されていました。
ところが、TypeScript v2.1.4でDocument.createElement
の型定義が変わります。「String Literal Types」を使っていません。こちらのコミットで変わったようです。
次のような型定義となっています。
TypeScript v2.1.4のib/lib.dom.d.tsより一部。
createElement<K extends keyof HTMLElementTagNameMap>(tagName: K): HTMLElementTagNameMap[K];
createElement(tagName: string): HTMLElement;
HTMLElementTagNameMap
は次のようなインターフェースです。
interface HTMLElementTagNameMap {
"a": HTMLAnchorElement;
"applet": HTMLAppletElement;
"area": HTMLAreaElement;
~略~
"p": HTMLParagraphElement;
"param": HTMLParamElement;
"picture": HTMLPictureElement;
"pre": HTMLPreElement;
"progress": HTMLProgressElement;
~略~
"x-ms-webview": MSHTMLWebViewElement;
"xmp": HTMLPreElement;
}
TypeScript 2.1から導入された keyofとHTMLElementTagNameMap
を使った型定義ファイルに変わりました。
このようにkeyofを活用することで
-
"a"
ならばHTMLAnchorElement
-
"p"
ならばHTMLParagraphElement
-
"span"
ならばHTMLSpanElement
という型推論が聞くようになります。
記事執筆時の最新の型定義ファイル「TypeScript v3.5.3のlib/lib.dom.d.ts」では、さらに改良が進んでいます。
HTMLElementTagNameMap
は、createElement
だけでなく、getElementsByTagName
やquerySelector
でも活用されています。
/**
* Creates an instance of the element for the specified tag.
* @param tagName The name of an element.
*/
createElement<K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K];
/** @deprecated */
createElement<K extends keyof HTMLElementDeprecatedTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementDeprecatedTagNameMap[K];
createElement(tagName: string, options?: ElementCreationOptions): HTMLElement;
/**
* Retrieves a collection of objects based on the specified element name.
* @param name Specifies the name of an element.
*/
getElementsByTagName<K extends keyof HTMLElementTagNameMap>(qualifiedName: K): HTMLCollectionOf<HTMLElementTagNameMap[K]>;
getElementsByTagName<K extends keyof SVGElementTagNameMap>(qualifiedName: K): HTMLCollectionOf<SVGElementTagNameMap[K]>;
getElementsByTagName(qualifiedName: string): HTMLCollectionOf<Element>;
/**
* Returns the first element that is a descendant of node that
* matches selectors.
*/
querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;
querySelector<K extends keyof SVGElementTagNameMap>(selectors: K): SVGElementTagNameMap[K] | null;
querySelector<E extends Element = Element>(selectors: string): E | null;
/**
* Returns all element descendants of node that
* match selectors.
*/
querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K): NodeListOf<HTMLElementTagNameMap[K]>;
querySelectorAll<K extends keyof SVGElementTagNameMap>(selectors: K): NodeListOf<SVGElementTagNameMap[K]>;
querySelectorAll<E extends Element = Element>(selectors: string): NodeListOf<E>;
このようにタグ名の文字列リテラルと型の結びつけをHTMLElementTagNameMap
に集約することで、String Literal Typesよりもエレガントに型を定義することができます。