TypeScript系列04-泛型编程

[复制链接]
发表于 2025-10-21 00:55:36 | 显示全部楼层 |阅读模式
本文探究TypeScript泛型编程,内容涵盖:

  • 泛型根本:包罗泛型函数、接口、类和束缚,这些是构建可重用和范例安全代码的根本。
  • 内置工具范例:把握了TypeScript提供的强大工具范例,如Partial、Required、Pick等,这些工具范例可以资助我们举行常见的范例操纵。
  • 条件范例与推断:学习了怎样使用条件范例和infer关键字举行范例运算和推断。
  • 实战应用:分析了Redux Toolkit中泛型的应用,展示了泛型在实际项目中的强大功能
1. 泛型根本概念

泛型是TypeScript中最强大的特性之一,它答应我们创建可重用的组件,这些组件可以与多种范例一起工作,而不但限于单一范例。泛型为代码提供了范例安全的同时保持了机动性。
1.1 泛型函数与泛型接口

泛型函数使用范例参数来创建可以处理处罚多种数据范例的函数,同时保持范例安全

以下是一个简单的泛型函数示例:
  1. function identity<T>(arg: T): T {
  2.     return arg;
  3. }
  4. // 使用方式
  5. const output1: string = identity<string>("hello");
  6. const output2: number = identity<number>(42);
  7. const output3: boolean = identity(true); // 类型参数推断为 boolean
复制代码
泛型接口使我们可以大概界说可实用于多种范例的接口结构:
  1. interface GenericBox<T> {
  2.     value: T;
  3.     getValue(): T;
  4. }
  5. // 实现泛型接口
  6. class StringBox implements GenericBox<string> {
  7.     value: string;
  8.    
  9.     constructor(value: string) {
  10.         this.value = value;
  11.     }
  12.    
  13.     getValue(): string {
  14.         return this.value;
  15.     }
  16. }
  17. class NumberBox implements GenericBox<number> {
  18.     value: number;
  19.    
  20.     constructor(value: number) {
  21.         this.value = value;
  22.     }
  23.    
  24.     getValue(): number {
  25.         return this.value;
  26.     }
  27. }
复制代码
1.2 泛型类与泛型束缚

泛型类答应我们创建可以处理处罚多种数据范例的类界说:
  1. class DataContainer<T> {
  2.     private data: T[];
  3.    
  4.     constructor() {
  5.         this.data = [];
  6.     }
  7.    
  8.     add(item: T): void {
  9.         this.data.push(item);
  10.     }
  11.    
  12.     getItems(): T[] {
  13.         return this.data;
  14.     }
  15. }
  16. // 使用泛型类
  17. const stringContainer = new DataContainer<string>();
  18. stringContainer.add("Hello");
  19. stringContainer.add("World");
  20. const strings = stringContainer.getItems(); // 类型为 string[]
  21. const numberContainer = new DataContainer<number>();
  22. numberContainer.add(10);
  23. numberContainer.add(20);
  24. const numbers = numberContainer.getItems(); // 类型为 number[]
复制代码
泛型束缚使我们可以限定范例参数必须具有特定属性或结构,进步范例安全性:
  1. interface Lengthwise {
  2.     length: number;
  3. }
  4. // 泛型约束:T 必须符合 Lengthwise 接口
  5. function getLength<T extends Lengthwise>(arg: T): number {
  6.     return arg.length; // 安全,因为我们保证 T 有 length 属性
  7. }
  8. getLength("Hello"); // 字符串有 length 属性,可以正常工作
  9. getLength([1, 2, 3]); // 数组有 length 属性,可以正常工作
  10. // getLength(123); // 错误!数字没有 length 属性
复制代码
1.3 默认范例参数

TypeScript 答应为泛型范例参数提供默认值,雷同于函数参数的默认值:
  1. interface ApiResponse<T = any> {
  2.     data: T;
  3.     status: number;
  4.     message: string;
  5. }
  6. // 没有指定类型参数,使用默认值 any
  7. const generalResponse: ApiResponse = {
  8.     data: "some data",
  9.     status: 200,
  10.     message: "Success"
  11. };
  12. // 明确指定类型参数
  13. const userResponse: ApiResponse<User> = {
  14.     data: { id: 1, name: "John Doe" },
  15.     status: 200,
  16.     message: "User retrieved successfully"
  17. };
  18. interface User {
  19.     id: number;
  20.     name: string;
  21. }
复制代码
2. 泛型工具范例详解

TypeScript 提供了很多内置的泛型工具范例,它们可以资助我们实验常见的范例转换。这些工具范例都是基于泛型构建的,展示了泛型的强大功能

2.1 Partial, Required, Readonly

这组工具范例紧张用于修改对象范例的属性特性:
  1. interface User {
  2.     id: number;
  3.     name: string;
  4.     email: string;
  5.     role: 'admin' | 'user';
  6.     createdAt: Date;
  7. }
  8. // Partial<T> - 将所有属性变为可选
  9. type PartialUser = Partial<User>;
  10. // 等同于:
  11. // {
  12. //   id?: number;
  13. //   name?: string;
  14. //   email?: string;
  15. //   role?: 'admin' | 'user';
  16. //   createdAt?: Date;
  17. // }
  18. // 更新用户时,我们只需要提供要更新的字段
  19. function updateUser(userId: number, userData: Partial<User>): Promise<User> {
  20.     // 实现省略
  21.     return Promise.resolve({} as User);
  22. }
  23. // Required<T> - 将所有可选属性变为必需
  24. interface PartialConfig {
  25.     host?: string;
  26.     port?: number;
  27.     protocol?: 'http' | 'https';
  28. }
  29. type CompleteConfig = Required<PartialConfig>;
  30. // 等同于:
  31. // {
  32. //   host: string;
  33. //   port: number;
  34. //   protocol: 'http' | 'https';
  35. // }
  36. // Readonly<T> - 将所有属性变为只读
  37. type ReadonlyUser = Readonly<User>;
  38. // 等同于:
  39. // {
  40. //   readonly id: number;
  41. //   readonly name: string;
  42. //   readonly email: string;
  43. //   readonly role: 'admin' | 'user';
  44. //   readonly createdAt: Date;
  45. // }
  46. const user: ReadonlyUser = {
  47.     id: 1,
  48.     name: "John Doe",
  49.     email: "john@example.com",
  50.     role: "user",
  51.     createdAt: new Date()
  52. };
  53. // 错误:无法分配到"name",因为它是只读属性
  54. // user.name = "Jane Doe";
复制代码
2.2 Record<K,T>, Pick<T,K>, Omit<T,K>

这组工具范例紧张用于构造或提取对象范例:
  1. // Record<K,T> - 创建一个具有类型 K 的键和类型 T 的值的对象类型
  2. type UserRoles = Record<string, 'admin' | 'editor' | 'viewer'>;
  3. // 等同于:
  4. // {
  5. //   [key: string]: 'admin' | 'editor' | 'viewer'
  6. // }
  7. const roles: UserRoles = {
  8.     'user1': 'admin',
  9.     'user2': 'editor',
  10.     'user3': 'viewer'
  11. };
  12. // 特别有用的情况:创建映射对象
  13. type UserIds = 'user1' | 'user2' | 'user3';
  14. const permissionsByUser: Record<UserIds, string[]> = {
  15.     user1: ['read', 'write', 'delete'],
  16.     user2: ['read', 'write'],
  17.     user3: ['read']
  18. };
  19. // Pick<T,K> - 从类型 T 中选择指定的属性 K
  20. type UserProfile = Pick<User, 'name' | 'email'>;
  21. // 等同于:
  22. // {
  23. //   name: string;
  24. //   email: string;
  25. // }
  26. // 非常适合生成表单或API相关的数据结构
  27. function getUserProfile(user: User): UserProfile {
  28.     return {
  29.         name: user.name,
  30.         email: user.email
  31.     };
  32. }
  33. // Omit<T,K> - 从类型 T 中排除指定的属性 K
  34. type UserWithoutSensitiveInfo = Omit<User, 'id' | 'createdAt'>;
  35. // 等同于:
  36. // {
  37. //   name: string;
  38. //   email: string;
  39. //   role: 'admin' | 'user';
  40. // }
  41. // 创建新用户输入表单,去除自动生成的字段
  42. function createUserFromForm(userData: UserWithoutSensitiveInfo): User {
  43.     return {
  44.         ...userData,
  45.         id: generateId(), // 假设的函数
  46.         createdAt: new Date()
  47.     };
  48. }
复制代码
2.3 Extract<T,U>, Exclude<T,U>, NonNullable

这组工具范例紧张用于团结范例的操纵:
  1. // 定义一些联合类型
  2. type Species = 'cat' | 'dog' | 'bird' | 'fish' | 'reptile';
  3. type Mammals = 'cat' | 'dog';
  4. // Extract<T,U> - 从 T 中提取可赋值给 U 的类型
  5. type MammalsFromSpecies = Extract<Species, Mammals>;
  6. // 结果: 'cat' | 'dog'
  7. // 更实用的例子
  8. type ApiResponse =
  9.     | { status: 'success'; data: any }
  10.     | { status: 'error'; error: string }
  11.     | { status: 'loading' };
  12. type SuccessResponse = Extract<ApiResponse, { status: 'success' }>;
  13. // 结果: { status: 'success'; data: any }
  14. // Exclude<T,U> - 从 T 中排除可赋值给 U 的类型
  15. type NonMammals = Exclude<Species, Mammals>;
  16. // 结果: 'bird' | 'fish' | 'reptile'
  17. // 排除所有错误状态
  18. type NonErrorResponses = Exclude<ApiResponse, { status: 'error' }>;
  19. // 结果: { status: 'success'; data: any } | { status: 'loading' }
  20. // NonNullable<T> - 从 T 中排除 null 和 undefined
  21. type MaybeString = string | null | undefined;
  22. type DefinitelyString = NonNullable<MaybeString>;
  23. // 结果: string
  24. // 使用场景:过滤数组中的非空值
  25. function filterNonNullable<T>(array: Array<T | null | undefined>): Array<NonNullable<T>> {
  26.     return array.filter((item): item is NonNullable<T> => item !== null && item !== undefined) as Array<NonNullable<T>>;
  27. }
  28. const mixedArray = ['hello', null, 'world', undefined, '!'];
  29. const filteredArray = filterNonNullable(mixedArray);
  30. // 结果: ['hello', 'world', '!']
复制代码
3. 条件范例与范例推断 - infer 关键字

条件范例是TypeScript中最强大的范例构造之一,它答应我们基于范例关系创建条件逻辑。

infer 关键字,它答应我们声明一个范例变量,用于捕捉和提取符合特定模式的范例。简单来说,它让我们可以大概从复杂范例中"提取"出我们关心的部门。
根本语法
  1. type ExtractSomething<T> = T extends Pattern_with_infer_X ? X : Fallback;
复制代码
在这个模式中:

  • T是我们要查抄的范例
  • Pattern_with_infer_X是包罗infer X声明的模式
  • 如果T符合该模式,结果范例就是我们提取出的X
  • 否则,结果范例为Fallback
简单示例:提取函数返回范例
  1. // 定义一个提取函数返回类型的工具类型
  2. type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : any;
  3. // 一个简单的函数
  4. function getUsername(): string {
  5.   return "张三";
  6. }
  7. // 提取函数的返回类型
  8. type Username = ReturnTypeOf<typeof getUsername>; // 结果: string
复制代码
infer的本质是举行模式匹配。就像我们辨认笔墨或图像一样,它根据预设的模式来找到并"捕捉"范例中的特定部门。
这就似乎我们看到"159****1234"如许的号码,立刻就能辨认出这是一个手机号,而且知道中央的星号部门是隐蔽的数字。infer在范例天下做的变乱与此雷同——它根据上下文模式自动推断出被省略或隐蔽的范例部门。
4. 案例:泛型在Redux Toolkit中的应用

Redux Toolkit是Redux的官方保举工具集,它大量使用了TypeScript的泛型来提供范例安全的状态管理。让我们看看它怎样使用泛型:

下面我们将看看Redux Toolkit中的泛型应用,并实现一个简单的TodoList应用:
  1. import {
  2.   createSlice,
  3.   createAsyncThunk,
  4.   PayloadAction,
  5.   configureStore
  6. } from '@reduxjs/toolkit';
  7. // 1. 定义类型
  8. interface Todo {
  9.   id: number;
  10.   text: string;
  11.   completed: boolean;
  12. }
  13. interface TodosState {
  14.   items: Todo[];
  15.   status: 'idle' | 'loading' | 'succeeded' | 'failed';
  16.   error: string | null;
  17. }
  18. // 2. 使用createAsyncThunk泛型
  19. // createAsyncThunk<返回值类型, 参数类型, { rejectValue: 错误类型 }>
  20. export const fetchTodos = createAsyncThunk
  21.   Todo[],
  22.   void,
  23.   { rejectValue: string }
  24. >('todos/fetchTodos', async (_, { rejectWithValue }) => {
  25.   try {
  26.     const response = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=10');
  27.     if (!response.ok) {
  28.       return rejectWithValue('Failed to fetch todos.');
  29.     }
  30.     return await response.json();
  31.   } catch (error) {
  32.     return rejectWithValue(error instanceof Error ? error.message : 'Unknown error');
  33.   }
  34. });
  35. // 3. 使用createSlice泛型来创建切片
  36. // createSlice<状态类型>
  37. const todosSlice = createSlice({
  38.   name: 'todos',
  39.   initialState: {
  40.     items: [],
  41.     status: 'idle',
  42.     error: null
  43.   } as TodosState,
  44.   reducers: {
  45.     // PayloadAction<载荷类型> 增强了action的类型安全
  46.     addTodo: (state, action: PayloadAction<string>) => {
  47.       const newTodo: Todo = {
  48.         id: Date.now(),
  49.         text: action.payload,
  50.         completed: false
  51.       };
  52.       state.items.push(newTodo);
  53.     },
  54.     toggleTodo: (state, action: PayloadAction<number>) => {
  55.       const todo = state.items.find(item => item.id === action.payload);
  56.       if (todo) {
  57.         todo.completed = !todo.completed;
  58.       }
  59.     },
  60.     removeTodo: (state, action: PayloadAction<number>) => {
  61.       state.items = state.items.filter(item => item.id !== action.payload);
  62.     }
  63.   },
  64.   extraReducers: (builder) => {
  65.     // 处理异步action状态
  66.     builder
  67.       .addCase(fetchTodos.pending, (state) => {
  68.         state.status = 'loading';
  69.       })
  70.       .addCase(fetchTodos.fulfilled, (state, action: PayloadAction<Todo[]>) => {
  71.         state.status = 'succeeded';
  72.         state.items = action.payload;
  73.       })
  74.       .addCase(fetchTodos.rejected, (state, action) => {
  75.         state.status = 'failed';
  76.         state.error = action.payload || 'Unknown error';
  77.       });
  78.   }
  79. });
  80. // 4. 导出actions
  81. export const { addTodo, toggleTodo, removeTodo } = todosSlice.actions;
  82. // 5. 配置store
  83. const store = configureStore({
  84.   reducer: {
  85.     todos: todosSlice.reducer
  86.   }
  87. });
  88. // 6. 从store中提取RootState和AppDispatch类型
  89. export type RootState = ReturnType<typeof store.getState>;
  90. export type AppDispatch = typeof store.dispatch;
  91. // 7. 强类型的Hooks
  92. import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
  93. // 为useDispatch和useSelector创建强类型的版本
  94. export const useAppDispatch = () => useDispatch<AppDispatch>();
  95. export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
  96. // 8. 在组件中使用
  97. import React, { useEffect, useState } from 'react';
  98. import { useAppDispatch, useAppSelector } from './store';
  99. import { addTodo, toggleTodo, removeTodo, fetchTodos } from './todosSlice';
  100. const TodoApp: React.FC = () => {
  101.   const [newTodo, setNewTodo] = useState('');
  102.   const dispatch = useAppDispatch();
  103.   
  104.   // 强类型的selector,IDE可以提供自动完成
  105.   const { todos, status, error} = useAppSelector(state => state.todos);
  106.   
  107.   useEffect(() => {
  108.     if (status === 'idle') {
  109.       dispatch(fetchTodos());
  110.     }
  111.   }, [status, dispatch]);
  112.   
  113.   const handleAddTodo = (e: React.FormEvent) => {
  114.     e.preventDefault();
  115.     if (newTodo.trim()) {
  116.       dispatch(addTodo(newTodo));
  117.       setNewTodo('');
  118.     }
  119.   };
  120.   
  121.   if (status === 'loading') {
  122.     return <div>Loading...</div>;
  123.   }
  124.   
  125.   if (status === 'failed') {
  126.     return <div>Error: {error}</div>;
  127.   }
  128.   
  129.   return (
  130.     <div>
  131.       <h1>Todo List</h1>
  132.       
  133.       <form onSubmit={handleAddTodo}>
  134.         <input
  135.           type="text"
  136.           value={newTodo}
  137.           onChange={(e) => setNewTodo(e.target.value)}
  138.           placeholder="Add a new todo"
  139.         />
  140.         <button type="submit">Add</button>
  141.       </form>
  142.       
  143.       <ul>
  144.         {todos.map(todo => (
  145.           <li
  146.             key={todo.id}
  147.             style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
  148.           >
  149.             <span onClick={() => dispatch(toggleTodo(todo.id))}>
  150.               {todo.text}
  151.             </span>
  152.             <button onClick={() => dispatch(removeTodo(todo.id))}>
  153.               Delete
  154.             </button>
  155.           </li>
  156.         ))}
  157.       </ul>
  158.     </div>
  159.   );
  160. };
  161. export default TodoApp;
复制代码
Redux Toolkit中的泛型带来的长处:

  • 范例安全的Actions:通过PayloadAction<T>泛型,确保了action的载荷范例精确。
  • 范例安全的Thunks:createAsyncThunk<返回值范例, 参数范例, 选项>泛型确保了异步操纵的范例安全。
  • 范例安全的State访问:通过RootState范例和强范例的selector hooks,确保了状态的范例安全访问。
  • 智能的自动完成:由于范例体系的存在,IDE可以提供更好的自动完乐成能。
  • 编译时错误查抄:错误在编译时而非运行时被捕捉。
这些高级泛型技能使Redux Toolkit可以大概提供精良的开辟体验,尤其在大型应用中尤为紧张。
总结

泛型是TypeScript最强大的特性之一,把握泛型可以资助我们写出更机动、更可重用、更范例安全的代码。随着TypeScript的不停发展,泛型的应用将变得越来越广泛和紧张。通过深入明确泛型,我们可以充实使用TypeScript的范例体系,进步代码质量和开辟服从。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

×
登录参与点评抽奖,加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表