JavaScriptにおいて、switch文は使い勝手の良い便利な文法です。
しかしコード全体の機能性で考えてみると、うまくフィットしていないことがあります。
変更できないイミュータブルなものとすることができず、他の関数と組み合わせることもできず、また使用することにより副作用を起こすこともあります。
switch文ではbreakが用いられますが、これもあまり好ましくありません。
望ましいのは、イミュータブルでかつ副作用ができるだけ少ないものです。
ひとつの値を与え、取り出すことができる、機能的なものが必要なのです。
ここで、Reduxのウェブサイトに用いられているコードを見てみましょう。
function counter(state = 0, action) { | |
switch (action.type) { | |
case 'INCREMENT': | |
return state + 1 | |
case 'DECREMENT': | |
return state - 1 | |
default: | |
return state | |
} | |
} |
このコードを次のように書き換えることができます。
const counter (state = 0, action) => | |
action.type === 'INCREMENT' ? state + 1 : | |
action.type === 'DECREMENT' ? state -1 : | |
: state |
だいぶ良くなりましたが、action.type ===が何度も使用されているのが気に入りません。
そこでオブジェクトリテラルを試してみましょう。
関数内にオブジェクトリテラルを含め、さらにデフォルトケースにも対応させることができます。
function switchcase (cases, defaultCase, key) => { | |
if (key in cases) { | |
return cases[key] | |
} else { | |
return defaultCase | |
} | |
} |
もっと良くなりますよ、ifを三項演算子とES6を用いて書き換えてみましょう。
const switchcase = cases => defaultCase => key => | |
key in cases ? cases[key] : defaultCase |
きれいになりました!ではReducerに取りかかりましょう。
const counter (state = 0, action) => | |
switchcase({ | |
'INCREMENT': state + 1, | |
'DECREMENT': state -1 | |
})(state)(action.type) |
初期バージョンのswitchcaseには、switchcase関数に値が渡される前にオブジェクトリテラルが評価されてしまう問題があります。
つまり、state +1とstate -1がどちらも評価されてしまうということです。
これが非常に危険となる場合があります。
もしオブジェクトリテラル内の値が関数であれば、ケースに合致した場合のみ実行させることができるはずです。
そこでswitchcaseF(Fは関数、つまりFunctionから来ています)という関数を作ってみましょう。
これはswitchcaseをベースに、値を関数としたものです。
const switchcaseF = cases => defaultCase => key => | |
switchcase(cases)(defaultCase)(key)() |
これでReducerの値が関数に変更され、ケースに合致した場合にのみ評価されるようになります。
const counter (state = 0, action) => | |
switchcase({ | |
'INCREMENT': () => state + 1, | |
'DECREMENT': () => state -1 | |
})(() => state)(action.type) |
うーん、しかしできれば、これらの関数を必須のものとはしたくないですね。
次のようなものを追加してみましょう。
const executeIfFunction = f => | |
f instanceof Function ? f() : f | |
const switchcaseF = cases => defaultCase => key => | |
executeIfFunction(switchcase(cases)(defaultCase)(key)) |
するとReducerがこのようになります。デフォルトの値に気付いてくださいね。
const counter (state = 0, action) => | |
switchcase({ | |
'RESET': 0, | |
'INCREMENT': () => state + 1, | |
'DECREMENT': () => state -1 | |
})(state)(action.type) |
さらに、こんな風にすることもできますね。(RESETを追加してみました。)
const counter (state = 0, action) => | |
switchcase({ | |
'RESET': 0, | |
'INCREMENT': () => state + 1, | |
'DECREMENT': () => state -1 | |
})(state)(action.type) |
結論
switch文は私たちの求める機能的パラダイムに合致していませんでしたが、同じような特徴を持つ関数を作成することができます。
これを利用することでswitch文を分解し、よりコンパクトで再利用が可能なコードに変えられるのです。
次にswitch文を使用する際には、ぜひ思い出して活用してみてください。
※本稿は「Rethinking JavaScript: Eliminate the switch statement for better code」を翻訳・再編集したものです。