TypeScript 进阶指南 - 使用泛型与keyof约束参数

在这里插入图片描述

古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
个人CSND主页——Micro麦可乐的博客
《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

TypeScript 进阶指南 - 使用泛型与 keyof 约束参数

  • 1. 前言
  • 2. 基础概念回顾
    • 泛型
    • keyof 操作符
  • 3. 结合泛型与 keyof 实现安全访问
  • 4. 封装修改属性值
  • 5. 综合示例:一个通用数据处理类
  • 6. 实用工具类型
    • ❶ 条件类型过滤
    • ❷ 类型安全表单验证
  • 7. 总结

1. 前言

TypeScript 中,泛型让你能够编写通用、高复用的代码,而 keyof 操作符则可以在编译期间获取某个对象的属性名列表。将两者结合起来,你可以轻松地实现对对象属性的安全访问、更新等操作,并在编译期间捕获许多潜在的错误。

TypeScript 进阶指南 - 使用泛型与keyof约束参数_第1张图片
那么今天博主就来和小伙伴们详细讲解如何使用泛型keyof 来约束参数,并通过完整的代码示例让大家快速掌握~


2. 基础概念回顾

在讲解之前我们先来回顾一下泛型keyof操作符的基础使用

泛型

泛型可以让函数、类或接口在定义时不指定具体的数据类型,而在使用时再传入相应的类型参数。这样能使代码更具灵活性,同时也能保证类型安全。例如,一个常见的泛型函数如下:

function identity<T>(arg: T): T {
  return arg;
}

const num = identity<number>(123); // 返回值类型为 number
const str = identity<string>('hello'); // 返回值类型为 string

keyof 操作符

keyof 操作符用于提取某个对象的所有键,生成一个联合类型。例如:

interface Person {
  name: string;
  age: number;
}

type PersonKeys = keyof Person; // 相当于 "name" | "age"

这样可以在编译期间确保对对象属性的访问不会出现拼写错误或者访问不存在的属性。


3. 结合泛型与 keyof 实现安全访问

在讲解 泛型keyof 结合前,我们先看如下代码:

// 基础示例:存在类型隐患的写法
function getProperty(obj: any, key: string) {
  return obj[key]; // 无法保证key存在于obj中
}

上述方法中 obj[key] ,无法保证key存在于obj中

上述场景是,定义一个函数来获取对象中某个属性的值。直接编写这样的函数在运行期间如果出现错误可能会导致问题,而利用 TypeScript泛型 结合 keyof,可以在编译期就做出约束,避免访问不存在的属性。

例如,我们可以将 getProperty 函数改造如下:

/**
 * 根据传入的 key,从对象中获取相应属性的值。
 * @param obj - 目标对象
 * @param key - 对象的键,该键必须是 obj 的属性之一
 */
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

// 示例接口
interface Person {
  name: string;
  age: number;
  city: string;
}

// 测试数据
const person: Person = {
  name: 'Alice',
  age: 25,
  city: 'Shanghai'
};

// 正确访问:编译正常
console.log(getProperty(person, 'name')); // 输出 "Alice"
console.log(getProperty(person, 'age'));  // 输出 25

// 错误示例:编译期间将报错
// console.log(getProperty(person, 'gender')); // 编译错误:"'gender'" 不存在于类型 "Person" 中

通过这个简单示例,我们可以看到 泛型keyof 的结合如何在编译期间确保访问对象属性时的类型安全性。


4. 封装修改属性值

除了上述读取属性值,我们还可以使用同样的方式来封装修改属性值的操作。下面定义一个 setProperty 函数,让我们能够用类型安全的方式更新对象的某个属性值:

/**
 * 根据传入的 key 和 value,更新对象中对应的属性值。
 * @param obj - 目标对象
 * @param key - 对象的键,该键必须是 obj 的属性之一
 * @param value - 要设置的新值,必须与属性类型一致
 */
function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
  obj[key] = value;
}

// 修改属性值
setProperty(person, 'city', 'Beijing');
console.log(person.city); // 输出 "Beijing"

这种方式同样保证了你只能修改对象中已存在的属性,并且传入的值必须和属性的类型相匹配。如果尝试修改不存在的属性或者传入错误类型的值,都会在编译期间抛出错误。


5. 综合示例:一个通用数据处理类

为了更好地展示 泛型keyof 的实际应用,下面以一个简单的类为例,该类可以动态地获取和设置对象的属性值,并在内部记录操作历史记录。

interface HistoryRecord<T> {
  key: keyof T;
  oldValue: any;
  newValue: any;
  timestamp: Date;
}

class DataStore<T extends object> {
  private data: T;
  private history: HistoryRecord<T>[] = [];

  constructor(initialData: T) {
    this.data = initialData;
  }

  // 获取属性值
  public get<K extends keyof T>(key: K): T[K] {
    return this.data[key];
  }

  // 设置属性值,同时记录变更历史
  public set<K extends keyof T>(key: K, value: T[K]): void {
    const oldValue = this.data[key];
    this.data[key] = value;
    this.history.push({
      key,
      oldValue,
      newValue: value,
      timestamp: new Date(),
    });
  }

  // 获取变更历史
  public getHistory(): HistoryRecord<T>[] {
    return this.history;
  }
}

// 使用示例
interface Product {
  id: number;
  name: string;
  price: number;
}

const product: Product = {
  id: 101,
  name: '笔记本电脑',
  price: 5000,
};

const store = new DataStore<Product>(product);

// 读取属性
console.log(store.get('name')); // 输出 "笔记本电脑"

// 更新属性
store.set('price', 4800);
console.log(store.get('price')); // 输出 4800

// 尝试访问非法属性(将在编译期间报错)
// store.set('description', '高性能笔记本'); // Error: 类型 “Product” 中不存在属性 “description”

// 获取操作历史
console.log(store.getHistory());

在这个示例中,我们创建了一个 DataStore 类,利用 泛型keyof 将数据访问和更新的方式做了严格约束。这样不仅保证了调用时的类型安全,还能记录每次属性变更的详细历史记录,便于后续的操作追踪和调试


6. 实用工具类型

下面博主再给大家例举两个实用工具类型

❶ 条件类型过滤

type StringKeys<T> = {
  [K in keyof T]: T[K] extends string ? K : never
}[keyof T];

function getStringValue<T>(obj: T, key: StringKeys<T>): string {
  return obj[key] as string;
}

// 测试数据
const person: Person = {
  name: 'Alice',
  age: 25,
  city: 'Shanghai'
};

// 自动过滤非字符串属性
getStringValue(person, 'name'); // 正确
getStringValue(person, 'age'); // 错误 类型错误 int

❷ 类型安全表单验证

interface ValidationRule<T> {
  field: keyof T;
  validate: (value: T[keyof T]) => boolean;
}

function createValidator<T>(rules: ValidationRule<T>[]) {
  return (obj: T) => 
    rules.every(rule => rule.validate(obj[rule.field]));
}

// 测试数据
const person: Person = {
  name: 'Alice',
  age: 25,
  city: 'Shanghai'
};

// 使用示例
const userValidator = createValidator<Person>([
  { 
    field: 'name', 
    validate: v => v.length >= 3 
  },
  { 
    field: 'age',
    validate: v => v >= 18 
  }
]);

const isValid = userValidator(person); // 自动类型检查

7. 总结

通过本篇文章的示例,小伙伴们可以看到 TypeScript泛型 keyof 操作符为编写类型安全且灵活的代码提供了强有力的支持。

泛型 允许代码在不牺牲类型安全性的前提下复用逻辑
keyof 则帮助我们限制传入的属性名只能是对象中定义的属性,从而在编译期避免错误

通过泛型与keyof的结合,我们可以实现:

  • 编译时类型安全检查
  • 智能代码提示
  • 接口契约自动维护
  • 重构友好性提升

通过合理运用这些类型技巧,可以显著提升TypeScript 项目的代码质量和开发体验,在保证类型安全的同时保持代码灵活性,希望这篇文章对小伙伴们理解和使用 泛型keyof 约束参数有所帮助,欢迎在项目中尝试并不断探索更多 TypeScript 的高级用法!

如果本本章内容对您有所帮助,希望 一键三连 给博主一点点鼓励,如果您有任何疑问或建议,请随时留言讨论!

前端技术专栏回顾:

01【前端技术】 ES6 介绍及常用语法说明
02【前端技术】标签页通讯localStorage、BroadcastChannel、SharedWorker的技术详解
03 前端请求乱序问题分析与AbortController、async/await、Promise.all等解决方案
04 前端开发中深拷贝的循环引用问题:从问题复现到完美解决
05 前端AJAX请求上传下载进度监控指南详解与完整代码示例


在这里插入图片描述

你可能感兴趣的:(前端技术,typescript,javascript,前端,泛型,keyof,参数约束)