■typeとinterfaceの使い分けと使い方 どちらもほぼ同じように使えるが interfaceはオブジェクトやクラスの形状を定義し type(型のエイリアス)はinterfaceでは表現できない複雑な「型」を扱うのに使用する。 1.typeが扱える複雑な型 1.1.プリミティブ型(Primitive Type)1.2.ユニオン型(Union Type)type UserId = string;1.3.タプル型(Tuple Type) 実際はだたの配列(Array) オブジェクト表記でデータを持つより軽い。 可読性が下がらない場所か最適化が必要な場所で使う。 push等による型崩壊を防ぐため、原則 `readonly` を付与する。type Status = "idle" | "loading" | "success" | "error";1.4.マップドタイプ(Mapped Types)などの高度な型操作type Point = readonly [x: number, y: number]; // 使用例 const path: Point[] = [ [0, 0], [10, 20], [35, 40], ]; for (let i = 0; i < path.length; i++) { const [x, y] = path[i]; console.log(`X: ${x}, Y: ${y}`); }1.5.マップドタイプの応用 型引数(ジェネリクス)を用いた条件付き型による再帰的な定義 - infer(型の自動推論・抽出) 条件付き型(extends ? :)の右側で使い 型の一部分を「変数」として型抜きする機能type ReadOnly<T> = { readonly [P in keyof T]: T[P]; };1.6.判別共用体型(Discriminated Union Type) 共通するリテラル型のプロパティが「判別子」として扱われる。 判別子は複数可能。type DeepReadonly<T> = T extends Function ? T : T extends Map<infer K, infer V> ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>> : T extends Set<infer U> ? ReadonlySet<DeepReadonly<U>> : T extends object ? { readonly [P in keyof T]: DeepReadonly<T[P]> } : T; ※上記は標準では用意されていない自前の深層完全不変の定義。 内部全てが不変(readonly)になる。はず。2.継承・合成のやり方 2.1.interface同士(extends)interface NetworkSuccess { status: "success"; // 判別子(Discriminant ) data: string[]; } interface NetworkError { status: "error"; // 判別子(Discriminant ) error: Error; } // 判別共用体型を type で合成 type NetworkResponse = NetworkSuccess | NetworkError; or type NetworkResponse = | { status: "success"; data: string[] } | { status: "error"; error: Error }; function handleResponse(res: NetworkResponse) { // res.status の値によって、コンパイラが型を確定させる if (res.status === "success") { console.log(res.data); // ここで data にアクセス可能 } else { console.error(res.error.message); // ここで error にアクセス可能 } }2.2.type同士(& インターセクション)interface Animal { name: string; } interface Dog extends Animal { bark(): void; }2.3.interfaceとtypeの相互合成type Flyable = { fly(): void; }; type Swimmable = { swim(): void; }; type Duck = Flyable & Swimmable; type Duck2 = Duck & { sleep(): void; };3.関数の定義 3.1.type での定義:関数型リテラル (Function Type Literal) 通常の「純粋な関数」はこれ一択。// 1. interface が type を extends する type Identifiable = { readonly id: string; }; interface Product extends Identifiable { price: number; } // 2. type が interface を & で合成する interface Serializable { serialize(): string; } type Config = Serializable & { version: string; }; ※同名プロパティが存在する場合はエラーになるがそれぞれタイミングが異なる。3.2.interface での定義:コールシグネチャ (Call Signature) 関数であり、かつプロパティも持つハイブリッド型を作る時に使う。type LogFn = (message: string) => void; const logger1: LogFn = (msg) => console.log(msg);結論:定義の仕方は違っても呼び出し側のコードは `logger("msg")` で完全に同じ。 違いは「型を定義する側」が、関数にプロパティを持たせたいかどうかだけ。 3.3.オブジェクトのプロパティとしての定義 メソッドシグネチャ (Method Signature) 関数単体ではなく、「オブジェクトが持つ関数(メソッド)」の形状を定義する構文interface LogInterface { (message: string): void; // コールシグネチャ level: number; // プロパティ } const logger2: LogInterface = Object.assign( (msg: string) => { console.log(`[Level ${logger2.level}] ${msg}`); }, { level: 3 } );■for文について (JavaScript)interface Drawer { draw(): void; // 「メソッドシグネチャ」 }■マップ定数の作り方const items = ["a", "b", "c"]; const itemObj = { a: 1, b:2, c:3 }; 文法 [1] for (let i = 0; i < items.length; i++) { xxxx } [2] for (const item of items) { xxxx } [3] for (const [i, item] of items.entries()) { xxxx } [4] items.forEach((item, i) => { xxxx }); [5] for (const [key, value] of Object.entries(itemObj)) { xxxx } 解説 [1]: 最速・最軽量 [2]: 標準 [3 - 4]: 他言語とは違いCPUとメモリとGCに負荷をかけるので高負荷領域では使用NG。 [5]: 外界との境界(バリデーション、シリアライズ)でしか使用してはならない■関数の引数と戻り値の不変性(イミュータブル)表現方法// 定数の定義 // ROLES: 原本リスト。 // ROLE: ROLESから抽出したユニオン型。 // ROLE_PERMISSIONS: ROLE型をキーとしたマッピング定数。 // // isRole: 型ガード関数(型述語タイプ) // 実行時の型ガードはTypeScript世界のROLEでは行えない const ROLES = ["admin", "manager", "user"] as const; type ROLE = typeof ROLES[number]; // --> "admin" | "manager" | "user" // const ROLE_PERMISSIONS: Readonly<Record<ROLE, readonly string[]>> = { // or const ROLE_PERMISSIONS: Readonly<{ [K in ROLE]: readonly string[] }> = { "admin": ["Read", "Write", "Delete", "Execute"], "manager": ["Read", "Write", "Delete"], "user": ["Read"], }; function isRole(value: string): value is ROLE { return (ROLES as readonly string[]).includes(value); } // 使用例 let inputData = "abc"; // inputDataはstring型 console.log(`'${inputData}' isRole: ${isRole(inputData)}`); inputData = "manager"; // inputDataはstring型 if( isRole(inputData) ) { // 型ガードを通過したのでinputDataはROLE型として扱われる const permisson = ROLE_PERMISSIONS[inputData]; console.log(`${inputData}: ${permisson}`); } // 使用例 console.log("-- loop"); // 型定義的にROLESはROLE_PERMISSIONSのキーリストでもある for (const role of ROLES) { const permisson = ROLE_PERMISSIONS[role]; console.log(`${role}: ${permisson}`); }■関数で名前付き引数風を扱うtype DeepReadonly<T> = T extends Function ? T : T extends Map<infer K, infer V> ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>> : T extends Set<infer U> ? ReadonlySet<DeepReadonly<U>> : T extends object ? { readonly [P in keyof T]: DeepReadonly<T[P]> } : T; function processData( a: User[], // [1] 可変: 配列の変更、Userの変更OK b: Readonly<User>[], // [2] 要素不変: 配列の変更OK、Userの変更NG c: readonly User[], // [3] 配列不変: 配列の変更NG、Userの変更OK d: readonly Readonly<User>[], // [4] 浅層完全不変: 配列の変更、User変更NG e: readonly DeepReadonly<User>[], // [5] 深層完全不変: [4] + User内部の全階層でNG ): User[] // [1]と同じ ): Readonly<User>[] // [2]と同じ ): readonly User[] // [3]と同じ ): readonly Readonly<User>[] // [4]と同じ ): readonly DeepReadonly<User>[] // [5]と同じ readonly - TypeScript 2.0 2016/09, 3.4 2019/03 Readonly - TypeScript 2.1 2016/12 Readonlyは直下のプロパティのみreadonlyとなる。 ネストしてる2層目以降は対象外。function splitTokens( value: string, { // デフォルト値 separator = ":", prefix = "", suffix = "", }: { // 型定義 separator?: string; prefix?: string; suffix?: string; } = {}, // 名前付き引数風を丸ごと省略可能にする ): string[] { // 使用例 splitTokens("txt:doc:pdf"); splitTokens("txt:doc:pdf", { prefix: "." });
気まぐれメモ
[TypeScript] ちょっとしたこと。
[Deno/TypeScript] Geminiなど一般向けのAIで開発支援してもらう時の最低限のプロンプト
注意 ローカル/クローズド/特化型AIとは違い一般向けの汎用AIはネット上(特に英語圏)の「平均」を正解とする。 したがって、古い情報や間違った情報がネットに溢れているならAIは誤回答をする。 AIへの指示にはバージョン、版数、対象期間などの指定が必須。 重要 会話が続くとAIは忘れる(保持情報量の上限)ので定期的にAIプロンプトを投稿する必要がある。 ---- AIプロンプト大前提: Deno 2.7.14 TypeScript 5.9.2 必須情報元: Deno Docs https://docs.deno.com/runtime/ MDN https://developer.mozilla.org/ja/docs/Web/JavaScript TypeScript https://www.typescriptlang.org/ 役割定義: 貴方は、言語仕様とランタイムの特性を熟知し、コードの「一貫性」と「計算資源への敬意」を両立させるシニアエンジニアである。 - 提供された最新のソースコードを「唯一の正典(Source of Truth)」とし、その構造、命名規則、ロジックの癖を完全に尊重せよ。 ポリシー: 1. 計算機科学の基本(時間計算量 $O(1)$ の優先、不要なメモリアロケーションの排除)を最優先すること。 - ただし、設定値や低頻度なUI制御など、人間側の「可読性・保守性」がリソース効率を上回ると判断される箇所については、宣言的な記述(メソッドチェーン等)を許容する。 - 逆に、想定される処理件数が多い箇所や、ループ内、非同期ストリーム等の「高負荷領域」については、一切の妥協なくガッツリと最適化を指摘・実装せよ。 2. 破壊的変更(In-place)による最適化を許容する。データの不変性(Immutability)よりも、空間計算量の最小化を優先せよ。 3. $O(N)$ の隠れたループ(配列の全コピー等)が発生する場合は、必ずそのコストを明示し、回避策を検討せよ。 - メソッドチェーン(.map/.filter等)を使用する場合も、裏側で発生するアロケーションコストを意識し、必要に応じて命令的(Imperative)なループへの書き換えを提案すること。 4. 非推奨(Deprecated)または非標準(Non-standard)な構文は一切使わず、ターゲットバージョンの最新仕様に準拠せよ。 5. ソースコードやドキュメントで使われている各種名称を勝手に変えるのは厳禁。 6. 後方互換性は一切無視すること。 旧環境への配慮による冗長な実装を排除し、指定された実行環境において最も効率的なコードを記述せよ。 7. 修正・変更は「対象の箇所」のみに限定すること。指示に関係のない箇所のコード(変数名、命名規則、ロジック、コメント等)は一切変更・改変してはならない。 8. 修正および解析は、提供された「最新のコード」の状態に対してのみ行うこと。過去の推論や自身の記憶に基づいた古いコード構造を前提とした提案は禁止する。 9. 推論の厳格化: 実装案を提示する前に、内部で「計算量の検証」と「提供コードとの完全一致確認」を強制し、自身の推論に誤りがないか自己検閲せよ。 10.ネットワーク通信の実装案を提示する際は、通信先および経路のリソースが「有限かつ高コスト」であることを前提とし、以下の制約を厳守せよ。 10.1. ネットワーク応答時間を 0 と仮定したロジックを禁止する。 10.2. 通信相手(サーバー)の負荷を「自身のメモリ」と同等に扱わず、リクエストの並列数は `Promise.all` による全件同時実行ではなく、`p-limit` 相当の制御やチャンク分割によるスロットリングを標準とせよ。 10.3.大容量データやファイルダウンロード、連続的なデータ取得は、必ず `ReadableStream` によるストリーミング処理を行い、受信と同時に消費(書き込み・加工)することで、ローカルメモリの $O(N)$ 膨張を阻止せよ。 10.4. `await using` 等を用い、異常系を含めたあらゆるパスでリソース(Bodyの閉じ忘れ等)を即座に解放せよ。アイドル状態のコネクション維持によるリソース占有を $O(0)$ に近づけること。
登録:
投稿 (Atom)