TypeScriptのv1.5から Decorator (デコレータ)の仕組みが入った。
将来のES7に入る予定になっていて、AngularJSなどで活用されているらしい。似たような仕組みはJavaやC#, Flex2/ActionScript3にもある。
何ができるのかは分かったがAngularJSの例をのぞくと具体的な活用例が浮かばなかった。そこで考えてみたのが次の画像読み込みの例である。
コード事例
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();
<!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"
でコンパイルして表示した結果が下。
解説
Main
クラスのプロパティimage
に@embed
デコレータで画像を組み込んでいる。@embed
の引数に画像のパスを渡しておくと、image
プロパティはHTMLImageElement
,つまりimg要素としてDOMで扱えるノードになっている。
@embed
デコレータの中身はグローバルに置かれたembed
関数である。やっていることは簡単で、createElement
でimg要素を作って返している。画像の"組み込み"と書いたが、単に読み込んでいるだけである。
元ネタ
元ネタはFlex2/ActionScript3にあったEmbedメタデータタグ。
[Embed(source='srcimage.png')]
const MyImage:Class;
stage.addChild(new MyImage);
元ネタの方はswfフォーマットの中にバイナリとして組み込み、クラスにひもづくデータとして持つ事ができた。使用する際にはクラスのインスタンスとして使うので同じ画像をたくさん表示する場合にも便利だった。
今回のTypeScript版もプロパティではなく元ネタと同じくクラスデコレータにしようと思ったが、うまくいかなかったのであきらめた。
@embed("dog-279698_640.jpg")
class EmbededClass{}
もうすこし使いやすくする
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
の引数でもう少し設定できるようにしておくと汎用的に使えるようになる。
@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モードで実行するとエラーになる。
githubの最新版ではなおっているとのことなので,実用で使う場合はそちらにしておいた方がいいと思われる。
追記:v1.5.3以降は--experimentalDecorators
を有効にする必要あり
正式リリースされたv1.5.3以降ではコンパイルオプションで--experimentalDecorators
を有効にする必要がある。