说实话,写 TS 的时候我最想干的事就是甩个 any 上去让编译器闭嘴。以前也这么干,后来线上爆了几次才慢慢改。类型这东西,写的时候多花两分钟,能省掉半夜爬起来查 bug 的工夫。下面几个是我现在基本天天在用的。
能不断言就别断言
as 说白了就是跟编译器讲「你别管,我知道我在干嘛」。问题是你经常并不知道。能让它自己推出来,就别硬来:
function format(value: string | number) {
if (typeof value === 'number') {
return value.toFixed(2); // 走到这,value 就是 number
}
return value.trim(); // 这边是 string
}
要是对象上带个 ok 之类的标记,收窄起来更舒服:
type Result =
| { ok: true; data: string }
| { ok: false; error: string };
function handle(r: Result) {
if (r.ok) return r.data; // 进了这分支它就知道有 data
throw new Error(r.error);
}
类型也能复用,别一个个手抄字段
接口改一个字段,结果三个地方跟着改,烦死。Partial、Pick、Omit、Record 派生一下就完事:
interface User { id: string; name: string; email: string; }
type UserPatch = Partial<Omit<User, 'id'>>; // 更新用,除了 id 都可选
type UserMap = Record<string, User>; // 拿 id 当 key
satisfies 是真香
以前我给对象加约束会写 const x: Record<string, string> = {...},结果取值的时候字面量类型全丢了。satisfies 就是治这个的——既校验,又不把类型给你拓宽:
const routes = {
home: '/',
post: '/posts/[slug]',
} satisfies Record<string, string>;
routes.home; // 还是 '/' 这个字面量,不是 string
泛型记得加约束
function pluck<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
加了 K extends keyof T,传个不存在的 key 当场编译报错,返回类型也是准的。不加的话……基本又退回 any 了。
总之就一句话:能让编译器帮你查的,别留到运行时自己查。
