💭

TypeScriptでunknownを扱うの、つらくない?

に公開

みなさん、こんにちは!

オランダでも花粉症に悩まされてるフロントエンドエンジニアの @nyaomaru です!

APIレスポンス、フォーム入力、外部データ・・・

TypeScriptでは最終的に 「unknown」をどう扱うかが、実務のストレスになることが多いですよね・・・

mr unknown

そう、unknown宇宙のような吸引力を持ってます

でも、型を適切に扱いたいとき、ありますよね?

そんなときに使えるのが、型ガードを組み立てるライブラリ is-kit です!

is-kit logo

https://github.com/nyaomaru/is-kit

Zodのように「schemaを定義する」アプローチもありますが、

is-kit既存のコードの中で自然に型を絞り込む ことにフォーカスしています。

つまり、「バリデーションを書く」のではなく、普段のif文の延長で型を扱えるイメージです。

Zodが「境界(API / 入力)で使うもの」なら、
is-kitは「アプリケーションの中で使うもの」です。

例えば、「3文字以下の文字列」を何度もチェックするようなケースでは、
条件を1箇所にまとめて再利用できます:

~/utils/is.ts
import { define, isString } from "is-kit";

const isShortString = define<string>( // narrowing先の型を明示
  (value) => isString(value) && value.length <= 3,
);
~/xxx.ts
import { isShortString } from "~/utils/is";

declare const input: unknown;

// before → 毎回条件を書く必要がある
if (typeof input === "string" && input.length <= 3) {
  input.toUpperCase();
}

// after (is-kit) → guardを再利用できる
if (isShortString(input)) {
  input.toUpperCase();
}

is-kit hammer

このスタイルは、filter / map / if文など、既存のロジックにそのまま組み込めるのが特徴です。

🐾 is-kit はどう進化してきたか

時が経つのは早いもんで、is-kitv1.0 のリリースから半年がたちました。

is-kitv1.0 は、define / and / or / struct / arrayOf のような基本ガードを揃えた、軽量な type guard ライブラリとして出発しました。

そこから現在の v1.6 系までの軌跡を見ると、単に「型ガードを作れる」から、「unknown な入力を現場で扱いやすい」ライブラリへ向かってきているのが分かるかと思います。

今回は大きな変更点である 5 点について、振り返ってみようと思います。

ほんなら、一緒に見ていこな!

🪄 1. struct が「missing」と「undefined」を分けて書けるようになったで!

APIレスポンスでよくあるのが、

  • キーが存在しない
  • キーはあるが undefined

この違いをちゃんと扱いたいケース。

そこで、v1.5.0 で追加された optionalKey(...) は、ええ感じの改修やで!

これまで struct では、「キーが存在しない」のか、「キーはあるが値が undefined なのか」を表現しづらい場面があった。

optional(...) は値側の話で、キーそのものの省略とは意味がちゃう。

せやから、optionalKey(...) が入ったことで、その差を schema で素直に書けるようになってんな。

import { isString, optional, optionalKey, struct } from "is-kit";

const isUser = struct({
  id: isString,
  nickname: optionalKey(isString), // key は optional として扱える
  displayName: optionalKey(optional(isString)), // key も value も optional として扱える
});

この追加は地味に見えて、API レスポンスやフォーム入力のような「プロパティが来ないこと自体に意味がある」データではかなり効く。

v1.0struct は便利やったけど、v1.5 以降はより現実の payload に近い書き方ができるようになってるで~!

🔑 2. hasKey / hasKeys / narrowKeyTo で、オブジェクトの絞り込みがかなり自然になったで!

v1.1.13hasKeyv1.4.0hasKeys、その流れで narrowKeyTo も加わって、オブジェクトの一部キーを軸にした narrowing がめっちゃ書きやすくなってん。

import {
  hasKeys,
  narrowKeyTo,
  oneOfValues,
  struct,
  isString,
  isNumber,
} from "is-kit";

const isUser = struct({
  id: isString,
  age: isNumber,
  role: oneOfValues("admin", "guest", "trial"),
});

const hasRoleAndId = hasKeys("role", "id"); // 複数の key を持っているかどうかを判別できる
const byRole = narrowKeyTo(isUser, "role"); // key を起点にさらに型の絞り込みができる
const isGuest = byRole("guest");

この系統の API が便利なんは、struct を毎回細かく再定義せんでも、既存ガードの上に「このキーがある」「このキーがこの値」といった条件を自然に積めることやな。

特に discriminated union 的なオブジェクトや、イベント payload を扱うコードではかなり相性がええ。

v1.0 の時点でも object guard はあったけど、v1.4 で「キーを起点にした使い勝手」が一段上がってるで~!

🧪 3. assert が入って、guard を fail-fast にも使えるようになったで!

v1.2.0assert 追加は、個人的な推しポイントや。

safeParse のような結果オブジェクト型も便利やけど、実際のアプリでは「ここで落としていい」「ここを通るなら型を確定させたい」という場所も多い。assert はそこにぴったりはまる。

import { assert, isString } from "is-kit";

declare const input: unknown;

assert(isString, input, "input must be a string");
input.toUpperCase();

この API のええとこは、is-kit がもともと持っていた guard-first な設計を崩さずに、実行時の失敗戦略だけを足していること。

Zod / Valibot のような大きな validation framework に寄せるんやなくて、**「既存の guard をそのまま assert にも使える」**というのが、この改修のええとこやと思ってる。

✨ 4. setOf と mapOf で、コレクション対応が一段広がったで!

v1.6.0 では setOfmapOf を追加したで。

import { mapOf, setOf, isString, isNumber } from "is-kit";

const isTags = setOf(isString);
const isScores = mapOf(isString, isNumber);

v1.0 でも arrayOftupleOfrecordOf はあってんけど、実務で扱う collection は配列だけやない。

Set や Map を型安全に扱いたいケースは、キャッシュ、集計、タグ集合、辞書構造などでザラにある。

ここが揃ったことで、is-kit は「小さな guard を組み立てる道具箱」から、「日常的な JavaScript のデータ構造を一通りカバーする guard kit」に近づいたんちゃうかな。

🥏 5. 数値の細かい edge case を直接扱えるようになったで!

v1.1.7v1.1.8 では数値ガードがかなり増えた。

isIntegerisSafeIntegerisPositiveisNegativeisNaNisInfiniteNumberisZero などやな。

これが便利なのは、「number ではあるが、欲しい number ではない」を guard の段階で表現できることや。

たとえば NaN、Infinity、-0 は JavaScript では地味に厄介やねんけど、そこを専用 API で拾えるようになった。

v1.0 でも isNumber は提供しててんけど、v1.1.8 以降は「数値の質」を guard で表現できるようになったのがデカいな。

🌟 ほかにもいろいろ更新してるで!

この記事では細かいこと説明せえへんけど、他にもいろいろ便利なもん追加しとるから、気になる人は document 眺めてみてな!

https://is-kit-docs.vercel.app/en

🎯 まとめ

v1.0is-kit は、軽くて composable な type guard ライブラリやった。

そこから v1.6 系までで特に良くなったのは、unknown な値を実務で扱うときの具体的な手触りや。

  • optionalKey で object schema が現実寄りになった
  • hasKey 系でキー起点の narrowing がしやすくなった
  • assert で fail-fast な使い方ができるようになった
  • setOf / mapOf で collection 対応が広がった
  • 数値ガード群で JavaScript の edge case に直接向き合えるようになった

つまり、v1.0 からの is-kit は「型ガードを作るための小さな道具」だけやなく、 「アプリケーションコードの中で unknown を扱いやすくする実践的な道具箱」 へ進化してきてる。

ほんで、これからも is-kit「型安全なコードを気持ちよく書ける」 ように、少しずつ育てていこうと思ってるで。

もし使ってみて気になる点や、「こういう guard があると嬉しい」というアイデアがあれば、ぜひ気軽に教えてな!

ほな、また次の記事で!

https://github.com/nyaomaru/is-kit

Discussion