教程雨

OKX新手入门教程导航,收录OKX注册、充值、买币、提现等基础操作教程

TypeScript前端开发进阶教程封面 - 电脑屏幕显示TypeScript代码与学习场景

TypeScript进阶教程:泛型+装饰器+工具类型完全指南

一、为什么TypeScript进阶能力这么重要

先说个我自己的经历。

三年前我去面试一家中型公司的前端岗位,面试官问了我一个问题:”你能手写一个DeepPartial工具类型吗?”我当时愣在原地,只能承认自己只会用、不会写。结果那轮面试就没后文了。

后来我认真研究了一下,发现很多看起来”高深”的TypeScript技巧,其实都是纸老虎。关键是你愿不愿意花时间去理解它的底层逻辑。

TypeScript代码示例配图 - 类型定义提示框与代码编写界面特写

为什么企业现在这么看重TypeScript能力?

  1. 代码质量需求升级:项目大了之后,any满天飞的代码根本没法维护
  2. 类型即文档:好的类型定义比任何注释都清晰
  3. 重构信心:强类型保障让你敢改代码
  4. 团队协作:类型是团队成员之间的”无声契约”

根据2026年前端技术调研报告,85%以上的中高级前端岗位要求候选人对TypeScript有深入理解。如果你还停留在”会用”的阶段,是时候往”精通”迈进了。

二、泛型:TypeScript最强大的武器

2.1 泛型基础:为什么需要泛型

先看一个实际的例子:

typescript

// 没有泛型:只能处理单一类型
function getFirst(arr: number[]): number {
  return arr[0];
}

// 换个类型又要写一遍?
function getFirstString(arr: string[]): string {
  return arr[0];
}

这种写法的问题是:代码重复,而且失去了类型灵活性。

泛型登场:

typescript

function getFirst<T>(arr: T[]): T {
  return arr[0];
}

// 使用时自动推断类型
const num = getFirst([1, 2, 3]);        // number
const str = getFirst(['a', 'b', 'c']); // string

// 也可以显式指定
const bool = getFirst<boolean>([true, false]);

这里的<T>就是泛型参数,T就像一个”待填充的占位符”,在实际调用时才会被具体类型替代。

2.2 泛型约束:给T加上限制

有时候我们希望T有某些属性,这时候就需要泛型约束:

typescript

// 希望T有length属性
interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(arg: T): void {
  console.log(arg.length);
}

logLength("hello");      // 字符串有length ✓
logLength([1, 2, 3]);   // 数组有length ✓
logLength({ length: 10 }); // 对象字面量有length ✓
// logLength(123);       // 报错:数字没有length ✗

2.3 多泛型参数

函数可以有多个泛型参数:

typescript

// 交换二元组的位置
function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]];
}

const swapped = swap(["hello", 42]); // [42, "hello"]

2.4 泛型接口与类

泛型不仅可以用在函数上,还可以用在接口和类上:

typescript

// 泛型接口
interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

// 使用时指定具体类型
interface User {
  id: number;
  name: string;
}

const response: ApiResponse<User> = {
  code: 0,
  message: 'success',
  data: {
    id: 1,
    name: '张三'
  }
};

// 泛型类
class Queue<T> {
  private items: T[] = [];

  enqueue(item: T): void {
    this.items.push(item);
  }

  dequeue(): T | undefined {
    return this.items.shift();
  }
}

const numberQueue = new Queue<number>();
numberQueue.enqueue(1);
numberQueue.enqueue(2);

2.5 泛型在实际项目中的应用

案例1:统一API请求函数

typescript

// 定义API响应的通用结构
interface ApiResult<T> {
  success: boolean;
  data?: T;
  error?: string;
}

// 封装请求函数
async function fetchApi<T>(
  url: string, 
  options?: RequestInit
): Promise<ApiResult<T>> {
  try {
    const response = await fetch(url, options);
    const data = await response.json();
    return { success: true, data: data as T };
  } catch (error) {
    return { success: false, error: String(error) };
  }
}

// 使用时
interface Article {
  id: number;
  title: string;
  content: string;
}

const result = await fetchApi<Article>('/api/article/1');
if (result.success && result.data) {
  console.log(result.data.title);
}

案例2:表单验证器

typescript

interface ValidationRule<T> {
  validate: (value: T) => boolean;
  message: string;
}

class FormValidator<T extends Record<string, any>> {
  private rules: Record<keyof T, ValidationRule<any>[]> = {} as Record<keyof T, ValidationRule<any>[]>;

  addRule<K extends keyof T>(field: K, rule: ValidationRule<T[K]>): this {
    if (!this.rules[field]) {
      this.rules[field] = [];
    }
    this.rules[field].push(rule);
    return this;
  }

  validate(data: T): Record<keyof T, string | null> {
    const errors = {} as Record<keyof T, string | null>;
    
    for (const field in this.rules) {
      for (const rule of this.rules[field]) {
        if (!rule.validate(data[field])) {
          errors[field] = rule.message;
          break;
        }
      }
    }
    
    return errors;
  }
}

// 使用示例
const validator = new FormValidator<{ email: string; password: string }>()
  .addRule('email', {
    validate: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
    message: '请输入有效的邮箱地址'
  })
  .addRule('password', {
    validate: (v) => v.length >= 8,
    message: '密码至少8位'
  });

三、装饰器:TypeScript的魔法棒

3.1 什么是装饰器

装饰器是ES7草案中的一项实验性特性(TypeScript默认关闭,需要在tsconfig中启用),它允许你在不修改类本身的情况下,给类或类的成员添加额外的行为。

装饰器开启配置:

json

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

3.2 类装饰器

类装饰器在类定义之前执行,可以用来修改类的构造函数或添加属性:

typescript

// 简单日志装饰器
function logClass(target: any) {
  console.log(`类 ${target.name} 被定义了`);
  return class extends target {
    newProperty = "新增的属性";
  };
}

@logClass
class User {
  name: string;
  
  constructor(name: string) {
    this.name = name;
  }
  
  greet() {
    return `你好,我是${this.name}`;
  }
}

const user = new User("张三");
console.log(user.newProperty); // "新增的属性"

3.3 方法装饰器

方法装饰器可以修改方法的行为,常用于日志记录、权限校验等:

typescript

// 方法装饰器:记录方法调用
function logMethod(
  target: any, 
  propertyKey: string, 
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    console.log(`调用 ${propertyKey},参数:`, args);
    const result = originalMethod.apply(this, args);
    console.log(`${propertyKey} 返回:`, result);
    return result;
  };
  
  return descriptor;
}

class Calculator {
  @logMethod
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(1, 2);
// 输出:
// 调用 add,参数: [1, 2]
// add 返回: 3

3.4 属性装饰器

typescript

// 属性装饰器:自动绑定this
function autobind(
  target: any, 
  propertyKey: string, 
  descriptor: PropertyDescriptor
) {
  const original = descriptor.get;
  descriptor.get = function() {
    const bound = original.bind(this);
    return bound;
  };
  return descriptor;
}

class Counter {
  count = 0;
  
  @autobind
  increment() {
    this.count++;
    return this.count;
  }
}

const counter = new Counter();
const fn = counter.increment; // 提取方法
console.log(fn()); // 1,而不是NaN(this丢失的问题解决了)

3.5 实战:实现一个权限验证装饰器

typescript

// 定义用户角色类型
type Role = 'admin' | 'user' | 'guest';

// 存储每个方法需要的角色
const ROLES_KEY = Symbol('roles');

// 角色要求装饰器
function requireRole(...roles: Role[]) {
  return function(
    target: any, 
    propertyKey: string, 
    descriptor: PropertyDescriptor
  ) {
    Reflect.defineMetadata(ROLES_KEY, roles, target, propertyKey);
    return descriptor;
  };
}

// 模拟当前用户
const currentUser = { role: 'user' as Role };

// 权限检查装饰器
function checkPermission(
  target: any, 
  propertyKey: string, 
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    const requiredRoles = Reflect.getMetadata(ROLES_KEY, target, propertyKey);
    
    if (requiredRoles && !requiredRoles.includes(currentUser.role)) {
      throw new Error(`需要 ${requiredRoles.join(' 或 ')} 权限`);
    }
    
    return original.apply(this, args);
  };
  
  return descriptor;
}

// 使用示例
class AdminPanel {
  @requireRole('admin')
  @checkPermission
  deleteUser(userId: number): string {
    return `用户 ${userId} 已删除`;
  }
  
  @requireRole('admin', 'user')
  @checkPermission
  editUser(userId: number): string {
    return `用户 ${userId} 已编辑`;
  }
}

const admin = new AdminPanel();

try {
  admin.deleteUser(123); // 抛出错误:需要 admin 权限
} catch (e) {
  console.log(e.message);
}

try {
  console.log(admin.editUser(123)); // 成功:用户 123 已编辑
} catch (e) {
  console.log(e.message);
}

3.6 装饰器执行顺序

当一个类有多个装饰器时,执行顺序如下:

typescript

// 从上到下读取装饰器,从下到上执行

function first() {
  console.log("first(): factory");
  return function(
    target: any, 
    propertyKey: string, 
    descriptor: PropertyDescriptor
  ) {
    console.log("first(): called");
  };
}

function second() {
  console.log("second(): factory");
  return function(
    target: any, 
    propertyKey: string, 
    descriptor: PropertyDescriptor
  ) {
    console.log("second(): called");
  };
}

class Example {
  @first()
  @second()
  method() {}
}

// 输出顺序:
// first(): factory
// second(): factory
// second(): called
// first(): called

四、工具类型:TypeScript内置的瑞士军刀

TypeScript内置了很多实用的工具类型,熟练使用它们可以让你的代码更简洁、类型更安全。

4.1 Partial<T> – 将所有属性变为可选

typescript

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

// 用于更新用户信息时,不需要全部字段
type UserUpdate = Partial<User>;

// 这样写是合法的
const update: UserUpdate = { name: '李四' }; // 只更新name

4.2 Required<T> – 将所有属性变为必需

typescript

interface User {
  id?: number;
  name?: string;
}

// 强制所有属性必须存在
type FullUser = Required<User>;

// 这样写会报错
const user: FullUser = { name: '张三' }; // 报错:缺少id

4.3 Pick<T, K> – 从T中选取部分属性

typescript

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  createdAt: Date;
}

// 只选取用于显示的属性
type UserPreview = Pick<User, 'id' | 'name'>;
// 等价于 { id: number; name: string; }

4.4 Omit<T, K> – 从T中排除部分属性

typescript

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

// 创建用户时不需要id(数据库自动生成)
type CreateUserInput = Omit<User, 'id'>;
// 等价于 { name: string; email: string; password: string; }

4.5 Record<K, V> – 创建键值对类型

typescript

// 状态到颜色的映射
type Status = 'success' | 'error' | 'loading';

const statusColors: Record<Status, string> = {
  success: 'green',
  error: 'red',
  loading: 'blue'
};

// 接口键值对
type PageName = 'home' | 'about' | 'contact';

interface PageConfig {
  title: string;
  path: string;
}

const pages: Record<PageName, PageConfig> = {
  home: { title: '首页', path: '/' },
  about: { title: '关于我们', path: '/about' },
  contact: { title: '联系我们', path: '/contact' }
};

4.6 Exclude<T, U> 和 Extract<T, U>

typescript

// Exclude:从T中排除可以赋值给U的类型
type T0 = Exclude<'a' | 'b' | 'c', 'a' | 'b'>; // 'c'
type T1 = Exclude<number | string | boolean, boolean>; // number | string

// Extract:从T中提取可以赋值给U的类型
type T2 = Extract<'a' | 'b' | 'c', 'a' | 'b'>; // 'a' | 'b'
type T3 = Extract<number | string | boolean, string>; // string

4.7 ReturnType<T> 和 Parameters<T>

typescript

function createUser(name: string, age: number) {
  return { name, age, id: Math.random() };
}

// 获取函数返回类型
type User = ReturnType<typeof createUser>;
// 等价于 { name: string; age: number; id: number; }

// 获取函数参数类型
type CreateUserParams = Parameters<typeof createUser>;
// 等价于 [name: string, age: number]

// 获取第一个参数
type FirstParam = Parameters<typeof createUser>[0]; // string

4.8 自定义高级工具类型

DeepPartial – 深度可选

typescript

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

interface Config {
  database: {
    host: string;
    port: number;
    user: {
      name: string;
      password: string;
    };
  };
}

// 所有嵌套属性都变成可选
type PartialConfig = DeepPartial<Config>;

DeepReadonly – 深度只读

typescript

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

// 所有嵌套属性都变成只读
type FrozenConfig = DeepReadonly<Config>;

五、综合实战:手写一个完整的类型安全的API客户端

学完上面的知识,我们来做一个大一点的实战:手写一个类型安全的API客户端。

typescript

// ==================== 类型定义 ====================

// HTTP方法类型
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';

// API路由定义
interface RouteDefinition<Path extends string, Response, Body = never> {
  method: HttpMethod;
  path: Path;
  response: Response;
  body?: Body;
}

// 定义API路由
interface ApiRoutes {
  // 获取文章列表
  '/articles': RouteDefinition<
    '/articles',
    { articles: Article[]; total: number }
  >;
  
  // 获取单篇文章
  '/articles/:id': RouteDefinition<
    '/articles/:id',
    Article
  >;
  
  // 创建文章
  '/articles': RouteDefinition<
    '/articles',
    Article,
    { title: string; content: string; authorId: number }
  >;
  
  // 更新文章
  '/articles/:id': RouteDefinition<
    '/articles/:id',
    Article,
    Partial<{ title: string; content: string }>
  >;
  
  // 删除文章
  '/articles/:id': RouteDefinition<
    '/articles/:id',
    { success: boolean }
  >;
}

// 文章接口
interface Article {
  id: number;
  title: string;
  content: string;
  authorId: number;
  createdAt: string;
  updatedAt: string;
}

// ==================== API客户端 ====================

type RouteParams<T extends string> = 
  T extends `${string}:${infer Param}/${infer Rest}` 
    ? Param | RouteParams<`/${Rest}`>
    : T extends `${string}:${infer Param}`
    ? Param
    : never;

type PathParams<T extends string> = 
  RouteParams<T> extends never 
    ? {} 
    : { [K in RouteParams<T>]: string | number };

type RequestBody<R extends RouteDefinition<any, any, any>> = 
  R['body'] extends never ? {} : { body: R['body'] };

// API客户端类
class ApiClient {
  private baseUrl: string;
  private defaultHeaders: Record<string, string>;

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
    this.defaultHeaders = {
      'Content-Type': 'application/json'
    };
  }

  setAuthToken(token: string): void {
    this.defaultHeaders['Authorization'] = `Bearer ${token}`;
  }

  async request<
    Path extends keyof ApiRoutes,
    Method extends ApiRoutes[Path]['method']
  >(
    path: Path,
    options: {
      method?: Method;
      params?: PathParams<Path>;
      body?: Method extends 'GET' ? never : ApiRoutes[Path]['body'];
    } = {}
  ): Promise<ApiRoutes[Path]['response']> {
    // 构建URL
    let url = this.baseUrl + path;
    
    if (options.params) {
      Object.entries(options.params).forEach(([key, value]) => {
        url = url.replace(`:${key}`, String(value));
      });
    }

    // 构建请求配置
    const config: RequestInit = {
      method: options.method || 'GET',
      headers: { ...this.defaultHeaders }
    };

    if (options.body) {
      config.body = JSON.stringify(options.body);
    }

    // 发送请求
    const response = await fetch(url, config);
    
    if (!response.ok) {
      throw new Error(`API Error: ${response.status}`);
    }

    return response.json();
  }
}

// ==================== 使用示例 ====================

const api = new ApiClient('https://api.example.com');
api.setAuthToken('your-token-here');

// GET请求
async function getArticles() {
  const result = await api.request('/articles');
  // result 的类型自动推断为 { articles: Article[]; total: number }
  console.log(`共 ${result.total} 篇文章`);
  return result.articles;
}

// GET带参数
async function getArticle(id: number) {
  const article = await api.request('/articles/:id', {
    params: { id }
  });
  // article 的类型自动推断为 Article
  return article;
}

// POST请求
async function createArticle(title: string, content: string) {
  const newArticle = await api.request('/articles', {
    method: 'POST',
    body: { title, content, authorId: 1 }
  });
  // newArticle 的类型自动推断为 Article
  return newArticle;
}

// PUT请求
async function updateArticle(id: number, updates: { title?: string }) {
  const updated = await api.request('/articles/:id', {
    method: 'PUT',
    params: { id },
    body: updates
  });
  return updated;
}

// DELETE请求
async function deleteArticle(id: number) {
  const result = await api.request('/articles/:id', {
    method: 'DELETE',
    params: { id }
  });
  return result;
}

这个API客户端的优势:

  1. 完全类型安全:所有请求参数和响应都有类型检查
  2. IDE智能提示:输入路径时会自动提示可用的路由
  3. 参数自动提取:id这类路径参数会被正确提取
  4. 方法限制:GET请求不能传body,编辑器会报错
  5. 响应类型推断:返回值类型自动匹配

六、总结与学习建议

TypeScript的进阶内容确实比基础要复杂一些,但只要理解了背后的逻辑,你会发现它其实很有规律:

  1. 泛型:让代码更灵活,同时保持类型安全
  2. 装饰器:用声明式的方式给代码添加行为
  3. 工具类型:TypeScript自带的类型操作瑞士军刀

下一步学习建议:

  • 阅读TypeScript官方文档中的高级类型章节
  • 学习一些成熟的TypeScript项目源码
  • 尝试手写几个工具类型练手
  • 了解TypeScript编译器选项对类型的影响
  • 关注TypeScript的新特性(如5.0+的装饰器标准)

记住,写好TypeScript不是一蹴而就的事。多写、多踩坑、多思考,慢慢你就能写出既优雅又安全的代码了。

推荐资源:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注