在Lingo.dev ,我編寫了大量 TypeScript 程式碼。我當然不算是高手,但我確實嘗試過一些超越基本類型的功能。
這篇文章描述了許多功能(以及您可能想要使用它們的時間),以幫助您在絕對基礎知識之外擴展您的知識。
as const斷言預設情況下,陣列和物件是可變的,而 TypeScript 會將字面值擴展為其通用類型。這使得 TypeScript 更難幫助您捕捉錯誤並提供準確的自動完成功能。
const colors = ["red", "green", "blue"];
// Type: string[] - could be any strings
colors.push("yellow"); // Allowed, might not be what you want
type Color = (typeof colors)[number]; // string (too general!)
用作as const使所有內容只讀並保留文字類型,或對特定陣列使用readonly 。
const colors = ["red", "green", "blue"] as const;
// Type: readonly ["red", "green", "blue"]
colors.push("yellow"); // ✗ Error: can't modify readonly array
type Color = (typeof colors)[number]; // "red" | "green" | "blue" ✓
// Or for function parameters:
function display(items: readonly string[]) {
items.push("x"); // ✗ Error: can't modify
items.forEach(console.log); // ✓ OK: reading is fine
}
不應改變的配置或常數資料
防止意外突變
保留文字類型以實現更好的類型推斷
不應修改的函數參數
了解更多: TypeScript 文件:ReadonlyArray
keyof typeof用於 object-as-const 枚舉TypeScript 枚舉有一些特殊之處,會產生 JavaScript 程式碼。有時你只想在物件中定義常數,並從中派生類型。
組合as const (鎖定文字值)、 typeof (取得物件的類型)和keyof (取得鍵或值的並集)。
// Define your constants as a plain object
const STATUS = {
PENDING: "pending",
APPROVED: "approved",
REJECTED: "rejected",
} as const; // Lock in the literal values
// Get a union of the values
type Status = (typeof STATUS)[keyof typeof STATUS];
// "pending" | "approved" | "rejected"
function setStatus(status: Status) {
// TypeScript validates and autocompletes!
}
setStatus(STATUS.APPROVED); // ✓
setStatus("pending"); // ✓
setStatus("invalid"); // ✗ Error
具有更好 JavaScript 輸出的枚舉替代方案
使用派生類型建立基於 const 的配置
當你需要執行時值和編譯時類型時
了解更多: TypeScript 文件:typeof 類型
像[number, number, boolean]這樣的元組可以工作,但每個位置的含義並不明顯。是[width, height, visible]還是[x, y, enabled] ?
為元組位置賦予有意義的名稱,這些名稱會顯示在編輯器的自動完成和錯誤訊息中。
// Before: unclear what each number means
type Range = [number, number, boolean?];
// After: self-documenting
type Range = [start: number, end: number, inclusive?: boolean];
function createRange([start, end, inclusive = false]: Range) {
// Your editor will show you the parameter names!
return { start, end, inclusive };
}
createRange([1, 10, true]); // Clear what each argument means
基於元組的函數參數
傳回具有多個相關資料的值
任何位置意義不明顯的元組
了解更多: TypeScript 文件:元組類型
您有一個複雜的類型,並且只想引用一個屬性的類型,或者提取陣列中的內容,而無需重複。
使用括號表示法( Type["property"] )存取屬性類型,使用[number]取得陣列元素類型。
type User = {
id: number;
profile: {
name: string;
emails: string[];
};
};
// Access nested property types
type ProfileType = User["profile"]; // { name: string; emails: string[] }
type NameType = User["profile"]["name"]; // string
// Extract array element type
type Email = User["profile"]["emails"][number]; // string
從現有型別衍生型別(DRY 原則)
提取陣列/元組元素類型
使用嵌套結構而無需重新定義類型
了解更多: TypeScript 文件:索引存取類型
arg is T )您編寫了一個函數來檢查某物是否屬於某種類型,但 TypeScript 並不理解該檢查實際上會縮小類型。
function isPerson(x: unknown) {
return typeof x === "object" && x !== null && "name" in x;
}
function greet(x: unknown) {
if (isPerson(x)) {
x.name; // ✗ Error: TypeScript still thinks x is 'unknown'
}
}
使用類型謂詞( arg is Type )告訴 TypeScript 你的函數執行類型檢查。
type Person = { name: string; age: number };
function isPerson(x: unknown): x is Person {
return (
typeof x === "object" &&
x !== null &&
"name" in x &&
typeof (x as any).name === "string"
);
}
function greet(x: unknown) {
if (isPerson(x)) {
console.log(x.name); // ✓ TypeScript knows x is Person here!
}
}
驗證來自 API 或使用者輸入的資料
類型安全驗證函數
區分聯合中的類型
了解更多: TypeScript 文件:類型謂詞
never你有一個聯合類型(例如不同的形狀或狀態)和一個 switch 語句。後來,有人向聯合中加入了一個新的變數,但忘記在 switch 中處理它了。沒有拋出任何錯誤——它只是默默地不起作用。
新增一個default情況,將值賦給never類型。如果所有情況都已處理,則無法存取預設情況。如果缺少一個情況,TypeScript 會報錯,因為該值無法賦給never類型。
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; size: number };
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.size ** 2;
default:
// If all cases are handled, this is unreachable
const _exhaustive: never = shape;
throw new Error(`Unhandled shape: ${_exhaustive}`);
}
}
// Later, someone adds triangle:
// type Shape = ... | { kind: "triangle"; base: number; height: number };
// ✓ TypeScript error in default case: triangle is not assignable to never!
針對可區分聯合的 Switch 語句
確保處理所有聯合變體
當類型隨時間演變時捕獲錯誤
了解更多: TypeScript 文件:詳盡性檢查
import type / export type )有時你會從其他模組導入類型,但這些導入的內容即使只是用於類型檢查,也會顯示在編譯後的 JavaScript 檔案中。這可能會導致循環依賴或包膨脹。
使用import type告訴 TypeScript:“這只是為了類型檢查,將其從 JavaScript 中完全刪除。”
// Regular import - might end up in compiled JS
import { User } from "./types";
// Type-only import - guaranteed to be removed from JS
import type { User } from "./types";
// Mixed imports
import { saveUser, type User } from "./api";
// ^^^^^^^^^ ^^^^^^^^^^^
// value type-only
防止循環依賴問題
保持 JavaScript 包更小
當使用需要明確類型匯入( isolatedModules )的建置工具時
澄清意圖(這僅適用於類型,不適用於執行時程式碼)
了解更多: TypeScript 文件:導入類型
您匯入非 TypeScript 檔案(如映像、CSS 或資料檔案),但 TypeScript 不知道它們應該具有哪種類型。
import logo from "./logo.svg"; // ✗ Error: Cannot find module
建立環境模組聲明,告訴 TypeScript 如何輸入這些導入。
// In a .d.ts file (like global.d.ts or declarations.d.ts)
declare module "*.svg" {
const url: string;
export default url;
}
declare module "*.css" {
const classes: { [key: string]: string };
export default classes;
}
// Now these work:
import logo from "./logo.svg"; // logo: string
import styles from "./app.css"; // styles: { [key: string]: string }
輸入圖像、字體、樣式的匯入
您的建置工具無法處理的 JSON 或資料文件
捆綁器處理的任何非 TypeScript 資產
了解更多:TypeScript 文件:模組聲明模板
satisfies運算符有時您希望 TypeScript 檢查物件是否與類型匹配,但您也希望 TypeScript 記住您使用的特定值(而不僅僅是它們是字串或數字)。
// Without satisfies - loses specific information
const routes: Record<string, string> = {
home: "/",
profile: "/users/:id",
};
// routes.profile is just 'string', not the specific "/users/:id"
satisfies檢查物件是否符合某種類型,而不改變 TypeScript 記住的內容。
const routes = {
home: "/",
profile: "/users/:id",
} satisfies Record<string, `/${string}`>; // Must be strings starting with "/"
// routes.profile is still the literal "/users/:id" - exact value preserved!
需要驗證和特定值類型的配置物件
當您需要自動完成精確值時,而不僅僅是一般類型
了解更多: TypeScript 文件:滿足運算符
asserts和asserts x is T )有時,您需要一個函數,當條件不滿足時拋出錯誤。類型保護(上文)僅適用於if語句 - 它們不會影響函數呼叫後的程式碼。
function assertNotNull(x: unknown) {
if (x == null) throw new Error("Value is null!");
}
const data: string | null = getValue();
assertNotNull(data);
// TypeScript still thinks data might be null here
斷言函數使用asserts來告訴 TypeScript:“如果此函數返回(不拋出),則條件為真。”
function assertNotNull<T>(x: T): asserts x is NonNullable<T> {
if (x == null) throw new Error("Value is null!");
}
const data: string | null = getValue();
assertNotNull(data);
// ✓ TypeScript now knows data is definitely string here!
data.toUpperCase(); // Safe to use
失敗時拋出的驗證函數
強制執行執行時不變數
在函數邊界進行早期錯誤檢查
了解更多: TypeScript 文件:斷言函數
假設您有諸如"user:login" 、 "user:logout" 、 "post:create"等事件名稱。您希望 TypeScript 自動完成這些事件並擷取拼字錯誤,但手動列出這些事件名稱實在太多了。
模板文字類型可讓您使用與 JavaScript 模板字串相同的語法來描述字串模式。
// Generate all combinations automatically
type EventName = `${"user" | "post"}:${"create" | "delete"}`;
// Result: "user:create" | "user:delete" | "post:create" | "post:delete"
function trackEvent(event: EventName) {
// TypeScript will autocomplete and validate the event names!
}
trackEvent("user:create"); // ✓ OK
trackEvent("user:update"); // ✗ Error - not a valid combination
遵循某種模式的 API 路由或事件名稱
帶有前綴/後綴的 CSS 類別名
任何結構化字串格式(如資料庫表名、檔案路徑)
了解更多: TypeScript 文件:範本文字類型
您想要透過對每個成員套用邏輯來過濾或轉換聯合類型(如string | number | null )。
當檢查的類型是「裸露的」(未包裝在另一種類型中)時,條件類型會自動分佈在聯合上。
// Remove null and undefined from a union
type NonNullish<T> = T extends null | undefined ? never : T;
// This distributes: checks each member separately
type Clean = NonNullish<string | number | null>;
// string | number (null was filtered out)
// Extract only function types
type FunctionsOnly<T> = T extends (...args: any[]) => any ? T : never;
type Fns = FunctionsOnly<string | ((x: number) => void) | boolean>;
// (x: number) => void
過濾聯合類型
建立實用程式類型,例如Exclude 、 Extract
以不同的方式改變工會的每個成員
了解更多: TypeScript 文件:分配條件類型
infer條件語句中的類型您需要提取一段複雜類型(例如「這個函數回傳什麼類型?」或「這個陣列裡面有什麼?」)。
使用infer建立一個類型變數,捕獲您正在檢查的類型的一部分。
// Extract the return type of a function
type ReturnType<F> = F extends (...args: any[]) => infer R ? R : never;
type MyFunc = (x: number) => string;
type Result = ReturnType<MyFunc>; // string
// Extract array element type
type ElementType<T> = T extends (infer E)[] ? E : never;
type Numbers = ElementType<number[]>; // number
type Mixed = ElementType<(string | boolean)[]>; // string | boolean
從函數中提取參數或返回類型
從陣列或元組取得元素類型
解析模板文字或複雜結構中的類型
了解更多: TypeScript 文件:在條件類型內推斷
+readonly 、 -?等)有時你需要將現有型別的所有屬性設為必要(移除? ),或將所有內容設為可變(移除readonly )。手動重寫每個屬性非常繁瑣。
映射類型可以新增( + )或刪除( - ) readonly和可選( ? )修飾符。
// Remove readonly from all properties
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
// Remove optional (?) from all properties
type Required<T> = {
[K in keyof T]-?: T[K];
};
type Config = Readonly<{ port?: number; host?: string }>;
type EditableConfig = Mutable<Required<Config>>; // { port: number; host: string }
建立唯讀類型的可編輯版本
使所有屬性成為驗證函數所必需的
建構轉換屬性修飾符的實用程式類型
了解更多: TypeScript 文件:映射修飾符
as )您想要透過變更屬性名稱(例如刪除前綴或過濾掉某些屬性)來轉換物件類型,但映射類型通常會保留相同的鍵。
在映射類型中使用as來在迭代鍵時轉換鍵。
// Remove private properties (those starting with _)
type RemovePrivate<T> = {
[K in keyof T as K extends `_${string}` ? never : K]: T[K];
};
type WithPrivate = { name: string; _secret: number };
type Public = RemovePrivate<WithPrivate>; // { name: string }
// Add a prefix to all keys
type Prefixed<T> = {
[K in keyof T as `app_${string & K}`]: T[K];
};
type Original = { id: number; name: string };
type Result = Prefixed<Original>; // { app_id: number; app_name: string }
透過過濾內部屬性來建立類型的公共版本
命名約定之間的轉換(camelCase 到 snake_case)
根據關鍵模式過濾物件類型
了解更多: TypeScript 文件:映射類型中的鍵重新映射
當您將陣列傳遞給泛型函數時,TypeScript 通常會將其「擴展」為通用陣列類型,從而丟失有關特定值的資訊。
function identity<T>(value: T) {
return value;
}
const pair = identity([1, 2]); // Type is number[], not [1, 2]
在類型參數前加入const來告訴 TypeScript:“盡可能保持具體。”
function identity<const T>(value: T) {
return value;
}
const pair = identity([1, 2]); // Type is [1, 2] - exact tuple preserved!
應保留精確陣列/元組結構的函數
想要透過轉換來追蹤文字值的建構器函數
了解更多: TypeScript 文件:const 類型參數
如何輸入一個需要接受不同數量參數的函數,同時追蹤每個參數的類型?
可變元組可讓您處理可增長或收縮的類型清單。可以把...想像成「將這個類型清單展開到這裡」。
// Type that adds an element to the end of a tuple
type Push<T extends unknown[], U> = [...T, U];
type Result = Push<[string, number], boolean>; // [string, number, boolean]
// Real example: Typing a function wrapper
function logged<Args extends unknown[], Return>(
fn: (...args: Args) => Return,
): (...args: Args) => Return {
return (...args) => {
console.log("Calling with:", args);
return fn(...args);
};
}
包裝函數並保留其確切的參數類型
建立類型安全的函陣列合實用程式
建構元組操作類型
了解更多: TypeScript 文件:元組類型
this參數當函數使用this時,TypeScript 不知道this應該是什麼類型。這會導致方法、回呼和事件處理程序出現問題。
function setName(name: string) {
this.name = name; // ✗ Error: 'this' has type 'any'
}
新增一個明確的this參數(在執行時不算作真正的參數)來鍵入this應該是什麼。
interface Model {
name: string;
setName(this: Model, newName: string): void;
}
const model: Model = {
name: "Initial",
setName(this: Model, newName: string) {
this.name = newName; // ✓ TypeScript knows what 'this' is!
},
};
model.setName("Updated"); // Works
const fn = model.setName;
fn("Test"); // ✗ Error: 'this' context is wrong
依賴於此this方法
事件處理程序回調
設計為使用.call()或.apply()呼叫的函數
了解更多: TypeScript 文件:this 參數
unique symbolTypeScript 使用「結構類型」—兩個具有相同結構的類型被視為相同。有時,您需要結構相同但邏輯不同的類型(例如 UserID 與 ProductID,它們都是字串)。
type UserId = string;
type ProductId = string;
function getUser(id: UserId) {
/* ... */
}
const productId: ProductId = "prod-123";
getUser(productId); // ✗ We want this to be an error, but it's not!
使用unique symbol來建立一個“品牌”,即使結構相同,也會使類型不相容。
declare const USER_ID: unique symbol;
type UserId = string & { [USER_ID]: true };
declare const PRODUCT_ID: unique symbol;
type ProductId = string & { [PRODUCT_ID]: true };
function getUser(id: UserId) {
/* ... */
}
const productId = "prod-123" as ProductId;
getUser(productId); // ✓ Now this IS an error!
防止混淆不同類型的 ID(使用者 ID、訂單 ID 等)
建立不能被意外替換的「名義」類型
用於註冊表或依賴注入的類型安全性金鑰
了解更多: TypeScript 文件:唯一符號
您正在使用第三方程式庫並需要在其類型中新增屬性(例如新增自訂配置選項),但您無法編輯程式庫的程式碼。
使用模組增強從外部加入現有介面或模組。
// In your own .d.ts file
declare module "express" {
// Add to Express's Request interface
interface Request {
user?: { id: string; name: string };
}
}
// Now TypeScript knows about req.user in your Express handlers!
使用自訂屬性擴充庫類型
為非完全類型的庫功能新增類型
您正在註冊新功能的插件系統
了解更多:TypeScript 文件:模組擴充
你想寫一個函數,它接受一個類別(而不是實例)並建立它的實例。如何輸入「可以用new構造的東西」?
function createInstance(SomeClass: ???) {
return new SomeClass();
}
使用建構子簽章: new (...args: any[]) => T來描述可以建構的東西。
// Type describing a constructor
type Constructor<T = unknown, Args extends unknown[] = any[]> = new (
...args: Args
) => T;
function createInstance<T>(Ctor: Constructor<T>): T {
return new Ctor();
}
class User {
name = "Unknown";
}
const user = createInstance(User); // user: User ✓
// More complex: factory with specific constructor arguments
function createPair<T>(
Ctor: Constructor<T, [string, number]>,
name: string,
age: number,
): T {
return new Ctor(name, age);
}
依賴注入框架
建立實例的工廠函數
將類別作為值使用的通用程式碼
測試模擬建構函數的實用程序
了解更多: TypeScript 文件:介面中的建構子簽名
原文出處:https://dev.to/lingodotdev/beyond-the-basics-21-typescript-features-you-might-not-know-about-1dbn