もう長いことコードを書いていると、「ああ、これまたif文か」と思う瞬間、増えてこない?
それ、わかるよ。若かったあの頃はif文で全てが解決できると思ってたんだよね。あの頃はね。
if文は、ロックみたいなもんさ。無骨で力強く、ガシガシとパワーコードを弾いてる感じでパワーコードを書いてた。
だけど、年を重ねてくると、if文もだんだん胃もたれしてきて、「これ、もう少しいい感じに書けないかな?」って思うことが増えてくるんだよ。
今日はそんな30代の疲れ目エンジニアのための話。
三項演算子、君は僕の愛だ
まず最初に言わせてほしい。僕は三項演算子が好きだ。いや、正確には愛している。?
と:
が生み出すあの短さと簡潔さ、条件分岐が一行で完結するところが最高なんだ。これだけでコードがぐっとスマートになる。
例えば、こんなコード(SWRのサイトから引用):
import useSWR from 'swr'
function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
if (isLoading) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
は三項演算子でこう書ける。
import useSWR from 'swr'
function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher)
return error ? (
<div>failed to load</div>
) : isLoading ? (
<div>loading...</div>
) : (
<div>hello {data.name}!</div>
)
}
見て、このスッキリ感。if文ではreturn
を3回も書かないといけないところを、たった1つのreturn
で済ませられる。これが三項演算子の魅力さ。三項演算子は文
ではなく式
だからね。
でもね、三項演算子にも限界があるんだ。
ネストの深みで見失うもの
三項演算子、条件が2つ、3つなら全然いいんだ。でも、条件が増えてくると、どうなる?
そう、ネストが深くなって、コードが追いかけにくくなるんだよね。
「ちょっと待てよ、何が起きてるんだ?」って脳みそインタプリタがバグる瞬間が来る。コードはシンプルなはずなのに、頭が追いつかない。これはもう、三項演算子の限界さ。
if文、君には感謝してるけど、もう卒業しよう
if文。プログラムを書き始めた頃は頼れる相棒だった。「if文とfor文があれば全ては解決できる」って思ってたよね。懐かしいあの頃だよ。
でも、条件が増えるたびに、if文がどんどん重くなっていく。
function UserAccess({ role }) {
if (role === 'admin') {
return <AdminDashboard />;
} else if (role === 'editor') {
return <EditorPanel />;
} else if (role === 'viewer') {
return <ViewerContent />;
} else {
return <GuestAccess />;
}
}
これ、最初はわかりやすいよね。でも、条件が増えていくと、if文が積み重なっていく。「これ、if文が無限に続くんじゃないか?」って思った瞬間。それはもう卒業の時が来たんだ。
Match
と When
:if文駆逐計画、始動
そこで登場するのが、Match
とWhen
。if文から解放され、コードをもっとシンプルで読みやすく、整理されたものにできるんだ。
ReactのMatch
とWhen
の実装
まず、Match
とWhen
を実装しよう。これで条件分岐をReactの中で扱いやすくするんだ。
const Match = ({ children }) =>
React.Children.toArray(children).find(
({ props: { exp, otherwise } }) => exp || otherwise
) || <></>;
const When = ({ children, exp = false, otherwise = false }) =>
exp === 0 ? <></> : (exp || otherwise) && children;
使用例:
例えば、ロールごとに異なるコンポーネントを表示するパターンを考えてみよう。
function UserAccess({ role }) {
return (
<Match>
<When exp={role === 'admin'}>
<AdminDashboard />
</When>
<When exp={role === 'editor'}>
<EditorPanel />
</When>
<When exp={role === 'viewer'}>
<ViewerContent />
</When>
<When otherwise>
<GuestAccess />
</When>
</Match>
);
}
このパターンなら、if文を並べる必要がなく、条件ごとにスッキリ整理できる。条件が増えてもネストが深くならないし、読みやすさが保たれる。
SWRとMatch
とWhen
の組み合わせ
例えば、SWR
のデータ取得の状態に応じて異なる表示をする場合にも、Match
とWhen
はとても便利。
import useSWR from 'swr'
function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher)
return (
<Match>
<When exp={error}>
<div>failed to load</div>
</When>
<When exp={isLoading}>
<div>loading...</div>
</When>
<When exp={data}>
<div>hello {data.name}!</div>
</When>
<When otherwise>
<div>no data available</div>
</When>
</Match>
)
}
SWRの結果に応じて、必要なUIコンポーネントを簡単に切り替えることができる。複雑なif文や三項演算子を使うより、こちらの方が圧倒的にシンプル。
Reactだけじゃない:JavaScript(TypeScript)でのmatch
とwhen
ReactでMatch
とWhen
を使ってif文を駆逐したら、次はもっと広くJavaScriptの世界でも同じことをしたくならない?
実は、match
とwhen
という関数を使えば、JavaScriptでもif文をもっとシンプルに整理できるんだ。
match
と when
の実装
TypeScript版:
type When<T> = [boolean, () => T];
const is = (exp: boolean) =>
<T>(t: () => T = () => true as T) =>
(f: () => T = () => false as T) =>
[f, t][Number(exp)]();
const match = <T>(l: When<T>[]) => (ow: () => T = () => undefined as unknown as T): T => {
const tl = l.flatMap(([exp, fn]) => is(exp)(() => [fn])(() => []));
return is(tl.length === 0)(ow)(tl[0]);
};
const when = <T>([exp, fn]: When<T>) => exp && fn();
JavaScript版:
const is = (exp) => (t = () => true) => (f = () => false) => [f, t][Number(exp)]();
const match = (l) => (ow = () => undefined) => {
const tl = l.flatMap(([exp, fn]) => is(exp)(() => [fn])(() => []));
return is(tl.length === 0)(ow)(tl[0]);
};
const when = ([exp, fn]) => exp && fn();
この実装では、内部的に is
関数を使って、シンプルな条件分岐を表現している。if文や三項演算子をもっと整理された形で処理できるようにしているんだ。
シンプルに使う例
この match
と when
を使って、条件分岐をもっと読みやすく、スッキリさせることができる。
match
の例:
例えば、スコアに応じて評価を返すような条件分岐があるとする。この場合、match
を使うことで、if文や三項演算子を使わずに、条件に応じた結果を返せる。
const score = 85;
const result = match([
[score >= 90, () => "A"],
[score >= 70, () => "B"],
[score >= 50, () => "C"],
[score < 50, () => "F"]
])(() => "Invalid score");
console.log(result); // "B"
match
関数は、条件と処理をペアにしてリスト化することで、コードの可読性を保ちながら柔軟に条件を追加できる。
when
の例:
もっとシンプルな1つの条件を評価したいときは、when
を使って結果を返せる。
const score = 95;
const result = when([score >= 90, () => "A"])
console.log(result); // "A"
このように、when
は1つの条件に対して利用可能
実は思いつきで作った制御構造
ここまで話しておいてなんだけど、実はこの Match
とWhen
、そして match
とwhen
、完全に思いつきで作ったものなんだ。まだこれを使って大規模なプロジェクトを組んだわけじゃない。でも、試してみたら案外いけるんじゃないかなって思ったんだよ。
もしこの記事を読んで「これ、試してみようかな?」って思ってくれたら、僕たちは一緒にif文駆逐計画の先駆者になれるかもしれない。そう思うと、少しワクワクしない?
より複雑なパターンマッチングへの道
ここまで紹介した match
と when
は、シンプルな条件分岐やパターンマッチングに適しているけど、もっと複雑なパターンやデータ構造に対してはどうするか?
その場合には、さらに強力なパターンマッチングライブラリとして ts-pattern がある。
これはTypeScript向けに設計されていて、型安全かつ強力なパターンマッチを実現できるライブラリだ。もし複雑な分岐に遭遇したら、ts-patternを使うことで、さらに柔軟に対応できるはず。
まとめ:if文はロック。でも、少し肩の力を抜いてみよう
if文はロックだよ。無骨で力強く、コードを書く楽しさを教えてくれた。でも、今はもっと楽に、気楽にコードを書いていきたい。
Match
とWhen
、そして match
とwhen
を使えば、if文を駆逐して、もっとシンプルなコードを書ける。もしif文に疲れてきたなら、この方法を試してみてほしい。次のステップに進んでいこう。