DenoでTypeScriptのDecoratorに入門してみる
はじめに
こんにちは、ヨシです。
最近楽しみなこととして、今年の 5 月に東京で TypeScript の一大イベントである TSKaigi が開催されます。TypeScript 好きの自分としては参加するしかないということで、現在はプロポーザル資料を誠意作成中です。
ということで今日の記事の内容になりますが、最近入ったプロジェクトでは、Vuex というライブラリを TypeScript で利用するために vuex-module-decorators というライブラリを合わせて使用しています。このライブラリでは Vuex のモジュールをクラスで記載し、mutation や action などを Decorator を付与したメソッドの形式で記述することができます。
Decorator は TypeScript を使っていても特定のライブラリなどを使わない限りはなかなか使用しない機能です。
今回の記事では、Decorator つながりということで、Deno の v1.40 以降で使えるようになった TypeScript の Decorator について紹介したいと思います。
Decorator とは
Decorator は TC39 で stage3 の段階にあるプロポーザルではありますが、様々なライブラリですでに利用されている機能です。TypeScript は v5.0 から --experimentalDecorators
のコンパイラオプションのフラグが不要になり、stage 3 の Decorator が利用できるようになりました。
TS のライブラリでこれまで利用されてきたデコレータは stage 2 のものらしく、それらの間に互換性はないため注意が必要です。
Decorator 機能とは、簡単に言えば、既存のクラスのメソッドなどに対して前後で追加したい処理を関数の形で記述し、メソッドに対して @additionalMethod
のような形でデコレーションすることで再利用可能な形でまとめつつ追加することができるという機能です。
stage3 の Decorator は以下のような形になっています。
function methodDecorator(
target: Function,
context: ClassMethodDecoratorContext
) {
// ...
}
第一引数にはデコレーション対象とする関数を受け取り、第二引数ではデコレーション対象のコンテキスト情報を持つオブジェクトを受け取ります。コンテキストオブジェクトの型はデフォルトで用意された型で、デコレーション対象がクラスメソッドの場合には ClassMethodDecoratorContext
を使用します。
そして、このように定義した Decorator メソッドをデコレーションしたい対象のメソッドの上に @
をつけて記述します。
class Hoge {
// ...
@methodDecorator
myMethod() {
// ...
}
}
これによって、対象のメソッドが Decorator で置き換えられて付加的な処理を実行することができるようになります。
なお、デコレータの付与はクラスのメソッド以外にもクラス自体や、フィールドなどに対しても行うことができます。
Deno の Decorator
Deno では v1.40 以降から stage3 の Decorator が正式にサポートされました。
TypeScript 自体は v5.0 以降から stage3 が利用できましたが、Deno ではコンパイラとして tsc と組み合わせて利用している Rust 製の SWC が Decorator を サポートできていなかったので すこし遅れてのサポートとなりました。
実際に利用してみる
それでは、試しに実際に stage3 Decorator を使ってみたいと思います。Decorator 関数の書き方は上で説明したやり方を基本としてデコレーション付与対象のメソッドを置き換える関数を返すようにします。さらに、その関数でもとの関数の返り値を返すようにします。
function trace(fn: Function, ctx: ClassMethodDecoratorContext) {
return function (...args: unknown[]) {
console.log("Start", ctx.name);
const v = fn(...args);
console.log("End");
return v;
};
}
デコレータは private メソッドや static メソッドにも付与することができ、そういったメタ情報はコンテキストオブジェクトから取得することが可能です。
class MyClass {
@trace
static sayHello() {
console.log("Hello");
}
}
MyClass.sayHello();
さて、これを記述した ts ファイル sample.ts
を実行してみます。
❯ deno run sample.ts
Start sayHello
Hello
End
無事にデコレートしたメソッドの出力結果を得ることができました。
vuex-module-decorators の Decorator を見てみる
どうせならということで、stage 2 の Decorator を利用している vuex-module-decorators の Action 用デコレータ関数 を見てみます。このデコレータ関数の使い方自体はほぼ stage3 と同じで、@Action
のように対象メソッドに付与します。
@Action
async fetchNewWheels(wheelStore: string) {
const wheels = await get(wheelStore)
this.context.commit('addWheel', wheels)
}
stage2 とは異なるのはその定義方法であり、実際の定義 では引数の数と型注釈が大分異なることが理解できます。また、Action
のデコレータ関数は以下のようにオーバーロードで多重定義されています。
export function Action<T, R>(
target: T,
key: string | symbol,
descriptor: TypedPropertyDescriptor<(...args: any[]) => R>
): void;
export function Action<T>(params: ActionDecoratorParams): MethodDecorator;
export function Action<T, R>(
targetOrParams: T | ActionDecoratorParams,
key?: string | symbol,
descriptor?: TypedPropertyDescriptor<(...args: any[]) => R>
) {
//...
}
stage 3 ではオリジナルのメソッドとコンテキストオブジェクトの2つが引数となっていましたが、stage2 では付与対象のメソッド以外に key
と descriptor
という引数が必要となっています。
このようにデコレータが使用されているライブラリを使う側ではそこまで違いについて意識しなくてもよさそうですが、しっかりと理解するには stage2 と stage3 の違いを意識していく必要がありそうです。
まだまだ Decorator について理解できていないところも多いですが、Deno を使って Decorator に入門してみました。
Temporal
少し話が変わりますが、Deno では v1.40 から Temporal API も利用できるようになりました。Temporal は Date オブジェクトの問題点を改善した ECMAScript の新しいグローバルオブジェクトであり、現在は ECMAScript の stage3 のプロポーザルとなっています。
Deno ではこのようにもうすぐで ECMAScript に正式に取り入れられるであろういくつかの新しいプロポーザルを先んじて利用できるようにサポートしており、実際に使って色々実験が手軽にできるようになっています。
個人的に Deno はとても好きなのでこれからも定期的に情報を追っていきたいと思います。
- Javascript , TypeScript , Deno