TypeScriptでswitchをオブジェクトで置き換える
JavaScript(TypeScript)のswitch文をオブジェクトで書き換えることで、コードをより簡潔に書ける場合があります。
switch文をオブジェクトリテラルに変換する簡単な例
例えば処理させたい情報を持ったtodo
と、todo
の処理内容を記述した関数(foo
,bar
など)があったとします。
これを愚直にswitch
を使って書くと以下のようになります。
const foo = () => {
console.log('doing foo...');
};
const bar = () => {
console.log('doing bar...');
};
const todo = ['foo', 'bar', 'hoge', 'toString', 'constructor'];
// switch version
todo.map((value) => {
switch (value) {
case 'foo':
foo();
break;
case 'bar':
bar();
break;
default:
console.log(`${value} is not supported`);
break;
}
});
このコードの出力結果は以下のようになります。
doing foo...
doing bar...
hoge is not supported
toString is not supported
constructor is not supported
ただし、このコードには以下の問題点があります。
- 対応するメソッドを編集する度に
switch
文の中身も編集しなければならない。 -
break;
を忘れがち
そこで以下のようにオブジェクトを使って書き直すことによって、switch
文が抱える問題を解消できます。
// object literal version
const switcher = {
foo,
bar,
};
todo.map((value) => {
Object.hasOwn(switcher, value)
? switcher[value as keyof typeof switcher]()
: console.log(`${value} is not supported`);
});
この書き方であれば、メソッドを追加する時はswitcher
オブジェクトに追加するだけで良くなります。
オブジェクトリテラルによるswitchの応用例
例えばクローラーを作っていて、URLごとに処理を変更したい場合などに、このテクニックが使えます。
new URL()
を使ってURLをパースし、URL.hostname
でドメインごとに動作を切り替えます。ただし、サイトによってはPC版とモバイル版でサブドメインが異なっている場合があるため、単にhostname
を使っても思うように切り替えできません。例えばYoutubeは同じ動画に対して4種類以上ものURLが存在します。
'https://www.youtube.com/watch?v=bQqN0fK3Gjg', // PC版YoutueのURL
'https://youtu.be/bQqN0fK3Gjg', // PC版Youtubeで生成した共有用のURL
'https://m.youtube.com/watch?v=bQqN0fK3Gjg', // Mobile版YoutubeのURL
'https://www.youtube.com/watch?app=desktop&v=bQqN0fK3Gjg', // Mobile版YoutubeのURLをPCで開いた時のURL
そこでpsl (Public Suffix List)というパッケージを使って、サブドメインを削除したドメインを取得します。
npm i psl
npm i --save-dev @types/psl
psl.get()
を使うとサブドメインを削除したドメインを取得できます。
import psl from 'psl';
const Urls = [
'https://www.youtube.com/watch?v=bQqN0fK3Gjg', // PC版YoutueのURL
'https://youtu.be/bQqN0fK3Gjg', // PC版Youtubeで生成した共有用のURL
'https://m.youtube.com/watch?v=bQqN0fK3Gjg', // Mobile版YoutubeのURL
'https://www.youtube.com/watch?app=desktop&v=bQqN0fK3Gjg', // Mobile版YoutubeのURLをPCで開いた時のURL
];
Urls.map((url) => {
const host = new URL(url).hostname;
const domain = psl.get(host);
console.log(domain);
});
以下のような出力が得られます。
youtube.com
youtu.be
youtube.com
youtube.com
残念ながら、youtu.be
ドメインに関してはyoutube.com
として変換してくれませんが、www
やm
などのサブドメインを含んでいるURLからyoutube.com
を抽出することに成功しました。
以下のコードはURLのリストをサイトごとに振り分けるコードの例です。switch
の代わりにオブジェクトリテラルを使うことで、コードの見通しやメンテナンス性が上がったことを実感していただければ幸いです。
import psl from 'psl';
const Urls = [
'https://www.youtube.com/watch?v=bQqN0fK3Gjg', // PC版YoutueのURL
'https://youtu.be/bQqN0fK3Gjg', // PC版Youtubeで生成した共有用のURL
'https://m.youtube.com/watch?v=bQqN0fK3Gjg', // Mobile版YoutubeのURL
'https://www.youtube.com/watch?app=desktop&v=bQqN0fK3Gjg', // Mobile版YoutubeのURLをPCで開いた時のURL
'https://twitter.com/vercel/status/1541814188619825154', // TwitterのURL
'https://mobile.twitter.com/vercel/status/1541814188619825154', // Mobile版のTwitterのURL
];
const youtubeParser = (url: string) => {
console.log(`Parsing ${url}`);
};
const twitterParser = (url: string) => {
console.log(`Parsing ${url}`);
};
const parserSwitcher = {
'youtube.com': youtubeParser,
'youtu.be': youtubeParser,
'twitter.com': twitterParser,
};
Urls.map((url) => {
const host = new URL(url).hostname;
const domain = psl.get(host);
domain && Object.hasOwn(parserSwitcher, domain)
? parserSwitcher[domain as keyof typeof parserSwitcher](url)
: console.log(`${url} is not supported`);
});
Discussion
以前にこのようなコードに関しての記事を書いたことがあります。
オブジェクトを連想配列として使うと痛い目にあいますよ
psl というものについては使ったことがないので検証できてないのですが、もしかしたらUrlsに意図せずtoStringとかの文字列が入ったとき、意図しない動きになるかもしれません。
(parserSwitcherに関数登録されてないからエラーになるのかな)
コード読むだけでは不具合があるのかないのかも見抜く事が難しいので
「コードの見通しやメンテナンス性が上がった」というのではなく、
逆にswitch文で書いているほうが、コードの見通しもメンテナンス性も高いように思います。
確かに
in
を使うと継承元のプロパティまで遡ってしまうので、おかしな挙動になってしまいます。Object.hasOwn()
を使って書き直しました。ご指摘いただきありがとうござます!!!