Kodokon kodokon.com

実運用パターン: ブランド型、satisfies、そして .d.ts

実際のコードベースを堅牢にするエキスパートのパターンを適用する: 疑似的な名前的型付け、拡大なしの検証、そしてアンビエント宣言。

11 分 · 3 問

このレッスンを Kodokon で開く

TypeScript の型付けは構造的です: 同じ形を持つ 2 つの型は互換であり、ユーザー ID の string は他のどんな string とも区別がつきません。ブランディングは、ベース型と、キーが unique symbol であるファントムプロパティとを交差させることで、名前的型付けを疑似的に再現します: 実際にそれを持つ値は存在しませんが、コンパイラは各ブランドを区別できるようになります。

TYPESCRIPT
declare const brand: unique symbol;

type Brand<T, Name extends string> =
  T & { readonly [brand]: Name };

type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;

function asUserId(raw: string): UserId {
  return raw as UserId;
}

declare function loadUser(id: UserId): void;

loadUser(asUserId("u_42")); // OK
// loadUser("u_42"); // error: bare string rejected
ランタイムコストゼロの、疑似的な名前的型付け。

satisfies 演算子(TypeScript 4.9)は、ある明確な隙間を埋めます。: T の注釈は変数の型を T拡大し、推論されたリテラルを失います。as T はチェックの一部を無効化します。satisfies T は式が T に適合することを、推論された型に手を加えることなく検証し、その型は可能な限り正確なまま保たれます。設定オブジェクトに理想的なツールです: 完全な検証と、損なわれない精度。

TYPESCRIPT
type RouteDef = { path: string; auth?: boolean };

const routes = {
  home: { path: "/" },
  admin: { path: "/admin", auth: true },
} satisfies Record<string, RouteDef>;

routes.admin.auth;
// literal type true: the inferred precision is
// preserved, and an unknown key would be rejected
satisfies: 拡大なしの厳格なチェック。

.d.ts ファイルはアンビエント宣言のみを含みます: それはランタイムに存在することになる値を記述しますが、JavaScript を一行も出力しません。モジュールからグローバルスコープを拡張するには declare global ブロックが必須です。そして仕様は、それが実際にモジュールであるファイルの中でのみそれを許可します。そのため、ファイル先頭の export {} というイディオムが用いられます。

TYPESCRIPT
export {};

declare global {
  interface Window {
    analytics: { track(event: string): void };
  }
}
env.d.ts: export {} がファイルをモジュールに変える。

型が公開されていない JavaScript の依存関係に対しては、declare moduleアンビエントモジュールを作成します: コンパイラは、そのパッケージのあらゆるインポートに対してあなたの宣言を使います。skipLibCheck のスコープに注意してください: 有効にすると、それはすべての .d.ts ファイル(あなたのものを含む)のエラーを無視します。ですから、通常の .ts ファイルに置いた型テストを通じて宣言を検証しましょう。

TYPESCRIPT
declare module "legacy-lib" {
  export interface InitOptions {
    debug?: boolean;
  }
  export function init(options?: InitOptions): void;
}
legacy-lib.d.ts: 型が公開されていないパッケージを型付けする。

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. satisfies T: T 注釈の違いは何ですか?
    • satisfies は式の型を T に拡大する
    • satisfies はより正確な推論された型を保ちつつ T への適合をチェックする
    • satisfies は as のようにチェックを無効化する
    • 違いはない: 2 つの形は等価である
  2. ランタイムにおいて、Brand<string, ...> で構築されたブランド値には何が含まれますか?
    • 文字列と、それに加えて symbol プロパティ
    • ただの文字列: ブランドはコンパイル時にしか存在しない
    • 元の文字列を包むオブジェクト
  3. declare global を使う .d.ts ファイルの先頭に、なぜ export {} を追加するのですか?
    • グローバル型を他のファイルにエクスポートするため
    • ファイルをモジュールに変えるため。declare global が要求する条件である
    • 自動的に strict モードを有効にするため
    • 純粋に装飾的なものである