topic: frontend
TypeScript 真香警告
2022 年之前,我一直拒绝 TypeScript。
“类型?NativeScript 就行了啊。”
“编译?浪费时间。”
“类型定义?写 any 就行。”
2022 年初,项目强制用 TS。用了三个月后,我道歉了。
为什么之前拒绝
- 麻烦:要写类型定义,代码变长了
- 不习惯:JavaScript 写久了,觉得类型是累赘
- 学习成本:新语法、新概念,又要学
但真正用了之后,我发现我错了。
真香现场
1. 编辑器报错提前知道
1 2 3 4 5 6 7 8 9 10 11 12 13
| interface User { id: number; name: string; email?: string; }
function getUser(id: number): Promise<User> { return fetch(`/api/users/${id}`) .then(res => res.json()); }
getUser('123');
|
还没运行,编辑器就告诉我错了。
2. 重构不再怕
1 2 3 4 5 6 7 8 9
| function processUser(user) { return user.name.toUpperCase(); }
function processUser(user: User): string { return user.name.toUpperCase(); }
|
改 User 接口,所有调用的地方都会报错。
3. 代码即文档
1 2 3 4 5 6 7
| function createOrder( items: OrderItem[], shippingAddress: Address, paymentMethod: PaymentMethod, couponCode?: string ): Promise<Order>
|
参数类型、返回值,一目了然。
核心概念
1. 基础类型
1 2 3 4 5 6 7 8 9 10
| let name: string = '张三'; let age: number = 25; let isActive: boolean = true; let hobbies: string[] = ['coding', 'music']; let config: [string, number] = ['host', 8080];
let anything: any = 'hello'; let something: unknown = 'hello';
|
2. 接口 vs 类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| interface User { id: number; name: string; }
type User = { id: number; name: string; };
interface User { email: string; }
type Admin = User & { role: 'admin' };
|
3. 泛型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function first<T>(arr: T[]): T | undefined { return arr[0]; }
function longest<T extends { length: number }>(a: T, b: T): T { return a.length > b.length ? a : b; }
interface ApiResponse<T> { code: number; data: T; message: string; }
function fetchUser(): ApiResponse<User> { }
|
4. Utility Types
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| interface User { id: number; name: string; email: string; password: string; }
type PartialUser = Partial<User>;
type RequiredUser = Required<User>;
type ReadonlyUser = ReadonlyUser;
type UserBasic = Pick<User, 'id' | 'name'>;
type UserPublic = Omit<User, 'password'>;
|
实战技巧
1. strict 模式一定要开
1 2 3 4 5 6 7
| { "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true } }
|
2. 用 unknown 而不是 any
1 2 3 4 5 6 7 8 9 10 11 12 13
| function parseJSON(json: any): any { return JSON.parse(json); }
function parseJSON(json: string): object { const result = JSON.parse(json); if (typeof result !== 'object') { throw new Error('Invalid JSON'); } return result; }
|
3. 类型守卫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function process(value: string | number) { if (typeof value === 'string') { return value.toUpperCase(); } else { return value.toFixed(2); } }
function process(value: User | Admin) { if ('role' in value) { console.log(value.role); } else { console.log(value.name); } }
|
4. 类型断言
1 2 3 4 5 6 7 8
| const data = response.data as User;
const name = user!.name;
const name = response.data as unknown as User;
|
常见错误与解决
1. Object is possibly null
1 2 3 4 5 6 7 8 9 10 11 12
| const name = user?.name;
if (user) { const name = user.name; }
function isUser(obj: User | Admin): obj is User { return 'name' in obj; }
|
2. Type is not assignable
1 2 3 4 5 6 7 8
| function greet(name: string) { } function greet(name: string | undefined) { }
function greet(name: string): void; function greet(name?: string): void; function greet(name?: string) { }
|
3. 第三方库没有类型
1 2 3 4 5 6 7
| npm install @types/lodash
declare module 'some-library' { export function doSomething(): void; }
|
感悟
TypeScript 不是负担,是保障。
它让代码更可靠,让协作更顺畅,让重构更安心。
现在让我回去写原生 JS,反而不会了。
真香。