文字列で指定する既知のパラメータをTypeScriptで型付けする
1. Enumに置き換える
JavaScriptでは、ライブラリ等に渡すパラメータを文字列で指定することが多いのですが、指定可能な値が分からなかったり、タイプミスによるバグを作り込む可能性があったりで嫌ですよね。
例えば以下のような場合、lineCap
には"butt" / "round" / "square"
が指定可能なのですが、ドキュメントを調べないと分かりませんし、 Typoしても気付き難いです。
//Canvasに描画する線の終端部分の形状を指定する var rc = canvas.getContext("2d"); rc.lineCap = "round"; //"butt" or "round" or "square"
このような場合、TypeScriptにあるEnum型が使えそうです。
//設定可能な値をEnumで定義 enum LineCap { butt, round, square } var rc = canvas.getContext("2d"); //enumの要素名を文字列で取得 rc.lineCap = LineCap[LineCap.round];
enumの要素名(butt, round, square)を取得するには、LineCap[lineCap.round]
の様に記述する必要があるのですね。
(C#のenum に慣れていると、LineCap.round.toString();
とやりたくなるのですが、LineCap.round
は単なる数値(1)なので、この場合"1"
が設定されてしまいます。)
あんまり嬉しくない?
上記サンプルでは、記述量が多くなるだけであまり有難味が無いように思えます。
rc.lineCap = LineCap[LineCap.round]
のような記述も不細工です。
実際に使う際には、enum ⇔ 文字列パラメータ の変換部分をラッパークラス等で隠ぺいして、enum の受け渡しだけで済むようにした方が良いでしょう。
例として、Canvasに描画するストロークのスタイルを指定するためのクラス(Penクラス)を作ってみます。
enum LineCap { butt, round, square } enum LineJoin { bevel, round, miter } class Pen { constructor(public color: Color, public width: number= 1, public lineDash: number[]= [], public lineCap: LineCap= LineCap.butt, public lineJoin = LineJoin.bevel, public miterLimit: number = 10.0) { } //ストロークのスタイルをまとめて設定する。 public applyTo(rc: CanvasRenderingContext2D) { rc.strokeStyle = this.color.cssColor; if (rc.setLineDash != undefined) { rc.setLineDash(this.lineDash); } rc.lineWidth = this.width rc.lineCap = LineCap[this.lineCap]; rc.lineJoin = LineJoin[this.lineJoin]; rc.miterLimit = this.miterLimit; } }
このクラスを使う側では、コンストラクタで各種パラメータを指定するのですが、
その際、(IDEを使用していることが前提ですが、)LineCap および LineJoin では入力補完が利き、一覧から選択するだけでOKとなります。
var rc = canvas.getContext("2d"); //IDEを使用していれば、`LineCap.`や`LineJoin.` と入力して、入力候補から選ぶだけ! var pen = new Pen(Color.Red, 1, [], LineCap.Round, LineJoin.Round); pen.applyTo(rc);
TypeScriptのenum について
enumをJavaScriptにコンパイルすると、以下のような連想配列に展開されるようです。
var LineCap; (function (LineCap) { LineCap[LineCap["butt"] = 0] = "butt"; LineCap[LineCap["round"] = 1] = "round"; LineCap[LineCap["square"] = 2] = "square"; })(LineCap || (LineCap = {})); /* LineCap[0] = "butt"; LineCap[1] = "round"; LineCap[2] = "square"; LineCap["butt"] = 0; LineCap["round"] = 1; LineCap["square"] = 2; */ //LineCap.round は、コンパイルするとNumberのリテラルに置き換えられる。 //var a = LineCap.round; var a = 1 /* round */; //var b = LineCap[LineCap.round]; var b = LineCap[1 /*round*/];
シンボルとしては使えない文字列を使用したい場合
引用符で囲めば、enumの要素名として使用できるようですが、インデクサからでしかアクセスできなくなってしまう為、残念ながら今回のような用途には使えそうにありません。
//これはNGだけど enum Test{ 1ab = 0, //数字から始まる abc def = 1, //スペースを含む } //これならOK、コンパイルが通る enum Test{ "1ab" = 0, "abc def" = 1, }
但し、アクセスはインデクサからのみとなります。入力候補にも出てきません。
Test.1ab //コンパイルエラー Test."1ab" //コンパイルエラー Test["1ab"] //OK
2. Static フィールドに置き換える
以下のように、Staticなフィールドを持つクラスに置き換える方法でも良いかもしれません。 これであれば、置き換えたいパラメータがどんな文字列でも(シンボルとして使えなくても)関係なく使えます。
class LineCap { constructor(private _index:number, private _value:string){} public get index():number { return this._index;} public get value():string { return this._value} static Butt:LineCap = new LineCap(0,"butt"); static Round:LineCap = new LineCap(1,"round"); static Round:LineCap = new LineCap(2,"square"); } var lineCapIndex = LineCap.Round.index; //1 var lineCapString = LineCap.Round.value; //"round"
以下のような基底クラスを用意しておけば、定義が楽になりますし、文字列以外の型も使えて便利です。
class EnumBase<TValue> { constructor(private _index:number, private _value:TValue){} public get index():number { return this._index;} public get value():TVale{ return this._value} } class LineCap extends EnumBase<string>{ static Butt:LineCap = new LineCap(0,"butt"); static Round:LineCap = new LineCap(1,"round"); static Round:LineCap = new LineCap(2,"square"); }
最後に
今回のような用途で、enumを使うのはちょっと無理があるような気がしてきました。
以下の様にEnumの値を文字列で指定出来たらいいのに
enum Test{ None = "", Hoge = "hoge", Piyo = "piyo piyo" }