TypeScript 基础
TypeScript 是 JavaScript 的超集,它在 JavaScript 的基础上添加了静态类型检查系统。本文档将全面介绍 TypeScript 的核心类型系统、接口、泛型、工具类型、声明文件以及类型守卫等知识。
TypeScript 类型系统
原始类型(Primitive Types)
TypeScript 支持 JavaScript 中的所有原始类型,并为其添加了类型注解能力:
// 字符串类型
const name: string = 'TypeScript';
// 数字类型(所有数字都是浮点数)
const age: number = 10;
// 布尔类型
const isReady: boolean = true;
// null 和 undefined
const n: null = null;
const u: undefined = undefined;
// symbol 类型
const sym: symbol = Symbol('unique');
// bigint 类型
const big: bigint = 9007199254740991n;
// void 类型(通常用于函数返回值)
function log(message: string): void {
console.log(message);
}对象类型(Object Types)
在 TypeScript 中,有多种方式描述对象的结构:
// 对象类型字面量
function greet(person: { name: string; age: number }): string {
return `Hello, ${person.name}`;
}
// 可选属性
interface Config {
url: string;
method?: 'GET' | 'POST'; // 可选属性
timeout?: number; // 可选属性
}
// 只读属性
interface Point {
readonly x: number;
readonly y: number;
}
const p: Point = { x: 10, y: 20 };
// p.x = 5; // 错误!x 是只读属性
// 索引签名
interface StringDictionary {
[key: string]: string;
}
const dict: StringDictionary = {
hello: 'world',
foo: 'bar'
};数组类型(Array Types)
// 两种等价写法
const arr1: number[] = [1, 2, 3];
const arr2: Array<number> = [1, 2, 3];
// 只读数组
const readonlyArr: ReadonlyArray<number> = [1, 2, 3];
// readonlyArr.push(4); // 错误!
// 元组(Tuple)- 固定长度和类型的数组
const tuple: [string, number, boolean] = ['hello', 42, true];
// 可选元组元素
const optionalTuple: [string, number?] = ['hello'];
const fullTuple: [string, number?] = ['hello', 42];
// 具名元组(TypeScript 4.0+)
const named: [name: string, age: number] = ['Alice', 30];元组(Tuple)
元组是 TypeScript 特有的类型,它允许精确地描述数组中每个位置的类型:
// 基本元组
type RGB = [number, number, number];
const red: RGB = [255, 0, 0];
// 解构元组
const [r, g, b] = red;
// 元组与可变参数
function concat<T extends unknown[]>(...args: T): T {
return args;
}
const result = concat('hello', 42, true); // 类型为 [string, number, boolean]枚举(Enum)
枚举允许定义一组命名常量,TypeScript 支持数字枚举和字符串枚举:
// 数字枚举(默认从 0 开始)
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
// 指定初始值
enum Status {
Active = 1,
Inactive, // 2
Pending // 3
}
// 字符串枚举
enum Color {
Red = 'RED',
Green = 'GREEN',
Blue = 'BLUE'
}
// 常量枚举(编译时完全移除)
const enum Size {
Small = 'S',
Medium = 'M',
Large = 'L'
}
// 使用枚举
const dir: Direction = Direction.Up;
const colorName: string = Color.Red; // 'RED'
// 反向映射(仅数字枚举支持)
const enumName = Direction[0]; // 'Up'使用枚举时的注意事项:
- 数字枚举支持反向映射,字符串枚举不支持
- 常量枚举(
const enum)在编译时会被内联,不会生成 JavaScript 代码 - 在声明文件(
.d.ts)中,推荐使用常量枚举减少体积
接口(Interface)与类型别名(Type)的区别
interface 和 type 是 TypeScript 中定义类型形状的两种主要方式,它们有许多重叠的功能,但也有一些关键区别。
接口(Interface)
// 定义对象结构
interface User {
id: number;
name: string;
email: string;
}
// 声明合并(Declaration Merging)
interface User {
age?: number; // 合并后 User 包含 id、name、email、age
role: 'admin' | 'user';
}
// 继承(extends)
interface Admin extends User {
permissions: string[];
}
// 可以被类实现(implements)
class SuperAdmin implements Admin {
// 必须实现所有属性和方法
}类型别名(Type)
// 定义联合类型
type ID = string | number;
// 定义交叉类型
type Person = { name: string } & { age: number };
// 定义工具类型
type Nullable<T> = T | null | undefined;
// 映射类型
type ReadonlyPerson = {
readonly [K in keyof Person]: Person[K];
};主要区别对比
| 特性 | Interface | Type |
|---|---|---|
| 声明合并 | 支持 | 不支持 |
| 继承语法 | extends | &(交叉类型) |
| 类实现 | 支持 implements | 支持 implements |
| 联合类型 | 不支持 | 支持 |
| 映射类型 | 不支持 | 支持 |
| 条件类型 | 不支持 | 支持 |
| 工具类型 | 不支持直接定义 | 支持 |
| 性能 | 通常更快 | 复杂类型可能较慢 |
最佳实践建议: 优先使用 interface 定义公共 API 的对象形状,使用 type 定义联合类型、交叉类型和工具类型。
泛型(Generics)
泛型允许创建可复用且类型安全的组件,是 TypeScript 类型系统的核心能力之一。
泛型函数
// 简单泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用类型推导
const num = identity(42); // 类型为 number
const str = identity('hello'); // 类型为 string
// 显式指定类型
const bool = identity<boolean>(true);
// 多个类型参数
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const p = pair('name', 42); // 类型为 [string, number]
// 泛型箭头函数
const wrap = <T>(value: T): { value: T } => ({ value });泛型接口
// 泛型接口
interface Repository<T> {
getById(id: string): T | undefined;
getAll(): T[];
create(item: T): void;
update(id: string, item: Partial<T>): void;
delete(id: string): void;
}
// 实现泛型接口
class UserRepository implements Repository<User> {
private users: Map<string, User> = new Map();
getById(id: string): User | undefined {
return this.users.get(id);
}
getAll(): User[] {
return Array.from(this.users.values());
}
create(item: User): void {
this.users.set(item.id.toString(), item);
}
update(id: string, item: Partial<User>): void {
const existing = this.users.get(id);
if (existing) {
this.users.set(id, { ...existing, ...item });
}
}
delete(id: string): void {
this.users.delete(id);
}
}泛型约束
使用 extends 关键字约束泛型参数的类型范围:
// 约束必须有 length 属性
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength('hello'); // 正确:字符串有 length
logLength([1, 2, 3]); // 正确:数组有 length
// logLength(42); // 错误:数字没有 length
// 使用 keyof 约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user: User = { id: 1, name: 'Alice' };
getProperty(user, 'name'); // 正确:'name' 在 User 中
// getProperty(user, 'age'); // 错误:'age' 不在 User 中泛型工具类型(Built-in Utility Types)
TypeScript 内置了一系列泛型工具类型,用于常见类型转换:
// Partial<T> - 将 T 的所有属性变为可选
interface Task {
title: string;
description: string;
completed: boolean;
}
type PartialTask = Partial<Task>;
// 等价于 { title?: string; description?: string; completed?: boolean }
function updateTask(id: string, updates: Partial<Task>): void {
// 只更新传入的字段
}
// Required<T> - 将 T 的所有属性变为必需
type RequiredTask = Required<PartialTask>;
// 等价于 { title: string; description: string; completed: boolean }
// Pick<T, K> - 从 T 中选择部分属性
type TaskPreview = Pick<Task, 'title' | 'completed'>;
// 等价于 { title: string; completed: boolean }
// Omit<T, K> - 从 T 中排除部分属性
type TaskWithoutDesc = Omit<Task, 'description'>;
// 等价于 { title: string; completed: boolean }
// Record<K, T> - 创建键为 K、值为 T 的对象类型
type PageInfo = Record<string, { title: string; url: string }>;
const pages: PageInfo = {
home: { title: 'Home', url: '/' },
about: { title: 'About', url: '/about' }
};工具类型详解
TypeScript 提供了一系列内置工具类型,可以极大地提高类型编程效率。
Partial<T>
将类型 T 的所有属性变为可选:
interface Config {
host: string;
port: number;
ssl: boolean;
}
// 实现原理
// type Partial<T> = { [P in keyof T]?: T[P] };
function createConfig(override?: Partial<Config>): Config {
return {
host: 'localhost',
port: 8080,
ssl: false,
...override
};
}Required<T>
将类型 T 的所有属性变为必需(移除所有可选标记):
interface Options {
debug?: boolean;
timeout?: number;
}
// 实现原理
// type Required<T> = { [P in keyof T]-?: T[P] };
const opts: Required<Options> = {
debug: true,
timeout: 5000
// 两个属性都必须提供
};Readonly<T>
将类型 T 的所有属性变为只读:
interface UserProfile {
id: number;
name: string;
}
// 实现原理
// type Readonly<T> = { readonly [P in keyof T]: T[P] };
type ReadonlyProfile = Readonly<UserProfile>;
const profile: ReadonlyProfile = { id: 1, name: 'Alice' };
// profile.id = 2; // 错误:id 是只读的Pick<T, K>
从类型 T 中选取一组属性 K 组成新类型:
interface Article {
id: number;
title: string;
content: string;
author: string;
createdAt: Date;
updatedAt: Date;
}
// 实现原理
// type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type ArticleSummary = Pick<Article, 'id' | 'title' | 'author'>;
// 在列表场景中非常有用
function getArticleSummary(articles: Article[]): ArticleSummary[] {
return articles.map(({ id, title, author }) => ({ id, title, author }));
}Omit<T, K>
从类型 T 中排除一组属性 K,得到剩余属性的类型:
// 实现原理
// type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type ArticleInput = Omit<Article, 'id' | 'createdAt' | 'updatedAt'>;
// 适用于创建不需要系统字段的输入类型
function createArticle(input: ArticleInput): Article {
return {
...input,
id: Date.now(),
createdAt: new Date(),
updatedAt: new Date()
};
}Record<K, T>
创建一个类型,其键为 K、值为 T:
// 实现原理
// type Record<K extends keyof any, T> = { [P in K]: T };
type Role = 'admin' | 'user' | 'guest';
type RolePermissions = Record<Role, string[]>;
const permissions: RolePermissions = {
admin: ['read', 'write', 'delete'],
user: ['read', 'write'],
guest: ['read']
};
// 枚举键的用法
enum HttpStatus {
OK = 200,
NotFound = 404,
Error = 500
}
type StatusMessages = Record<HttpStatus, string>;
const messages: StatusMessages = {
[HttpStatus.OK]: 'Success',
[HttpStatus.NotFound]: 'Not Found',
[HttpStatus.Error]: 'Internal Server Error'
};Exclude<T, U>
从联合类型 T 中排除可以赋值给 U 的类型:
// 实现原理
// type Exclude<T, U> = T extends U ? never : T;
type T0 = Exclude<'a' | 'b' | 'c', 'a'>;
// 结果:'b' | 'c'
type T1 = Exclude<string | number | boolean, boolean>;
// 结果:string | numberExtract<T, U>
从联合类型 T 中提取可以赋值给 U 的类型:
// 实现原理
// type Extract<T, U> = T extends U ? T : never;
type T0 = Extract<'a' | 'b' | 'c', 'a' | 'f'>;
// 结果:'a'
type T1 = Extract<string | number | boolean, boolean | number>;
// 结果:number | booleanNonNullable<T>
从类型 T 中排除 null 和 undefined:
// 实现原理
// type NonNullable<T> = T extends null | undefined ? never : T;
type T0 = NonNullable<string | number | null | undefined>;
// 结果:string | numberReturnType<T>
获取函数类型 T 的返回值类型:
// 实现原理
// type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
function fetchUser(id: number): Promise<User> {
return Promise.resolve({ id, name: 'Alice', email: 'alice@example.com' });
}
type FetchUserResult = ReturnType<typeof fetchUser>;
// 结果:Promise<User>
// 配合条件类型提取深层类型
type UnwrappedUser = FetchUserResult extends Promise<infer T> ? T : never;
// 结果:UserParameters<T>
获取函数类型 T 的参数类型(以元组形式):
// 实现原理
// type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
function log(format: string, ...args: unknown[]): void {
console.log(format, ...args);
}
type LogParams = Parameters<typeof log>;
// 结果:[format: string, ...args: unknown[]]
// 在函数包装中非常有用
function wrapFunction<T extends (...args: any[]) => any>(
fn: T,
...args: Parameters<T>
): ReturnType<T> {
console.log('Calling function with:', args);
return fn(...args);
}声明文件(Declaration Files)
声明文件(.d.ts)是 TypeScript 的类型声明来源,它们描述了 JavaScript 代码的类型信息,使得 TypeScript 可以在纯 JavaScript 代码上提供类型检查。
声明文件基础
// math.d.ts - 声明一个模块
declare module 'math-utils' {
export function add(a: number, b: number): number;
export function subtract(a: number, b: number): number;
export const PI: number;
}
// global.d.ts - 声明全局变量
declare const VERSION: string;
declare function logToServer(message: string): void;
// 声明全局类型
interface Window {
__APP_CONFIG__: {
apiUrl: string;
debug: boolean;
};
}@types 包
对于大多数流行的 JavaScript 库,社区已经维护了对应的类型声明包:
# 安装 React 的类型声明
npm install --save-dev @types/react @types/react-dom
# 安装 Node.js 的类型声明
npm install --save-dev @types/node
# 安装 Lodash 的类型声明
npm install --save-dev @types/lodash模块声明
当使用非 JavaScript/TypeScript 文件时,需要为其创建类型声明:
// 声明 CSS 模块
declare module '*.module.css' {
const classes: { [key: string]: string };
export default classes;
}
// 声明图片文件
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.svg' {
import React from 'react';
const SVGComponent: React.FC<React.SVGProps<SVGSVGElement>>;
export default SVGComponent;
}全局声明
在 TypeScript 中扩展现有类型或添加全局声明:
// global.d.ts
export {};
declare global {
interface String {
toCapitalCase(): string;
}
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
API_KEY: string;
}
}
}声明文件的最佳实践
- 始终在声明文件顶部使用
export {}或import来确保文件被视为模块 - 使用
declare关键字标记所有声明,不要包含实现 - 对于第三方库,优先使用
@types包而非手动编写声明 - 使用
/// <reference types="..." />指令引用其他类型声明 - 在
tsconfig.json中配置typeRoots和types字段控制类型加载
类型断言与类型守卫
类型断言(Type Assertions)
类型断言告诉 TypeScript 编译器某个值的具体类型,它在编译时被移除,不影响运行时行为:
// 尖括号语法(在 JSX 中不可用)
const someValue: unknown = 'hello world';
const strLength: number = (<string>someValue).length;
// as 语法(推荐)
const input = document.getElementById('input') as HTMLInputElement;
input.value = 'Hello';
// 非空断言
function processResult(result?: string | null): void {
const value = result!; // 断言 result 非空
console.log(value.toUpperCase());
}
// const 断言
const colors = ['red', 'green', 'blue'] as const;
// 类型为 readonly ['red', 'green', 'blue'],而非 string[]类型守卫(Type Guards)
类型守卫是运行时检查,帮助 TypeScript 在特定代码块中缩小类型范围:
// typeof 守卫
function printValue(value: string | number): void {
if (typeof value === 'string') {
// 此处 value 类型为 string
console.log(value.toUpperCase());
} else {
// 此处 value 类型为 number
console.log(value.toFixed(2));
}
}
// instanceof 守卫
class Dog { bark(): void { console.log('Woof!'); } }
class Cat { meow(): void { console.log('Meow!'); } }
function makeSound(animal: Dog | Cat): void {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.meow();
}
}
// 自定义类型守卫
interface Fish {
swim(): void;
layEggs(): void;
}
interface Bird {
fly(): void;
layEggs(): void;
}
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function getPetAction(pet: Fish | Bird): void {
if (isFish(pet)) {
pet.swim(); // TypeScript 知道此处 pet 是 Fish
} else {
pet.fly(); // TypeScript 知道此处 pet 是 Bird
}
}
// in 操作符守卫
interface AdminUser {
role: 'admin';
permissions: string[];
}
interface RegularUser {
role: 'user';
group: string;
}
function getUserInfo(user: AdminUser | RegularUser): string {
if ('permissions' in user) {
return `Admin with ${user.permissions.length} permissions`;
}
return `User in group ${user.group}`;
}
// 可辨识联合(Discriminated Unions)
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rectangle'; width: number; height: number }
| { kind: 'triangle'; base: number; height: number };
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'rectangle':
return shape.width * shape.height;
case 'triangle':
return (shape.base * shape.height) / 2;
default:
// exhaustive check
const _exhaustive: never = shape;
return _exhaustive;
}
}实用示例
示例一:类型安全的 API 客户端
// API 响应包装
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
interface ApiError {
code: number;
message: string;
details?: Record<string, string[]>;
}
// 条件类型处理成功/失败
type ApiResult<T> = ApiResponse<T> | ApiError;
// 泛型 API 请求函数
async function apiRequest<T>(
url: string,
options?: RequestInit
): Promise<ApiResponse<T>> {
const response = await fetch(url, options);
const data = await response.json();
if (!response.ok) {
throw data as ApiError;
}
return data as ApiResponse<T>;
}
// 具体类型定义
interface UserDTO {
id: number;
name: string;
email: string;
}
// 使用
async function getUser(id: number): Promise<UserDTO | null> {
try {
const response = await apiRequest<UserDTO>(`/api/users/${id}`);
return response.data;
} catch (error) {
const apiError = error as ApiError;
console.error(`API Error ${apiError.code}: ${apiError.message}`);
return null;
}
}示例二:类型安全的事件系统
// 事件映射类型
interface EventMap {
click: { x: number; y: number; target: EventTarget };
keydown: { key: string; ctrlKey: boolean; shiftKey: boolean };
focus: { element: HTMLElement };
custom: { payload: unknown };
}
// 泛型事件发射器
class TypedEventEmitter<T extends Record<string, unknown>> {
private listeners: Map<keyof T, Array<(data: T[keyof T]) => void>> = new Map();
on<K extends keyof T>(event: K, listener: (data: T[K]) => void): void {
const existing = this.listeners.get(event) || [];
existing.push(listener as (data: T[keyof T]) => void);
this.listeners.set(event, existing);
}
emit<K extends keyof T>(event: K, data: T[K]): void {
const listeners = this.listeners.get(event);
if (listeners) {
listeners.forEach(listener => listener(data));
}
}
off<K extends keyof T>(event: K, listener: (data: T[K]) => void): void {
const existing = this.listeners.get(event);
if (existing) {
this.listeners.set(
event,
existing.filter(l => l !== listener) as Array<(data: T[keyof T]) => void>
);
}
}
}
// 使用
const emitter = new TypedEventEmitter<EventMap>();
emitter.on('click', (data) => {
// data 类型为 { x: number; y: number; target: EventTarget }
console.log(`Clicked at (${data.x}, ${data.y})`);
});
emitter.emit('click', { x: 100, y: 200, target: document.body });示例三:泛型工厂模式
// 类型安全的工厂
interface Factory<T> {
create(...args: any[]): T;
reset(): void;
}
abstract class BaseFactory<T extends { id: number }> implements Factory<T> {
protected counter = 0;
abstract create(...args: any[]): T;
reset(): void {
this.counter = 0;
}
protected nextId(): number {
return ++this.counter;
}
}
// 具体工厂
interface Product {
id: number;
name: string;
price: number;
}
class ProductFactory extends BaseFactory<Product> {
create(name: string, price: number): Product {
return {
id: this.nextId(),
name,
price
};
}
}
const factory = new ProductFactory();
const product1 = factory.create('Widget', 19.99);
const product2 = factory.create('Gadget', 29.99);示例四:深度 Partial 类型
// 递归工具类型
type DeepPartial<T> = T extends object
? { [P in keyof T]?: DeepPartial<T[P]> }
: T;
interface NestedConfig {
server: {
host: string;
port: number;
ssl: {
enabled: boolean;
cert: string;
key: string;
};
};
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
}
// 允许只提供部分配置
function mergeConfig(defaults: NestedConfig, override: DeepPartial<NestedConfig>): NestedConfig {
function deepMerge<T>(target: T, source: DeepPartial<T>): T {
const result = { ...target };
for (const key in source) {
if (source[key] !== undefined && typeof source[key] === 'object') {
result[key] = deepMerge(target[key], source[key] as any);
} else if (source[key] !== undefined) {
result[key] = source[key] as any;
}
}
return result;
}
return deepMerge(defaults, override);
}总结
TypeScript 的类型系统功能强大且丰富,掌握这些基础知识后,你将能够:
- 写出更安全的代码 - 类型系统在编译时捕获大量常见错误
- 提高开发效率 - IDE 智能提示和自动补全让开发更高效
- 更好的代码文档 - 类型本身就是最好的文档
- 更轻松的重构 - 编译器会告诉你所有需要修改的地方
进一步学习建议:
- 阅读 TypeScript 官方文档(TypeScript Handbook)
- 学习 TypeScript 项目配置(
tsconfig.json各选项详解) - 深入条件类型、模板字面量类型和递归类型
- 实践中使用 TypeScript 重写现有 JavaScript 项目