TS 4.9 引入的 satisfies 操作符解决了一个长期存在的两难:给变量加类型注解会"丢失"字面量推断,不加类型注解又无法校验结构是否正确。satisfies 相当于"既校验类型,又保留原始推断"——它会检查表达式是否满足指定类型,但不会改变表达式的推断结果。配合 as const 使用可以同时获得类型安全和精确字面量类型,是 TS 近几个版本最实用的新特性之一。
// satisfies 操作符:既校验类型,又保留字面量推断
// TS 4.9+ 引入,解决类型注解 vs 字面量推断的两难
// 1. 场景一:颜色映射(校验结构 + 保留精确值)
type ColorConfig = Record<string, string>;
// 传统写法 A:加类型注解 → 丢失字面量,color 是 string 不是 '#ff0000'
const colorsAnnotated: ColorConfig = {
red: '#ff0000',
green: '#00ff00',
blue: '#0000ff',
};
type RedAnnotated = typeof colorsAnnotated.red; // string(太宽了)
// 传统写法 B:不加类型 → 保留字面量,但结构错误不报错
const colorsNoType = {
red: '#ff0000',
green: '#00ff00',
// blue: 123, // 不会报错(但运行时可能有问题)
};
type RedNoType = typeof colorsNoType.red; // '#ff0000'(精确)
// ✅ satisfies 写法:既校验类型,又保留字面量推断
const colors = {
red: '#ff0000',
green: '#00ff00',
blue: '#0000ff',
} satisfies ColorConfig;
type RedColor = typeof colors.red; // '#ff0000'(精确字面量)
type ColorNames = keyof typeof colors; // 'red' | 'green' | 'blue'(精确联合)
// 2. 场景二:API 路径配置(校验结构 + 保留字面量路径)
type ApiConfig = Record<string, string>;
const apiPaths = {
getUser: '/api/user/:id',
listUsers: '/api/users',
createUser: '/api/users',
updateUser: '/api/user/:id',
deleteUser: '/api/user/:id',
} satisfies ApiConfig;
// 可以用精确字面量类型做进一步推导
type ApiName = keyof typeof apiPaths;
// => 'getUser' | 'listUsers' | 'createUser' | 'updateUser' | 'deleteUser'
// 3. 场景三:配置对象(嵌套结构 + as const + satisfies)
interface AppConfig {
env: 'development' | 'production' | 'test';
port: number;
features: Record<string, boolean>;
}
const config = {
env: 'development',
port: 3000,
features: {
darkMode: true,
i18n: false,
analytics: true,
},
} as const satisfies AppConfig;
type EnvType = typeof config.env;
// => 'development'(精确字面量,不是联合)
type FeatureNames = keyof typeof config.features;
// => 'darkMode' | 'i18n' | 'analytics'
// 4. 场景四:函数返回值 satisfies(校验返回类型但保留精确值)
function createUser(name: string) {
return {
id: Date.now(),
name,
createdAt: new Date().toISOString(),
} satisfies { id: number; name: string; createdAt: string };
}
const user = createUser('Alice');
// user 的类型是 { id: number; name: string; createdAt: string }
// 但如果后续修改返回值结构不符合,TS 会在函数内部报错
// 5. satisfies + 泛型:更灵活的类型约束
function validateConfig<T extends AppConfig>(config: T): T {
return config;
}
const validated = validateConfig({
env: 'production',
port: 8080,
features: { ssr: true },
});
// 等价于 satisfies,但可以在函数中加入运行时校验
const x: T = expr 会把 x 的类型强制变成 T;const x = expr satisfies T 只校验 expr 是否符合 T,x 的类型还是 expr 的原始推断类型。前者更安全但丢失精度,后者精度高但有结构校验。as const satisfies T 是非常强力的组合——as const 把所有属性收窄为只读字面量,satisfies 校验整体结构是否符合 T,既精确又安全,是配置对象的最佳实践。// 对比三种写法的类型推导结果
// 写法 1:类型注解
const a: Record<string, string> = { foo: 'bar' };
type A = typeof a.foo; // string(太宽)
// 写法 2:不加类型
const b = { foo: 'bar' };
type B = typeof b.foo; // string(对象属性默认拓宽)
// 写法 3:as const
const c = { foo: 'bar' } as const;
type C = typeof c.foo; // 'bar'(精确,但结构错了不报错)
// ✅ 写法 4:as const + satisfies
const d = { foo: 'bar' } as const satisfies Record<string, string>;
type D = typeof d.foo; // 'bar'(精确 + 结构校验)
// satisfies 校验失败的例子(会报错)
// const bad = { foo: 123 } satisfies Record<string, string>; // 错误
// 实战:从路由配置提取路径字面量
const routes = {
home: '/',
about: '/about',
user: '/user/:id',
} as const satisfies Record<string, string>;
type RoutePath = typeof routes[keyof typeof routes];
// => '/' | '/about' | '/user/:id'
// 实战:颜色值精确推导 + 校验
type ThemeColors = {
primary: string;
secondary: string;
background: string;
};
const lightTheme = {
primary: '#3b82f6',
secondary: '#64748b',
background: '#ffffff',
} as const satisfies ThemeColors;
type PrimaryColor = typeof lightTheme.primary;
// => '#3b82f6'(精确到具体色值)