🎯
ts-patternを使うのをやめた話
最近、「TypeScriptプロジェクトにスキーマ駆動開発を持ち込み、より型安全な世界へ(パターンマッチングを持ち込む)」という記事を読んで、試しに利用してみました。しかし、最終的には導入を見送る決断をしました。この記事では、その理由と代わりの対策について説明します。
導入を見送った背景
number
型のenum
において網羅性チェックが不十分
issueでも取り上げられていますが、number
型のenum
は任意のnumber
型を代入できるという型安全性に関するtypescriptの仕様に由来して、number
型のenum
を分岐keyに利用する場合に網羅性チェックが上手くいきません。
例として、以下のようなnumber
型のenum
がある場合を考えます。
enum NumberEnum {
ZERO = 0,
ONE = 1,
}
通常のts-patternの使用方法で書くと、以下のようになりますが、exhaustive()
を使った場合に型エラーが発生します。
const handleNumberEnum = (value: NumberEnum) => {
return match(value)
.with(NumberEnum.ZERO, () => 'zero')
.with(NumberEnum.ONE, () => 'one')
.exhaustive();
// Type 'NonExhaustiveError<NumberEnum>' has no call signatures.
};
型エラーを回避するためには、以下のように書く必要があります。
const handleNumberEnum = (value: NumberEnum) => {
return match(value)
.with(0, () => 'zero') // NumberEnum.ZERO
.with(1, () => 'one') // NumberEnum.ONE
.exhaustive();
};
代替手段
TypeScriptの公式ドキュメントで紹介されているnever型を用いた網羅性チェックの手法を採用しました。
具体的には、以下のようにコードを書きました。
const ensureAllCasesHandled = (key: never): never => key;
const handleNumberEnum = (value: NumberEnum) => {
switch (value) {
case NumberEnum.ZERO:
return 'zero';
case NumberEnum.ONE:
return 'one';
default:
ensureAllCasesHandled(value);
}
};
この方法で、網羅性を確保しつつ、より型安全なコードを実現できました。
Discussion