Typescript instanceof kotlin風
Typescriptのinstanceofをkotlinのwhen文+isのような感じでできないかを検討してみました。
経緯
vueの実装をしている際に
<template>
<ChildComponent
@event:change="handelChange"
@event:add="handelAdd"
@event:delete="handelDelete"
@event:failed="handelFailed"
@event:hoge="handelHoge"
@event:fuga="handelFuga"
/>
</template>
のように、子のコンポーネントからイベントを受け取って何かを処理することがあると思うのですが、イベントが増えるたびにカスタムイベントを増やさないといけないのが個人的にめんどくさいなと感じてしまいましてですね。。
じゃあカスタムのイベントを1つにすればええやんって話にはなるのですが、それをすると今度は、
<template>
<ChildComponent
@event="handleEvent"
/>
</template>function handleEvent(e: Event) {
if (e instanceof Hoge) {
// なんか処理
return
} if (e instanceof Fuga) {
// なんか処理
return
} ・
・
・
// 以降も同じような処理が続く}
のようにeventを受け取って処理するところがif文地獄になり、これはこれで嫌だなー、もうちょっと上手いやり方ないかなー(こわいなーこわいなー)、ってことで試してみました。(kotlin風でtryしたのはただ単に言語の中で一番好きなヤーツだからってだけです。)
結果
以下のように実装してみました。
/**
* kotlinのwhen文ライクにinstanceofを実行するためのインターフェース
*/
export interface InstanceofLikeKotlinWhen<T> {
/**
* インスタンスの判定メソッド
* @param clazz 判定したいクラス
* @param fn クラスが一致した場合に実行されるコールバック
*/
is<Type>(
clazz : new (...args: any) => Type,
fn : (target:Type) => void
) : InstanceofLikeKotlinWhen<T>
/**
* 上記のisメソッドに引っ掛からなかった場合に実行する処理
* @param fn コールバック
*/
else(fn: (target:T) => void) : void
}
/**
* InstanceofLikeKotlinWhenインターフェースの実体
*/
class InstanceofLikeKotlinWhenImpl<T> implements InstanceofLikeKotlinWhen<T> {
// 判定したい対象のインスタンス
private readonly target : T
// isメソッドの実行でインスタンスが一致した場合にtrueとなる
private hit : boolean
constructor(target : T) {
this.target = target
this.hit = false
}
is<Type>(
clazz : new (...args: any) => Type,
fn : (target:Type) => void
) : InstanceofLikeKotlinWhen<T> {
if (this.hit) {
// すでに一致済みのisメソッドが実行されていた場合は何もせずに終了
return this
}
if (this.target instanceof clazz) {
// インスタンスが一致した場合はコールバックを実行
this.hit = true
fn(this.target)
}
return this;
}
else(fn: (target:T) => void) : void {
if (!this.hit) {
// isメソッドの判定に一度も引っ掛からなかった場合はコールバック実行
fn(this.target)
}
}
}
export function when<T>(target: T) : InstanceofLikeKotlinWhen<T> {
return new InstanceofLikeKotlinWhenImpl<T>(target)
}
使い方としては以下のように子のコンポーネント側でイベントを定義してあげて、それらを同じカスタムイベントで状況に応じてemitします。
// イベント用のマーカーインターフェース
export interface HogeEvent{}// イベントその1
class HogeComplete implements HogeEvent{}
// イベントその2
class HogeFailed implements HogeEvent{
readonly message : string
constructor(message: string) {
this.message = message;
}
}
で、イベントを受ける側にwhen文を書いてあげます。
<template>
<ChildComponent
@event="handleEvent"
/>
</template>function handleEvent(e: HogeEvent) {
when(e)
.is(HogeComplete,(v) =>{
console.log("HogeComplete")
})
.is(HogeFailed,(v) => {
console.log("HogeFailed")
// キャストされた値でコールバックが実行されるので、
// 実体内のフィールドにもアクセスできる
console.log(v.message)
})
.else(()=>{
console.log("else")
})
}
所感
とりあえず、やってみての感想ですが、個人的にはこっちの方がスッキリ書けるので超絶好みですが、記述量が劇的に改善されるってわけでもないし、なんかオラオラ感も感じるので実際の開発で取り入れるかどうかは悩ましいところです。あとは今回interfaceで実装したけど、これもより上手いやり方がありそうな気がしなくもない。。。(Typescriptよくわかっていないマン)
ネセセローン!!!