読者です 読者をやめる 読者になる 読者になる

JSX の型を整理してみた

JSX の型はかなり複雑なことになっている気がしたので、整理してみました。

プリミティブ型、オブジェクト型、可変型、未定義許可型

JSX における型は、この 4 種類に分類されるらしいです。

プリミティブ型

プリミティブ型は現在、

  • boolean
  • int
  • number
  • string

の 4 種類があります*1
これらの型を持つ変数には null を入れることができません。

var x: int = null; // compile error!

また、これらの型の値は変更不可能 (イミュータブル) となります。
3 がいつの間にか 4 に変わっていたりしてほしくないですよね?
"hoge" という文字列の o という文字がいつの間にか a に変わっていて "hage" とか悲しいですよね?
これらの型の値では、そのようなことは起こりません。

オブジェクト型

オブジェクト型は例えば、

  • string 配列
  • 関数
  • Date
  • Object

などが該当します。
class などによりユーザが定義する型は全てオブジェクト型となります。
プリミティブ型とは違い、これらの型を持つ変数には null を入れることができます。

var xs: int[] = null; // OK

また、プリミティブ型とは違い、オブジェクト型は全てがイミュータブルというわけではありません。

class ImmutableSample {
  var _x: int;
  function constructor(x: int) { this._x = x; }
  function x(): int { return this._x; }
}
class MutableSample {
  var _x: int;
  function constructor(x: int) { this._x = x; }
  function x(): int { return this._x; }
  function setX(x: int): void { this._x = x; }
}

この例では、ImmutableSample のオブジェクトに対しては (_で始まるメンバにはアクセスしない、という約束事を守ると) 生成後にその状態を変更することはできません。
それに対して、MutableSample のオブジェクトに対しては、setX を呼び出すことで生成後にオブジェクトの状態を変更することができます。

可変型

可変型はコンパイル時に定まらない型のために用意されています。
この型の変数には null を入れることが可能です。
チュートリアルには、「この型の値に対してできるのは同じかどうかの判定だけで、何かやりたければキャストが必要になる」というようなことが書いてあります。
が、現在はプロパティアクセスなら許しているようです。
プロパティアクセスも禁止するようになったようです。不便になったと感じるかもしれませんが、自分としてはプロパティアクセスも不可の方が正しいと思います。

JavaScript とちがい、typeof 演算子は可変型のみに許されています*2
また、可変型を表すキーワードには variant を使います。個人的には any 型とか別の言葉を使ってほしかったところです。


可変型は null を扱えますので、こんなことも可能です。

var v: variant = null;
var i: int = v as int;
log i; // => 0

boolean の場合 false に、string の場合は null という文字列に変換されるようです。
string の場合は空文字列とかになってほしいような気もします。

未定義許可型

未定義許可型は、未定義 (undefined) という値を取りうる唯一の型です*3
例えば、0 での割り算を undefined にしたい場合、

static function div(a: int, b: int): MayBeUndefined.<int> {
  if (b == 0) return undefined;
  return a / b;
}

このように、MayBeUndefined. とすると、T が受け付ける値以外に、undefined という特殊な値も扱えるような型になります。
ただし、この T に MayBeUndefined 自身を指定するとコンパイルエラーになるようです。
また、可変型を指定してもコンパイルエラーとなります。


未定義許可型は、T の扱える範囲に加えて undefined が扱える型ですので、T がプリミティブ型であれば null は扱えませんし、オブジェクト型であれば null も扱える、という非常に特殊な型となっています。

var x: MayBeUndefined.<int> = null;  // コンパイルエラー
var y: MayBeUndefined.<Date> = null; // OK

その他特殊な型

JSX の型はプリミティブ型、オブジェクト型、可変型、未定義許可型の 4 種類と書きましたが、これらに (おそらく) 属さない特殊な型や、JSX 処理系によって特別扱いされている型もあります。

配列型

配列はオブジェクト型の属しますが、扱いはかなり特殊です。
配列の型は Array. と書きますが、略記法として T[] と書くこともできます。
また、Array. の要素の型は T ではなく、MayBeUndefined. になります。
このあたり、Java などから入る人には罠だと思います。
これはサイズを指定した配列を作った時に、JavaScript では配列の各要素が undefined で埋められるのが主な原因と思われます。


Array. には更に特殊な扱いがあります。
それは、T に可変型が指定できることです*4
未定義許可型の T には可変型が指定できない、と書きましたが、実はできる方の方が特殊なのです。
そのため、JSX では「なんでも入るコレクション」を作ることはできません。
追記:これは自分の勘違いでした。コメント欄参照。


他に不思議なこととして、

var xs = [1, 2, undefined]: int[];
var ys = new int[3];
ys[0] = 1; ys[1] = 2;
log xs; // => [ 1, 2, undefined ]
log ys; // => [ 1, 2,  ]

となります。ううん・・・?

void 型

void 型は値を返さない関数の戻り値に指定します。
それ以外の場所では指定できません。

null 型

null 型は null リテラルに型を指定しなかった場合に付く型です*5
この型は、JSX コード中では見ることはできないと思われます。
似たような型として undefined 型というのも JSX コンパイラの中にはあるのですが、おそらくこれは使われていません。

こんな感じで

JavaScript に引っ張られたりバグなのか仕様なのかよく分からん動きをしたりするので、割と複雑です。
ルールさえわかってしまえばどうということはないのですが、そういう文章が JSX はあまり整備されていませんので、勝手にまとめてみました。

*1:int の扱いは色々と微妙なので、もしかすると今後消えるかもしれません

*2:typeof 演算子JavaScript に落ちたときの型を調べる演算子となっているため、JSX の型を調べたいときには使えないことを覚えておきましょう。

*3:未定義許可型は、Nullable が導入された場合はもしかすると非推奨扱いになるかもしれません。

*4:他にも Map. の T には可変型が指定できる

*5:つまり、null リテラルには型を指定することができます。var x = null: Date; とか書けます。