TypeScript satisfies 操作符完全指南
更新時間:2026年05月25日 09:24:29 作者:兆子龍
本文主要介紹了TypeScript 4.9 引入的 satisfies 操作符,可以同時獲得類型推斷和類型驗證,提升你的 TypeScript 代碼質(zhì)量,感興趣的可以了解一下
一、背景與問題
1.1 傳統(tǒng)類型聲明的困境
在 TypeScript 中,我們經(jīng)常面臨一個兩難選擇:是使用類型注解犧牲類型推斷,還是保持推斷卻失去類型檢查?
// 方案 1:類型注解 —— 失去類型推斷
const config1: Record<string, string | number> = {
port: 8080,
host: "localhost",
debug: true // ? 錯誤:debug 是 boolean,不是 string | number
};
// TypeScript 會報錯,但我們失去了具體的類型信息
// 方案 2:類型推斷 —— 失去類型驗證
const config2 = {
port: 8080,
host: "localhost",
debug: true // ? 推斷為 { port: number; host: string; debug: boolean }
};
// TypeScript 沒有報錯,但我們無法確保 config2 符合預(yù)期格式
// 方案 3:雙重標注 —— 代碼冗余
const config3: { port: number; host: string } = {
port: 8080,
host: "localhost"
};
// 既保證類型,又保留推斷,但寫起來麻煩
1.2satisfies的誕生
TypeScript 4.9 引入了 satisfies 操作符來解決這個困境:
// 使用 satisfies —— 魚與熊掌兼得
const config = {
port: 8080,
host: "localhost",
debug: true
} satisfies ConfigType;
// ? 類型被驗證是否符合 ConfigType
// ? 同時保留了 config 的具體類型推斷({ port: number; host: string; debug: boolean })
1.3 本文目標
- 理解
satisfies的核心概念 - 掌握各種使用場景
- 學(xué)會配合其他 TypeScript 特性使用
- 了解最佳實踐和注意事項
二、核心概念
2.1 基本語法
// 語法結(jié)構(gòu)
expression satisfies Type
// 實際示例
type RGB = [red: number, green: number, blue: number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
} satisfies Record<string, RGB | string>;
// palette 的類型是:
// {
// red: RGB;
// green: string;
// blue: RGB;
// }
// 而不是 Record<string, RGB | string>
2.2 類型推斷 vs 類型驗證
// 場景:定義一個配置對象
type Config = {
port: number;
host: string;
protocol: "http" | "https";
};
// 傳統(tǒng)方式 1:類型注解
const config1: Config = {
port: 8080,
host: "localhost",
protocol: "http"
};
// ? 類型檢查通過
// ? config1.port 被推斷為 number,但 Config 允許任意 number
// 如果 Config 變成 { port: 80 | 8080 },config1 仍然有效(因為 number 兼容)
// satisfies 方式
const config2 = {
port: 8080,
host: "localhost",
protocol: "http"
} satisfies Config;
// ? 類型檢查通過
// ? config2.port 是具體的字面量類型 8080,而不是 number
2.3 關(guān)鍵特性
特性 1:保留字面量類型
type Direction = "north" | "south" | "east" | "west";
const directions1: Direction[] = ["north", "south"]; // string[] 被推斷
const directions2 = ["north", "south"] satisfies Direction[]; // ("north" | "south")[] 被推斷
// 差異:
directions1.push("任意字符串"); // ? 允許(類型為 string[])
directions2.push("任意字符串"); // ? 錯誤(類型為 ("north" | "south")[])
特性 2:聯(lián)合類型成員驗證
type StringOrNumber = string | number;
const mixed = {
a: "hello",
b: 42,
c: true // ? 這里會報錯,因為 c: boolean 不在 StringOrNumber 中
} satisfies Record<string, StringOrNumber>;
特性 3:嵌套對象驗證
type Nested = {
user: {
name: string;
age: number;
};
settings: {
theme: "light" | "dark";
notifications: boolean;
};
};
const data = {
user: {
name: "張三",
age: 25
},
settings: {
theme: "dark",
notifications: true
}
} satisfies Nested;
// data.user.name 是 string 類型
// data.settings.theme 是 "light" | "dark" 類型
三、實戰(zhàn)應(yīng)用場景
3.1 配置對象驗證
// 場景 1:應(yīng)用配置
type AppConfig = {
server: {
port: number;
host: string;
};
database: {
url: string;
poolSize: number;
};
features: string[];
};
const appConfig = {
server: {
port: 3000,
host: "localhost"
},
database: {
url: "postgresql://localhost/mydb",
poolSize: 10
},
features: ["auth", "logging", "analytics"]
} satisfies AppConfig;
// ? 驗證通過
// ? appConfig.server.port 是 number 類型(具體值 3000)
// ? appConfig.features 是 string[] 類型
// 訪問時獲得完整類型提示
appConfig.server.port // number
appConfig.features[0] // string
3.2 路由配置
// 場景 2:路由表定義
type Route = {
path: string;
component: string;
meta?: {
title?: string;
requiresAuth?: boolean;
};
};
const routes = {
home: {
path: "/",
component: "HomePage"
},
about: {
path: "/about",
component: "AboutPage",
meta: {
title: "關(guān)于我們"
}
},
login: {
path: "/login",
component: "LoginPage",
meta: {
requiresAuth: false
}
},
profile: {
path: "/profile",
component: "ProfilePage",
meta: {
title: "個人中心",
requiresAuth: true
}
}
} satisfies Record<string, Route>;
// ? 類型安全
// ? 可以用 routes.home.path 訪問
// ? meta 的可選屬性都被正確處理
// 類型推斷示例
type RouteKey = keyof typeof routes;
// type RouteKey = "home" | "about" | "login" | "profile"
routes.home.meta?.requiresAuth // boolean | undefined
3.3 主題與樣式系統(tǒng)
// 場景 3:設(shè)計令牌系統(tǒng)
type DesignToken = string | number;
type Theme = {
colors: Record<string, DesignToken>;
spacing: Record<string, number>;
fonts: Record<string, string>;
};
const theme = {
colors: {
primary: "#007bff",
secondary: "#6c757d",
success: "#28a745",
error: "#dc3545",
white: "#ffffff"
},
spacing: {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32
},
fonts: {
sans: "system-ui, sans-serif",
mono: "Monaco, monospace"
}
} satisfies Theme;
// 使用示例
function createStyles(colorKey: keyof typeof theme.colors) {
return { color: theme.colors[colorKey] };
}
createStyles("primary"); // ? 正確
createStyles("unknown"); // ? 類型錯誤
3.4 狀態(tài)機定義
// 場景 4:有限狀態(tài)機
type State = "idle" | "loading" | "success" | "error";
type Transition = {
from: State;
to: State;
guard?: (ctx: Context) => boolean;
};
type StateMachine = {
initial: State;
transitions: Transition[];
context: Context;
};
const machine = {
initial: "idle" as const,
transitions: [
{ from: "idle", to: "loading" },
{ from: "loading", to: "success" },
{ from: "loading", to: "error" },
{ from: "error", to: "loading" }
],
context: {
retryCount: 0,
lastError: null as Error | null
}
} satisfies StateMachine;
// 類型安全的狀態(tài)機
type StateKey = typeof machine.initial;
// type StateKey = "idle"
3.5 API 響應(yīng)結(jié)構(gòu)
// 場景 5:API 響應(yīng)類型
type ApiResponse<T> = {
data: T;
status: number;
message?: string;
};
type UserResponse = {
data: {
id: string;
name: string;
email: string;
};
status: 200;
};
const userResponse = {
data: {
id: "user_123",
name: "張三",
email: "zhangsan@example.com"
},
status: 200
} satisfies ApiResponse<{ id: string; name: string; email: string }>;
// userResponse.data.name 是 string
// userResponse.status 是字面量 200
3.6 插件/擴展系統(tǒng)
// 場景 6:插件注冊表
type Plugin = {
name: string;
version: string;
init: (app: App) => void;
dependencies?: string[];
};
type PluginRegistry = Record<string, Plugin>;
const registry = {
auth: {
name: "Auth Plugin",
version: "1.0.0",
init: (app) => {
console.log("Initializing auth...");
}
},
logger: {
name: "Logger Plugin",
version: "2.0.0",
init: (app) => {
console.log("Initializing logger...");
},
dependencies: ["auth"] // 依賴 auth 插件
},
analytics: {
name: "Analytics Plugin",
version: "1.5.0",
init: (app) => {
console.log("Initializing analytics...");
}
}
} satisfies PluginRegistry;
// registry 是 Record<string, Plugin>
// 同時保留了每個插件的具體類型
function loadPlugin(name: keyof typeof registry) {
const plugin = registry[name];
plugin.init(app); // 完整類型提示
}
四、高級用法
4.1 結(jié)合類型推斷
// 使用 infer 推斷 satisfies 的結(jié)果類型
type InferSatisfies<T, U> = T extends satisfies U ? T : never;
// 示例
type Colors = "red" | "green" | "blue";
const myColors = ["red", "green"] satisfies Colors[];
// myColors 的類型是 ("red" | "green")[]
// 結(jié)合 typeof
const baseColors = ["red", "green", "blue"] as const;
type BaseColors = typeof baseColors;
// type BaseColors = readonly ["red", "green", "blue"]
const userColors = ["red", "green"] satisfies typeof baseColors;
// userColors 的類型是 ("red" | "green")[]
4.2 條件類型中的 satisfies
// 根據(jù) satisfies 結(jié)果改變類型
type ValidateConfig<T> = T satisfies Record<string, unknown>
? { [K in keyof T]: T[K] }
: never;
// 使用
type Result = ValidateConfig<{ port: number; host: string }>;
// Result = { port: number; host: string }
type InvalidResult = ValidateConfig<"not an object">;
// InvalidResult = never
4.3 多層嵌套驗證
// 復(fù)雜的嵌套類型
type DeepConfig = {
level1: {
level2: {
level3: {
value: number;
label: string;
}[];
};
};
};
const deepConfig = {
level1: {
level2: {
level3: [
{ value: 1, label: "One" },
{ value: 2, label: "Two" }
]
}
}
} satisfies DeepConfig;
// deepConfig.level1.level2.level3[0].value 是 number
// deepConfig.level1.level2.level3[0].label 是 string
4.4 與 readonly 配合
// 不可變配置
type ImmutableConfig = {
readonly [key: string]: string | number;
};
const immutableConfig = {
apiUrl: "https://api.example.com",
timeout: 5000
} satisfies ImmutableConfig;
// 如果嘗試修改會報錯
immutableConfig.apiUrl = "other"; // ? 錯誤(假設(shè) ApiUrl 不在類型中)
4.5 與 as const 配合
// 結(jié)合 as const 實現(xiàn)完全字面量類型
const strictRoutes = {
home: "/",
about: "/about",
contact: "/contact"
} as const satisfies Record<string, string>;
// 所有值都是字面量類型
type RoutePath = typeof strictRoutes[keyof typeof strictRoutes];
// type RoutePath = "/" | "/about" | "/contact"
五、與 typeof 的區(qū)別
5.1 基本對比
const obj = {
x: 1,
y: "hello"
};
// typeof 獲取類型
type ObjType = typeof obj;
// type ObjType = { x: number; y: string; }
// satisfies 驗證并保留推斷
const validated = {
x: 1,
y: "hello"
} satisfies { x: number; y: string };
// validated 的類型仍然是 { x: number; y: string }
// 關(guān)鍵區(qū)別:typeof 不驗證,satisfies 驗證
5.2 實際差異
// 示例 1:超出類型的字段
const obj1 = {
x: 1,
y: 2,
z: 3 // 額外的字段
};
type Obj1Type = typeof obj1;
// type Obj1Type = { x: number; y: number; z: number; }
// ? 沒有錯誤,額外字段被保留
const obj2 = {
x: 1,
y: 2
} satisfies { x: number; y: string };
// ? 錯誤:y: number 不能賦值給 string | number
5.3 組合使用
// 最佳實踐:先 satisfies 驗證,再 typeof 提取類型
const validatedConfig = {
port: 8080,
host: "localhost",
debug: false
} satisfies {
port: number;
host: string;
debug?: boolean;
};
// 用 typeof 提取新類型
type Config = typeof validatedConfig;
// type Config = {
// port: number;
// host: string;
// debug: boolean | undefined;
// }
// 兩者結(jié)合的優(yōu)勢:
// 1. 驗證初始對象符合預(yù)期結(jié)構(gòu)
// 2. 提取出帶完整推斷的新類型供其他地方使用
六、常見問題與解決方案
6.1 問題 1:可選屬性處理
// 問題
type Config = {
required: string;
optional?: number;
};
const config = {
required: "value"
} satisfies Config;
// ? 正確,可選屬性可以省略
// 但如果想訪問 optional,需要處理 undefined
const opt: number | undefined = config.optional;
6.2 問題 2:聯(lián)合類型驗證
// 問題:如何驗證值屬于聯(lián)合類型
type Color = "red" | "green" | "blue";
const colors = ["red", "green", "purple"] satisfies Color[];
// ? 錯誤:purple 不在 Color 中
// 解決方案:使用類型謂詞
function isColor(val: string): val is Color {
return ["red", "green", "blue"].includes(val);
}
function filterColors(arr: string[]): Color[] {
return arr.filter(isColor);
}
6.3 問題 3:類屬性驗證
// satisfies 不能用于類
class Config {
port = 8080;
}
const config = new Config() satisfies { port: number };
// ? 錯誤:satisfies 不能用于類實例
// 解決方案:使用類型斷言或 separate 類型檢查
const config = new Config();
const checked: { port: number } = config as { port: number };
6.4 問題 4:泛型中的 satisfies
// 問題:泛型約束
function processConfig<T extends { port: number }>(
config: T satisfies T
) {
// 這個語法不對
}
// 正確用法
function processConfig<T>(
config: T satisfies { port: number }
) {
// config 是 T 類型,同時滿足 { port: number }
}
七、性能考慮
7.1 類型檢查開銷
// satisfies 在編譯時進行類型檢查
// 對運行時性能沒有影響
// 但復(fù)雜的多層嵌套 satisfies 可能增加編譯時間
const veryComplex = {
// 100+ 嵌套層
} satisfies VeryDeepNestedType;
// 編譯時間可能增加
7.2 最佳實踐
// ? 推薦:明確的類型定義
type KnownShape = {
a: string;
b: number;
};
const good = { a: "x", b: 1 } satisfies KnownShape;
// ? 不推薦:過度嵌套
const bad = {
level1: {
level2: {
// 更多層...
}
}
} satisfies DeepType;
八、遷移指南
8.1 從類型斷言遷移
// 舊代碼(使用類型斷言)
const config = {
port: 8080
} as {
port: number;
};
// 新代碼(使用 satisfies)
const config = {
port: 8080
} satisfies {
port: number;
};
// 差異:
// as:不驗證類型,可能導(dǎo)致意外的類型寬化
// satisfies:驗證類型,同時保留字面量推斷
8.2 從類型注解遷移
// 舊代碼(使用類型注解)
const config: {
port: number;
host: string;
} = {
port: 8080,
host: "localhost"
};
// 新代碼(使用 satisfies)
const config = {
port: 8080,
host: "localhost"
} satisfies {
port: number;
host: string;
};
// 優(yōu)勢:
// 1. 錯誤信息更明確(如果類型不匹配,指向具體字段)
// 2. 保留字面量類型推斷
九、與其他 TypeScript 特性的對比
9.1 vs 類型別名
type A = string | number; type B = "a" | "b"; // satisfies 驗證值是否符合類型 const val = "a" satisfies B;
9.2 vs 類型守衛(wèi)
// 類型守衛(wèi)需要在運行時檢查
function isConfig(val: unknown): val is Config {
return typeof val === "object" && val !== null && "port" in val;
}
// satisfies 在編譯時驗證
const config = {} satisfies Config;
9.3 vs 類型斷言
// 類型斷言(as)不驗證 const a = "hello" as number; // ? 沒有錯誤,但類型是錯的 // satisfies 驗證 const b = "hello" satisfies number; // ? 錯誤:string 不能賦值給 number
十、總結(jié)
10.1 關(guān)鍵要點
- satisfies 在編譯時驗證類型,同時保留類型推斷
- 它解決了類型注解和類型推斷的兩難困境
- 適用于配置對象、路由表、主題系統(tǒng)等場景
- 可以與 typeof、泛型、條件類型等配合使用
10.2 使用建議
- ? 使用
satisfies驗證配置對象和常量 - ? 使用
satisfies替代as類型斷言 - ? 使用
satisfies保留字面量類型推斷 - ? 不要在簡單場景過度使用
- ? 不要用
satisfies替代運行時驗證
10.3 資源推薦
到此這篇關(guān)于TypeScript satisfies 操作符完全指南的文章就介紹到這了,更多相關(guān)TypeScript satisfies 操作符內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript 刪除數(shù)組中重復(fù)項(uniq)
巧妙去除數(shù)組中的重復(fù)項的方法參考,需要的朋友可以參考下。2010-01-01
WebGL利用FBO完成立方體貼圖效果完整實例(附demo源碼下載)
這篇文章主要介紹了WebGL利用FBO完成立方體貼圖效果的方法,以完整實例形式分析了WebGL實現(xiàn)立方體貼圖的具體步驟與相關(guān)技巧,并附帶了demo源碼供讀者下載參考,需要的朋友可以參考下2016-01-01

