9
7

More than 5 years have passed since last update.

HaxeでjavascriptのDOMの型をキャストする時

Last updated at Posted at 2014-02-08

Haxeでjavascriptを書いている時にちょっと困ったことがありました。

困ったこと

HaxeではDOMエレメントを厳密な型に分けて管理しています。

例えば、inputタグならInputElementとかformタグならFormElementとか...
これらの型は基底となるElementを継承することで表現しています、しっかりタイプ分けされていて気持ちいのですが...

以下のようなコードをjavascriptの感覚で書けないのです。

<input type="text" id="foo" value="foo">
var element = document.getElementById("foo");
console.log(element.value); // -> foo javascriptならOKだ!!
class Main {
  function foo() {
    var element = Browser.document.getElementById("foo");
    trace(element.value); // -> js.html.Element has no field value Haxeはコンパイルエラー...
  }
}

Browser.document.getElementByIdElement型を返すので、
取得したDOMオブジェクトをInputElementとして扱うことは出来ません。(Element型はvalueプロパティを持っていない)

当然解決策は用意されていますが...

untypedキーワードを利用することでコンパイルチェックを外すことが可能です。
或いは、代入する変数の型をDynamicにしておくことでも対処可能です。
しかし、いづれの方法もHaxeの型システムを放棄するものですので、あまり利用したくはありません。

class Main {
  function foo() {
    var element1 = Browser.document.getElementById("foo");
    trace(untyped element.value); // -> foo
    var element2:Dynamic = Browser.document.getElementById("foo");
    trace(element.value); // -> foo
  }
}

cast関数を利用することで、異なる型に変換することが可能です。
castを利用すればほぼ解決出来るのですが、どんな型にでも変換出来てしまうという危険性も備えています。

class Main {
  function foo() {
    var element1 = cast( Browser.document.getElementById("foo"), InputElement );
    trace(element.value); // -> foo
    var element2 = cast( Browser.document.getElementById("foo"), NodeList );
    trace(element.value); // -> js.html.NodeList has no field value
  }
}

変換可能な型を限定するためのCastクラスを作って解決する

Haxeの機能にcast可能な型を限定しておける機能があるかはよく分からなかったので、
独自にcastする処理を書いて解決してみました。

class FromElement {
  var element: Element;

  public function new(element: Element) {
    this.element = element;
  }

  public function toInputElement(): InputElement {
    return cast( this.element, InputElement );
  }
}
import FromElement;

class Main {
  function foo() {
    var element = Browser.document.getElementById("foo");
    trace(new FromElement(element).toInputElement().value); // -> foo
  }
}

castするクラスを利用することでcast可能な型を限定出来るようになり型安全なcastが可能になりましたが、
記述がやや冗長になります。
(From To の関係がコードから分かるので、この冗長さはある意味綺麗なものだとも思いますが...)

抽象型を作って暗黙的に型をcastする

Haxe3からは抽象型という機能が追加され、型の定義を柔軟に設定出来るようになりました。
Element型をcastするための抽象型を定義します。

abstract CastElement(Element) {
  public function new(element: Element) {
    this = element;
  }
  @:to public function toInputElement(): InputElement {
    return cast(this, InputElement);
  }
}
import CastElement;

class Main {
  function foo() {
    var element: InputElement = new CastElement( Browser.document.getElementById("foo") );
    trace(element.value); // -> foo
  }
}

抽象型を利用すると、代入先の型へのキャストが可能であればその型に暗黙的に変換して代入してくれます。
castクラスを定義して利用するよりコードがスッキリ...したような気もします。

まとめ

まとめではないけど、もっといい方法ご存じの方がいらっしゃれば是非教えていただきたい。

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7