🦄

ts-patternでTypeScriptにパターンマッチングを持ち込み、より型安全な世界へ

に公開
5

Discussion

dqndqn

通常のswitch文の書き方では、分岐処理の漏れを防ぐ方法がありません

複雑な型定義をせずとも、switch 文の default 節で never 型の値に代入したり never 型を引数にとる関数に渡したりすることで網羅チェックをするテクニックが知られていますね。

type Animal = "Cat" | "Dog" | "Penguin"; // "Penguin" を加えた

const say = (animal: Animal): string => {
  switch(animal) {
    case "Cat":
      return "Meow";
    case "Dog":
      return "Bow";
    // case "Penguin": が抜けている
    default:
      const _: never = animal;
      // Type 'string' is not assignable to type 'never' .
  }
};
リーダブル秋山リーダブル秋山

コメントありがとうございます、勉強になりました。本文の方も編集させていただきました。

自分の書いたような簡単なケースであれば、単にdefault文を書かない方法でも良さそうですね。

type Animal = "Cat" | "Dog" | "Penguin"; // "Penguin" を加えた

const say = (animal: Animal): string => { // ここで string 型を返り値にしているので、型エラーになる
  switch(animal) {
    case "Cat":
      return "Meow";
    case "Dog":
      return "Bow";
    // case "Penguin": が抜けている
  }
};

switch文単体で分岐漏れを防げないとか、Linterでdefault文を追加させない工夫が必要など、考慮する点はありますが、有効そうです。

jintzjintz

めっちゃ便利そうなライブラリですね。こういうライブラリを探してました。
1点だけ、記述ミスらしきものを見つけたので報告いたします。

2-2. プリミティブ型で分岐する

// (略)
test({ age: "32" }); // age is number: "32"

ここでの出力は age is string: "32"になるかと思います。

nap5nap5

zodでバリデーションをユースケースとしてneverthrowライブラリのResult型とts-patternを使いながらデモ作ってみました。

デモコードです。
https://codesandbox.io/p/sandbox/charming-rumple-d6obeh?file=%2Fsrc%2Findex.ts

import { safeParseUsersData, UserData } from "@/features/user/types/user";
import { Chance } from "chance";
import { getFalsyValue } from "@/utils";
import { validateAge } from "@/features/user/validations/age";
import { validateEmail } from "@/features/user/validations/email";
import { Result } from "neverthrow";
import { distinct, tidy } from "@tidyjs/tidy";
import { match } from "ts-pattern";

const validateUser = (data: UserData) => {
  return Result.combine([validateAge(data, 40), validateEmail(data)]);
};

const createUser = (data: UserData) => {
  validateUser(data).match(
    (v) => {
      const neatData = tidy(safeParseUsersData(v), distinct(["id"])).pop();
      console.log(neatData);
      // call create user service
    },
    // @ts-ignore
    (e) => {
      match(e.errorCode)
        .with("E01", () => {
          console.log(`[E01]${e.message}`);
          // call sentry service
        })
        .with("E02", () => {
          console.log(`[E02]${e.message}`);
          // call sentry service
        })
        .otherwise(() => {
          console.log(`[XXX]Unhandling errorCode.`);
          // call sentry service
        });
    }
  );
};

(() => {
  const seed = 1;
  const data: UserData = Chance(seed).bool()
    ? {
        id: 1,
        name: "Spike",
        age: 39,
        company: "Cowboy Bebop",
        email: "spike@cowboy.bebop",
      }
    : getFalsyValue();

  createUser(data);
})();

簡単ですが、以上です。