引言
TypeScript 已经成为了现代前端开发的标配,但很多开发者仅仅把它当作「带类型的 JavaScript」来用,实际上 TypeScript 的类型系统非常强大,善用它能让你的代码更健壮、团队协作更顺畅。这篇文章将带你深入理解 TypeScript 的核心概念,并分享真实项目中的实用技巧。
类型基础
原始类型
// 基础类型
const name: string = 'Sean';
const age: number = 25;
const isDeveloper: boolean = true;
const nullValue: null = null;
const undefinedValue: undefined = undefined;
// Symbol 和 BigInt
const symbolKey: symbol = Symbol('key');
const bigNumber: bigint = 100n;
数组和元组
// 数组
const numbers: number[] = [1, 2, 3];
const strings: Array<string> = ['a', 'b', 'c'];
// 元组 - 固定长度和类型的数组
const tuple: [string, number] = ['Sean', 25];
const optionalTuple: [string, number?] = ['Sean']; // 可选元素
对象类型
// 方式一:interface
interface Person {
name: string;
age: number;
email?: string; // 可选属性
readonly id: number; // 只读属性
}
// 方式二:type
type Person = {
name: string;
age: number;
email?: string;
};
// 使用
const person: Person = {
name: 'Sean',
age: 25,
id: 1, // 不能修改
};
高级类型
联合类型和交叉类型
// 联合类型:可以是多种类型之一
type ID = string | number;
const userId: ID = 'abc123';
const postId: ID = 123;
// 交叉类型:合并多个类型
interface Name {
name: string;
}
interface Age {
age: number;
}
type Person = Name & Age;
const person: Person = { name: 'Sean', age: 25 };
泛型
// 基础泛型
function identity<T>(arg: T): T {
return arg;
}
const result = identity<string>('hello'); // 显式指定类型
const inferred = identity('hello'); // 类型推断
// 泛型约束
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): void {
console.log(arg.length);
}
logLength('hello'); // ✓
logLength([1, 2, 3]); // ✓
// logLength(123); // ✗ Error
// 泛型接口
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
interface User {
id: number;
name: string;
}
const response: ApiResponse<User> = {
data: { id: 1, name: 'Sean' },
status: 200,
message: 'success',
};
// 泛型类
class Container<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
get(): T {
return this.value;
}
}
映射类型
// 从现有类型创建新类型
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
interface User {
id: number;
name: string;
age: number;
}
type ReadonlyUser = Readonly<User>;
type PartialUser = Partial<User>;
// 实用类型(TypeScript 内置)
type PickUser = Pick<User, 'id' | 'name'>;
type OmitUser = Omit<User, 'age'>;
实用技巧
1. 类型守卫
// typeof - 原始类型
function print(value: string | number) {
if (typeof value === 'string') {
console.log(value.toUpperCase());
} else {
console.log(value.toFixed(2));
}
}
// instanceof - 类
class Dog {
bark() { console.log('Woof!'); }
}
class Cat {
meow() { console.log('Meow!'); }
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.meow();
}
}
// 自定义类型守卫
interface Fish {
swim(): void;
}
interface Bird {
fly(): void;
}
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
2. 模板字面量类型
// 动态创建字符串类型
type EventName = 'click' | 'focus' | 'blur';
type Handler = `on${EventName}`;
const handler: Handler = 'onClick'; // ✓
// const invalid: Handler = 'onHover'; // ✗
// 组合类型
type Path = '/home' | '/about' | '/user';
type Method = 'get' | 'post' | 'delete';
type Route = `${Method}${Path}`;
const route: Route = 'get/home'; // ✓
// 提取部分
type Props = 'marginTop' | 'marginBottom' | 'paddingLeft';
type CssProperty = `margin${'Top' | 'Bottom'}` | `padding${'Left' | 'Right'}`;
3. 条件类型
// 基础条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<123>; // false
// 提取元素类型
type ArrayElement<T> = T extends (infer U)[] ? U : never;
type Elem = ArrayElement<string[]>; // string
type Elem2 = ArrayElement<number[]>; // number
// 实用场景:API 响应类型
interface ApiSuccess<T> {
success: true;
data: T;
}
interface ApiError {
success: false;
error: string;
}
type ApiResponse<T> = ApiSuccess<T> | ApiError;
// 提取成功数据类型
type SuccessData<T> = T extends ApiSuccess<infer U> ? U : never;
type Result = SuccessData<ApiResponse<User>>; // User
4. satisfies 操作符
// 既类型推断,又保持字面量类型
const config = {
port: 3000,
host: 'localhost',
timeout: 5000,
} satisfies Record<string, number | string>;
console.log(config.port); // 类型是 3000(字面量),不是 number
console.log(config.host); // 类型是 'localhost',不是 string
5. 类型断言
// as 断言
const value: unknown = 'hello';
const length = (value as string).length;
// 非空断言
const element = document.getElementById('app')!; // 确信元素存在
// 双重断言
const input: unknown = 'hello';
const str = input as string as number; // 危险!尽量避免
// const 断言
const colors = ['red', 'green', 'blue'] as const;
// 类型是 readonly ['red', 'green', 'blue']
最佳实践
1. 优先使用接口
// ✓ 推荐:接口可以合并
interface User {
name: string;
}
interface User {
age: number;
}
const user: User = { name: 'Sean', age: 25 }; // 合并后
// ✗ type 不支持声明合并
type User = { name: string };
type User = { age: number }; // Error
2. 使用 type 别名简化复杂类型
// ✗ 难以阅读
function process(
items: Array<{ id: number; name: string; status: 'pending' | 'done' }>
): Array<{ id: number; name: string }> {}
// ✓ 使用 type 别名
type Item = {
id: number;
name: string;
status: 'pending' | 'done';
};
type ProcessedItem = Pick<Item, 'id' | 'name'>;
function process(items: Item[]): ProcessedItem[] {
return items
.filter(item => item.status === 'done')
.map(({ id, name }) => ({ id, name }));
}
3. strict 模式始终开启
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}
// ✗ noImplicitAny: 会报错
function greet(message) {
console.log(message);
}
// ✓
function greet(message: string) {
console.log(message);
}
// ✗ strictNullChecks: 会报错
const element = document.getElementById('app');
element.innerHTML = 'Hello'; // element 可能是 null
// ✓
const element = document.getElementById('app');
if (element) {
element.innerHTML = 'Hello';
}
4. 避免使用 any,使用 unknown
// ✗ any - 放弃所有类型检查
function parse(value: any): any {
return value;
}
const result = parse('hello');
result.foo.bar.baz; // 不会报错,隐藏风险
// ✓ unknown - 强制类型检查
function parse(value: unknown): string {
if (typeof value === 'string') {
return value.toUpperCase();
}
throw new Error('Invalid value');
}
const result = parse('hello');
// result.foo // Error - unknown 类型
5. 使用枚举还是常量对象
// 方式一:const object(推荐)
const Status = {
Pending: 'pending',
Done: 'done',
Failed: 'failed',
} as const;
type Status = typeof Status[keyof typeof Status];
// 方式二:enum
enum StatusEnum {
Pending = 'pending',
Done = 'done',
Failed = 'failed',
}
// 推荐 const object 的原因:
// - 更易 tree-shake
// - 兼容性好(enum 在某些场景有坑)
// - 符合函数式思维
常见错误解析
错误 1:找不到模块
// ✗
import { something } from './module';
// ✓ 检查路径和扩展名
import { something } from './module.ts';
import { something } from './module/index.ts';
错误 2:类型不匹配
// ✗
const arr = [1, '2', 3];
const num: number = arr[1]; // string 无法赋值给 number
// ✓ 使用联合类型
const arr: (number | string)[] = [1, '2', 3];
const item = arr[1]; // number | string
错误 3:泛型实例化
// ✗
class Container<T> {
value: T;
}
const container: Container = new Container(); // 需要指定 T
// ✓
const container: Container<string> = new Container();
container.value = 'hello';
总结
TypeScript 的学习曲线并不陡峭,但深入理解其类型系统能让你:
- 更早发现 bug:编译时而非运行时
- 更好的代码提示:IDE 自动补全
- 更安全的重构:修改类型,全局检查
记住这些核心概念:
- 泛型是 TypeScript 的灵魂
- 类型守卫让运行时检查更优雅
- strict 模式是代码质量的保障
- 优先使用 interface,复杂类型用 type
Happy TypeScripting! 🚀