TypeScriptのDecoratorで画像組み込み

TypeScriptのv1.5から **Decorator** (デコレータ)の仕組みが入った。

 * [TypeScriptのDecoratorメモ](http://qiita.com/Quramy/items/e3a43bb1734b8a7331e8)
 * [TypeScript 1.5 Alphaに入ったDecoratorsは何をするのか](http://qiita.com/nana4gonta/items/9f6273ead66351705036)

[将来のES7に入る予定](https://github.com/wycats/javascript-decorators)になっていて、[AngularJSなどで活用されている](https://angular.io/docs/js/latest/guide/setup.html#section-set-up-the-starting-component)らしい。似たような仕組みはJavaやC#, Flex2/ActionScript3にもある。

何ができるのかは分かったがAngularJSの例をのぞくと具体的な活用例が浮かばなかった。そこで考えてみたのが次の画像読み込みの例である。

# コード事例

```typescript:(Embed.ts)Decoratorで画像組み込み
function embed(path:string) {
	"use strict";
	return (target:Object, propKey:string)=>{
		target[propKey] = document.createElement("img");
		let img = <HTMLImageElement>target[propKey];
		img.src = path;
		img.alt = "decorator image";
		img.width = 640;
		img.height = 480;
	};
}

class Main{
	@embed("dog-279698_640.jpg")
	image:HTMLImageElement;
	
	constructor(){
		var d = document
				.getElementById("body")
				.appendChild(this.image);
	}
}

var m = new Main();
```

```html
<!DOCTYPE html>
<html>
	<head>
		<title>Embed Test</title>
	</head>
<body id="body">
	<script src="Embed.js"></script>
</body>
</html>
```

`% tsc src/Embed.ts -out src/Embed.js -t "ES5"` でコンパイルして表示した結果が下。

![スクリーンショット 2015-07-04 17.39.47.png](https://qiita-image-store.s3.amazonaws.com/0/54764/8fe49db1-bb45-965a-764b-f4d419f1b284.png "スクリーンショット 2015-07-04 17.39.47.png")

# 解説

`Main`クラスのプロパティ`image`に`@embed`デコレータで画像を組み込んでいる。`@embed`の引数に画像のパスを渡しておくと、`image`プロパティは`HTMLImageElement`,つまりimg要素としてDOMで扱えるノードになっている。

`@embed`デコレータの中身はグローバルに置かれた`embed`関数である。やっていることは簡単で、`createElement`でimg要素を作って返している。画像の"組み込み"と書いたが、単に読み込んでいるだけである。

## 元ネタ

元ネタはFlex2/ActionScript3にあったEmbedメタデータタグ。

```actionscript
[Embed(source='srcimage.png')]
const MyImage:Class;
stage.addChild(new MyImage);
```

元ネタの方はswfフォーマットの中にバイナリとして組み込み、クラスにひもづくデータとして持つ事ができた。使用する際にはクラスのインスタンスとして使うので同じ画像をたくさん表示する場合にも便利だった。

今回のTypeScript版もプロパティではなく元ネタと同じくクラスデコレータにしようと思ったが、うまくいかなかったのであきらめた。

```typescript:やりたかったクラスのデコレータ
@embed("dog-279698_640.jpg")
class EmbededClass{}
```

## もうすこし使いやすくする

```typescript
function embed(
	path:string,
	width:number=640,
	height:number=480,
	alt:string="decorator image"
) {
	"use strict";
	return (target:Object, propKey:string)=>{
		target[propKey] = document.createElement("img");
		let img = <HTMLImageElement>target[propKey];
		img.src = path;
		img.alt = alt;
		img.width = width;
		img.height = height;
	};
}
```

`embed`の引数でもう少し設定できるようにしておくと汎用的に使えるようになる。

```typescript:使うとき
@embed("dog.png",100,100,"可愛い子犬")
imageDog:HTMLImageElement;
@embed("cat.png",200,150,"もふもふ子猫")
imageCat:HTMLImageElement;
```

## 思いつくDecoratorのメリット

今回の例は画像を表示するだけなので、画像を返す関数をつくって`image = embed("src.png");`とこれまでのようにプロパティに代入していく方法と比べ,コード量では同じかむしろ増えている。

だが、Decoratorを使う事で、ぱっと見、プロパティに **情報をちょっと追加しているだけに見えるようになる**。これはコードの量が多くなればなるにつれて効いてくる。

今回の事例は24行の短いコード、やることも画像を表示と明確である。これが数百・数千行、やることもたくさん、となってくると追いかけるのも大変である。

AngularJSや今回の元ネタのActionScript3のFlex2は巨大なフレームワークで、こういったものは大抵、 **『お約束』で書かなければならないコードがある**。お約束コードを書いていると本来自分の書きたい・やりたい処理の部分が『お約束』に埋もれて見通しが悪くなる。

Decoratorはこういった、 **ライブラリ・フレームワークの使用者が知らなくてもいいコードをある意味隠蔽** し、 **やるべきことに集中させてくれる** メリットがある。

今回の例も、『お約束』のDOM周りを意識しないで書きたい事に集中させてくれてる！と言えなくもない（`constructor`の中で思いっきり`appendChild`しているのは気にしない！メソッドのデコレータでうまく意識させないようにできるかも？）。

巨大なライブラリやフレームワークを使わない時でも、複数人や開発したり、あるいは大昔の自分と一緒にコードを書く場合にも似たようなメリットがある。

 * まとめ
	 * 情報をちょっと追加しているだけに見えるようになる
	 * ライブラリ・フレームワークの使用者が知らなくてもいいお約束をうまく隠してくれる
	 * なので本来やりたい事に集中できる
	 * 他人や大昔の自分と一緒にコードを書く時にも便利

# 注意点(v1.5.0-beta)

TypeScript 1.5.0-betaの場合、吐き出すJSのコードが`"use strict";`をつけてstrictモードで実行するとエラーになる。
 * [Decorators are not working in strict mode #3251](https://github.com/Microsoft/TypeScript/issues/3251)

githubの最新版ではなおっているとのことなので,実用で使う場合はそちらにしておいた方がいいと思われる。

# 追記：v1.5.3以降は`--experimentalDecorators`を有効にする必要あり

正式リリースされたv1.5.3以降ではコンパイルオプションで`--experimentalDecorators`を有効にする必要がある。