← 返回导航

const 断言技巧

进阶技巧 · 基础

场景说明

as const(const 断言)是 TS 3.4 引入的轻量级特性,却是日常开发中最实用的技巧之一。它的作用是告诉 TS:把这个表达式收窄到最精确的字面量类型,并且设为只读。as const 有四种经典用法:对象属性只读、数组变元组、字面量类型收窄、替代 enum。配合 satisfies 使用时,能同时获得"结构校验 + 精确字面量 + 只读保护"三重收益,是现代 TS 项目的标配写法。

完整代码


      
// as const 的四种用法 + satisfies 最佳实践
// 对象只读、数组变元组、字面量收窄、枚举替代

// ===== 用法一:对象属性只读 =====

// 普通对象:属性可修改,类型是宽泛的 string/number
const user = {
  name: 'Alice',
  age: 25,
};
user.name = 'Bob';  // 可以改
type UserName = typeof user.name;  // string(太宽)

// as const 对象:所有属性变为 readonly,类型收窄为字面量
const userConst = {
  name: 'Alice',
  age: 25,
  address: {
    city: 'Beijing',
    street: 'Main St',
  },
} as const;

// userConst.name = 'Bob';  // 错误:只读属性不能修改
// userConst.address.city = 'Shanghai';  // 错误:嵌套也只读
type UserNameConst = typeof userConst.name;     // 'Alice'(精确字面量)
type UserCityConst = typeof userConst.address.city; // 'Beijing'(嵌套也精确)

// ===== 用法二:数组变元组 =====

// 普通数组:类型是 number[],长度不固定
const point = [10, 20];
type PointArr = typeof point;  // number[]

// as const 数组:变成只读元组,长度固定,每个位置类型精确
const pointTuple = [10, 20] as const;
type PointTuple = typeof pointTuple;  // readonly [10, 20]
type XCoord = typeof pointTuple[0];  // 10
type YCoord = typeof pointTuple[1];  // 20

// 常见场景:React useState 返回值就是元组
const stateTuple = ['hello', (v: string) => {}] as const;
// 第一个是 string,第二个是函数,位置固定

// ===== 用法三:字面量类型收窄 =====

// 默认情况下,const 字符串/数字已经是字面量类型
const str = 'hello';  // 'hello' 类型
const num = 42;       // 42 类型

// 但 let/var 会拓宽
let strLet = 'hello';  // string 类型
let numLet = 42;       // number 类型

// as const 可以强制收窄(不过 let + as const 意义不大,一般用 const 声明)
let strNarrow = 'hello' as const;  // 'hello' 类型,但仍是 let 可重新赋值

// 更实用的:函数返回值收窄为字面量
function getStatus() {
  return 'success' as const;  // 返回 'success' 字面量,不是 string
}
type Status = ReturnType<typeof getStatus>;  // 'success'

// ===== 用法四:替代 enum =====

// 传统 enum 有反向映射、不是运行时真实对象等问题
enum DirectionEnum {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT',
}

// 推荐:as const 对象替代 enum
const Direction = {
  Up: 'UP',
  Down: 'DOWN',
  Left: 'LEFT',
  Right: 'RIGHT',
} as const;

type DirectionValue = typeof Direction[keyof typeof Direction];
// => 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'

// 运行时可用:遍历、Object.values、Object.keys
const allDirections = Object.values(Direction);  // ['UP', 'DOWN', 'LEFT', 'RIGHT']

// ===== 最佳实践:as const + satisfies =====

// 既有结构校验,又保留字面量精度,还只读
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type EndpointConfig = Record<string, { path: string; method: HTTPMethod }>;

const ENDPOINTS = {
  getUser: { path: '/api/user/:id', method: 'GET' },
  createUser: { path: '/api/users', method: 'POST' },
  updateUser: { path: '/api/user/:id', method: 'PUT' },
  deleteUser: { path: '/api/user/:id', method: 'DELETE' },
} as const satisfies EndpointConfig;

// 三重收益:
// 1. satisfies:结构不符合 EndpointConfig 会报错
// 2. as const:所有属性都是精确字面量 + 只读
// 3. 可以推导出精确的 key 联合和 value 字面量

type EndpointKey = keyof typeof ENDPOINTS;
// => 'getUser' | 'createUser' | 'updateUser' | 'deleteUser'

type GetUserPath = typeof ENDPOINTS.getUser.path;
// => '/api/user/:id'(精确字面量)

关键点解析

使用示例与类型推导


      
// 1. 对象字面量收窄对比
const obj1 = { a: 'x', b: 1 };
type T1 = typeof obj1.a;  // string

const obj2 = { a: 'x', b: 1 } as const;
type T2 = typeof obj2.a;  // 'x'

// 2. 数组 vs 元组
const arr1 = ['a', 'b', 'c'];
type A1 = typeof arr1;      // string[]
type A1Len = typeof arr1.length; // number

const arr2 = ['a', 'b', 'c'] as const;
type A2 = typeof arr2;      // readonly ['a', 'b', 'c']
type A2Len = typeof arr2.length; // 3(精确长度!)

// 3. 从 as const 数组推导联合类型
const FRUITS = ['apple', 'banana', 'orange'] as const;
type Fruit = typeof FRUITS[number];
// => 'apple' | 'banana' | 'orange'

// 4. 替代数字 enum
const Status = {
  Pending: 0,
  Success: 1,
  Failed: 2,
} as const;

type StatusValue = typeof Status[keyof typeof Status];
// => 0 | 1 | 2

// 运行时可用
const statusText = {
  [Status.Pending]: '待处理',
  [Status.Success]: '成功',
  [Status.Failed]: '失败',
};

// 5. as const + satisfies 验证:结构错误会被拦截
type ConfigShape = { api: string; timeout: number };

// 正确:结构符合,且保留字面量
const good = {
  api: 'https://api.example.com',
  timeout: 5000,
} as const satisfies ConfigShape;

// 错误:timeout 是 string,不符合 ConfigShape
// const bad = {
//   api: 'https://api.example.com',
//   timeout: '5000',
// } as const satisfies ConfigShape;
在 Playground 中尝试