Typescript instanceof kotlin風

Typescriptのinstanceofをkotlinのwhen文+isのような感じでできないかを検討してみました。

経緯

<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")
})
}

所感

ネセセローン!!!

--

--

engineer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store