三块面板:TS 源码 / 写死的 tsc 报错样本(非现场运行)/ 可点跑的等价 JS。核心:TS 里一个具体值本身就是一种类型,白名单错误在写的时候就暴露。

一、把允许值并成联合,当作参数白名单

TS 源码

type Dir = 'up' | 'down' | 'left' | 'right'
function move(d: Dir) { /* 只认这四个 */ }
move('up')          // ✓
move('diagonal')    // ✗ 不在白名单,写的时候就报错
move.ts:4:7 - error TS2345: Argument of type '"diagonal"' is not assignable to parameter of type 'Dir'. 4 move('diagonal') ~~~~~~~~~~
为什么报:Dir 是字面量联合,等于给参数列了白名单。'diagonal' 不属于任何一支,赋值兼容性失败——比用 string 宽接、运行时再 if 判断安全得多。

二、字面量宽化 vs as const

const a = { dir: 'up' }            // dir: string(被宽化)
const b = { dir: 'up' } as const   // dir: 'up'(钉死),且整个对象只读
const arr = [1, 2] as const        // readonly [1, 2]
[迷你控制台]

三、把值当类型用:typeof + 索引访问

const CONFIG = { api: '/v1', retry: 3 } as const
type ConfigKey = keyof typeof CONFIG        // 'api' | 'retry'
type RetryVal  = (typeof CONFIG)['retry']   // 3(把值当类型取出来)

四、用「as const 对象 + 联合」替代 enum

const Direction = { Up: 0, Down: 1, Left: 2 } as const
type Direction = (typeof Direction)[keyof typeof Direction]   // 0 | 1 | 2
function go(d: Direction) { /* d 只能是 0 | 1 | 2 */ }
go(Direction.Up)   // ✓
go(9)              // ✗ 9 不在联合里
dir.ts:5:4 - error TS2345: Argument of type '9' is not assignable to parameter of type 'Direction'. // Direction 被提炼成字面量联合 0 | 1 | 2,9 越界即报错
为什么这样替代 enum:as const 对象零运行时开销(不像 enum 会生成一个真实对象),又能把值反查成联合类型当白名单用——两头的好处都拿到。