一、interface 还是 type
interface User { id: number; name: string }
type Point = { x: number; y: number };
两者在描述对象时几乎等价,区别在于:
interface可以声明合并(同名自动合并),适合给第三方库扩展类型。type可以定义联合类型、交叉类型、映射类型,表达力更强。interface的报错信息更友好(名字会保留)。
实用建议:对象结构用 interface,需要组合/变形时用 type。不必纠结,两者可互相转换。
二、联合类型与字面量
type Status = 'pending' | 'paid' | 'shipped';
type Id = string | number;
function setStatus(s: Status) { /* ... */ }
setStatus('paid'); // OK
setStatus('done'); // 报错,'done' 不在联合里
字面量联合比 string 更安全:编辑器能自动补全,写错立刻报红。用它替代「魔法字符串」是最容易见效的改进。
三、泛型:让类型跟着数据走
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const n = first([1, 2, 3]); // number | undefined
const s = first(['a', 'b']); // string | undefined
不用泛型就得写 any,或者为每种类型写一个函数。泛型让「输入什么类型,输出就对应什么类型」。
常见约束:
function pluck<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
K extends keyof T 保证传入的键一定存在于对象上。
四、类型收窄:报错的解药
TypeScript 只有在「确定类型」时才允许访问对应属性。收窄的四种方式:
// 1. typeof
function f(x: string | number) {
if (typeof x === 'string') return x.toUpperCase(); // 这里 x 是 string
return x.toFixed(2); // 这里 x 是 number
}
// 2. 判别联合(最推荐)
type Shape =
| { kind: 'circle'; r: number }
| { kind: 'rect'; w: number; h: number };
function area(s: Shape) {
if (s.kind === 'circle') return Math.PI * s.r ** 2;
return s.w * s.h;
}
// 3. in 操作符
if ('r' in s) { /* ... */ }
// 4. 类型谓词
function isUser(x: unknown): x is User {
return typeof x === 'object' && x !== null && 'id' in x;
}
判别联合(一个有共同字面量字段的联合)是最清晰的建模方式,比一堆可选字段强得多。
五、any 与 unknown
let a: any = 1; // 放弃检查,之后任何操作都通过
let u: unknown = 1; // 未知类型,使用前必须收窄
any 会污染整条调用链(传进去一个 any,返回值也失去类型)。unknown 是安全的「未知」:必须先判断类型才能用。
接收外部数据(接口响应、JSON.parse、catch 的 error)时,用 unknown 再校验,而不是 any 直接点下去。
六、常见报错怎么读
| 报错 | 含义 | 处理 |
|---|---|---|
Object is possibly 'undefined' | 可能为 undefined | 可选链 ?.、默认值或判空 |
Property 'x' does not exist on type 'Y' | 类型上没有该属性 | 检查拼写、补类型定义、收窄 |
Type 'string' is not assignable to type 'number' | 类型不匹配 | 转换或修正声明 |
Argument of type ... is not assignable | 参数类型错 | 看函数签名,别急着 as any |
Cannot find module 'x' | 缺类型声明 | npm i -D @types/x 或写 declare module |
as any 能让报错消失,但问题还在运行时。用它之前先想清楚「为什么类型对不上」,多数情况下是建模有问题。
七、几个实用类型工具
Partial<T> // 所有字段变可选
Required<T> // 所有字段变必填
Pick<T, 'a'|'b'> // 只取部分字段
Omit<T, 'id'> // 去掉某些字段
Record<string, number> // 键值映射
ReturnType<typeof fn> // 函数返回值类型
Awaited<Promise<T>> // 解开 Promise
做 API 的入参/出参类型时,Pick/Omit 能避免重复定义。
八、配置建议
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true
}
}
strict 一次性打开一批严格检查(含 strictNullChecks),新项目一定开。老项目可以逐步开,先开 noImplicitAny 把隐式 any 清掉。
小结
对象结构用 interface、组合变形用 type;字面量联合替代魔法字符串;泛型让类型跟着数据走;收窄优先用判别联合;外部数据用 unknown 不用 any;strict 必开。