実際のコードベースを堅牢にするエキスパートのパターンを適用する: 疑似的な名前的型付け、拡大なしの検証、そしてアンビエント宣言。
このレッスンを Kodokon で開くTypeScript の型付けは構造的です: 同じ形を持つ 2 つの型は互換であり、ユーザー ID の string は他のどんな string とも区別がつきません。ブランディングは、ベース型と、キーが unique symbol であるファントムプロパティとを交差させることで、名前的型付けを疑似的に再現します: 実際にそれを持つ値は存在しませんが、コンパイラは各ブランドを区別できるようになります。
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 rejectedsatisfies 演算子(TypeScript 4.9)は、ある明確な隙間を埋めます。: T の注釈は変数の型を T に拡大し、推論されたリテラルを失います。as T はチェックの一部を無効化します。satisfies T は式が T に適合することを、推論された型に手を加えることなく検証し、その型は可能な限り正確なまま保たれます。設定オブジェクトに理想的なツールです: 完全な検証と、損なわれない精度。
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.d.ts ファイルはアンビエント宣言のみを含みます: それはランタイムに存在することになる値を記述しますが、JavaScript を一行も出力しません。モジュールからグローバルスコープを拡張するには declare global ブロックが必須です。そして仕様は、それが実際にモジュールであるファイルの中でのみそれを許可します。そのため、ファイル先頭の export {} というイディオムが用いられます。
export {};
declare global {
interface Window {
analytics: { track(event: string): void };
}
}型が公開されていない JavaScript の依存関係に対しては、declare module がアンビエントモジュールを作成します: コンパイラは、そのパッケージのあらゆるインポートに対してあなたの宣言を使います。skipLibCheck のスコープに注意してください: 有効にすると、それはすべての .d.ts ファイル(あなたのものを含む)のエラーを無視します。ですから、通常の .ts ファイルに置いた型テストを通じて宣言を検証しましょう。
declare module "legacy-lib" {
export interface InitOptions {
debug?: boolean;
}
export function init(options?: InitOptions): void;
}satisfies T と : T 注釈の違いは何ですか?Brand<string, ...> で構築されたブランド値には何が含まれますか?declare global を使う .d.ts ファイルの先頭に、なぜ export {} を追加するのですか?