変化に強いallow list/deny listをTypeScriptで書く
allow list/deny listを定義すると、後から変更を加えたときlistを修正し忘れることがある。
例えば「果物だけ」を抽出したallow listを定義する。
product.ts
const products = [
"apple",
"banana",
"orange",
"tomato",
"eggPlant",
"pumpkin",
] as const;
type Product = typeof products[number];
const fruits = ["apple", "banana", "orange"];
const filterFruits = (products: Product[]) =>
products.filter((product) => fruits.includes(product));
console.log(filterFruits([...products]));
後からproductsに果物を追加しても、fruits配列を更新し忘れないようにTypeScriptで対策する。
網羅チェックしたい。
Excludeを使う方法
果物の定義を配列で管理できる。
スマートだけど難しい感じ。
なるべく型定義で頑張る。
product.ts
const products = [
"apple",
"banana",
"orange",
"tomato",
"eggPlant",
"pumpkin",
] as const;
type Product = typeof products[number];
const fruits = ["apple", "banana", "orange"] as const;
type Fruit = typeof fruits[number];
const exhaustiveCheck: Fruit = fruits[0] as Exclude<
Product,
"tomato" | "eggPlant" | "pumpkin"
>;
const filterFruits = (products: Product[]) =>
products.filter((product) =>
(fruits as unknown as string[]).includes(product)
);
console.log(filterFruits([...products]));
switch文を使う方法
果物の定義をswitch文で管理する。
イケてないコードのように見えて、実はシンプルな感じ。
型定義を頑張らず、JavaScript側で妥協してる。
型の都合で、filter()ではなくflatMap()を使ったりしてる。
product.ts
const products = [
"apple",
"banana",
"orange",
"tomato",
"eggPlant",
"pumpkin",
] as const;
type Product = typeof products[number];
const filterFruits = (products: Product[]) =>
products.flatMap((product) => {
switch (product) {
case "apple":
case "banana":
case "orange":
return [product];
case "tomato":
case "eggPlant":
case "pumpkin":
return [];
default:
const exhaustiveCheck: never = product;
throw new Error("unreachable");
}
});
console.log(filterFruits([...products]));
switch文だいすき。
flatMap()も好き。