极简工具箱

免费在线工具 · 本地计算 · 无需上传

TypeScript 类型入门:interface、泛型与类型收窄

TypeScript 的类型不是「写给别人看的注释」,而是一套能在编译期抓出 bug 的检查。学会类型收窄,80% 的报错都能自己想明白。

广告位(百度联盟)

一、interface 还是 type

interface User { id: number; name: string }
type Point = { x: number; y: number };

两者在描述对象时几乎等价,区别在于:

实用建议:对象结构用 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 必开。

本文相关工具(本地计算,无需上传)

更多使用教程