← 返回导航

satisfies 操作符

进阶技巧 · 中级

场景说明

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,但可以在函数中加入运行时校验

关键点解析

使用示例与类型推导


      
// 对比三种写法的类型推导结果

// 写法 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'(精确到具体色值)
在 Playground 中尝试