本文探究TypeScript泛型编程,内容涵盖:
- 泛型根本:包罗泛型函数、接口、类和束缚,这些是构建可重用和范例安全代码的根本。
- 内置工具范例:把握了TypeScript提供的强大工具范例,如Partial、Required、Pick等,这些工具范例可以资助我们举行常见的范例操纵。
- 条件范例与推断:学习了怎样使用条件范例和infer关键字举行范例运算和推断。
- 实战应用:分析了Redux Toolkit中泛型的应用,展示了泛型在实际项目中的强大功能。
1. 泛型根本概念
泛型是TypeScript中最强大的特性之一,它答应我们创建可重用的组件,这些组件可以与多种范例一起工作,而不但限于单一范例。泛型为代码提供了范例安全的同时保持了机动性。
1.1 泛型函数与泛型接口
泛型函数使用范例参数来创建可以处理处罚多种数据范例的函数,同时保持范例安全。
以下是一个简单的泛型函数示例:- function identity<T>(arg: T): T {
- return arg;
- }
- // 使用方式
- const output1: string = identity<string>("hello");
- const output2: number = identity<number>(42);
- const output3: boolean = identity(true); // 类型参数推断为 boolean
复制代码 泛型接口使我们可以大概界说可实用于多种范例的接口结构:- interface GenericBox<T> {
- value: T;
- getValue(): T;
- }
- // 实现泛型接口
- class StringBox implements GenericBox<string> {
- value: string;
-
- constructor(value: string) {
- this.value = value;
- }
-
- getValue(): string {
- return this.value;
- }
- }
- class NumberBox implements GenericBox<number> {
- value: number;
-
- constructor(value: number) {
- this.value = value;
- }
-
- getValue(): number {
- return this.value;
- }
- }
复制代码 1.2 泛型类与泛型束缚
泛型类答应我们创建可以处理处罚多种数据范例的类界说:- class DataContainer<T> {
- private data: T[];
-
- constructor() {
- this.data = [];
- }
-
- add(item: T): void {
- this.data.push(item);
- }
-
- getItems(): T[] {
- return this.data;
- }
- }
- // 使用泛型类
- const stringContainer = new DataContainer<string>();
- stringContainer.add("Hello");
- stringContainer.add("World");
- const strings = stringContainer.getItems(); // 类型为 string[]
- const numberContainer = new DataContainer<number>();
- numberContainer.add(10);
- numberContainer.add(20);
- const numbers = numberContainer.getItems(); // 类型为 number[]
复制代码 泛型束缚使我们可以限定范例参数必须具有特定属性或结构,进步范例安全性:- interface Lengthwise {
- length: number;
- }
- // 泛型约束:T 必须符合 Lengthwise 接口
- function getLength<T extends Lengthwise>(arg: T): number {
- return arg.length; // 安全,因为我们保证 T 有 length 属性
- }
- getLength("Hello"); // 字符串有 length 属性,可以正常工作
- getLength([1, 2, 3]); // 数组有 length 属性,可以正常工作
- // getLength(123); // 错误!数字没有 length 属性
复制代码 1.3 默认范例参数
TypeScript 答应为泛型范例参数提供默认值,雷同于函数参数的默认值:- interface ApiResponse<T = any> {
- data: T;
- status: number;
- message: string;
- }
- // 没有指定类型参数,使用默认值 any
- const generalResponse: ApiResponse = {
- data: "some data",
- status: 200,
- message: "Success"
- };
- // 明确指定类型参数
- const userResponse: ApiResponse<User> = {
- data: { id: 1, name: "John Doe" },
- status: 200,
- message: "User retrieved successfully"
- };
- interface User {
- id: number;
- name: string;
- }
复制代码 2. 泛型工具范例详解
TypeScript 提供了很多内置的泛型工具范例,它们可以资助我们实验常见的范例转换。这些工具范例都是基于泛型构建的,展示了泛型的强大功能。
2.1 Partial, Required, Readonly
这组工具范例紧张用于修改对象范例的属性特性:- interface User {
- id: number;
- name: string;
- email: string;
- role: 'admin' | 'user';
- createdAt: Date;
- }
- // Partial<T> - 将所有属性变为可选
- type PartialUser = Partial<User>;
- // 等同于:
- // {
- // id?: number;
- // name?: string;
- // email?: string;
- // role?: 'admin' | 'user';
- // createdAt?: Date;
- // }
- // 更新用户时,我们只需要提供要更新的字段
- function updateUser(userId: number, userData: Partial<User>): Promise<User> {
- // 实现省略
- return Promise.resolve({} as User);
- }
- // Required<T> - 将所有可选属性变为必需
- interface PartialConfig {
- host?: string;
- port?: number;
- protocol?: 'http' | 'https';
- }
- type CompleteConfig = Required<PartialConfig>;
- // 等同于:
- // {
- // host: string;
- // port: number;
- // protocol: 'http' | 'https';
- // }
- // Readonly<T> - 将所有属性变为只读
- type ReadonlyUser = Readonly<User>;
- // 等同于:
- // {
- // readonly id: number;
- // readonly name: string;
- // readonly email: string;
- // readonly role: 'admin' | 'user';
- // readonly createdAt: Date;
- // }
- const user: ReadonlyUser = {
- id: 1,
- name: "John Doe",
- email: "john@example.com",
- role: "user",
- createdAt: new Date()
- };
- // 错误:无法分配到"name",因为它是只读属性
- // user.name = "Jane Doe";
复制代码 2.2 Record<K,T>, Pick<T,K>, Omit<T,K>
这组工具范例紧张用于构造或提取对象范例:- // Record<K,T> - 创建一个具有类型 K 的键和类型 T 的值的对象类型
- type UserRoles = Record<string, 'admin' | 'editor' | 'viewer'>;
- // 等同于:
- // {
- // [key: string]: 'admin' | 'editor' | 'viewer'
- // }
- const roles: UserRoles = {
- 'user1': 'admin',
- 'user2': 'editor',
- 'user3': 'viewer'
- };
- // 特别有用的情况:创建映射对象
- type UserIds = 'user1' | 'user2' | 'user3';
- const permissionsByUser: Record<UserIds, string[]> = {
- user1: ['read', 'write', 'delete'],
- user2: ['read', 'write'],
- user3: ['read']
- };
- // Pick<T,K> - 从类型 T 中选择指定的属性 K
- type UserProfile = Pick<User, 'name' | 'email'>;
- // 等同于:
- // {
- // name: string;
- // email: string;
- // }
- // 非常适合生成表单或API相关的数据结构
- function getUserProfile(user: User): UserProfile {
- return {
- name: user.name,
- email: user.email
- };
- }
- // Omit<T,K> - 从类型 T 中排除指定的属性 K
- type UserWithoutSensitiveInfo = Omit<User, 'id' | 'createdAt'>;
- // 等同于:
- // {
- // name: string;
- // email: string;
- // role: 'admin' | 'user';
- // }
- // 创建新用户输入表单,去除自动生成的字段
- function createUserFromForm(userData: UserWithoutSensitiveInfo): User {
- return {
- ...userData,
- id: generateId(), // 假设的函数
- createdAt: new Date()
- };
- }
复制代码 2.3 Extract<T,U>, Exclude<T,U>, NonNullable
这组工具范例紧张用于团结范例的操纵:- // 定义一些联合类型
- type Species = 'cat' | 'dog' | 'bird' | 'fish' | 'reptile';
- type Mammals = 'cat' | 'dog';
- // Extract<T,U> - 从 T 中提取可赋值给 U 的类型
- type MammalsFromSpecies = Extract<Species, Mammals>;
- // 结果: 'cat' | 'dog'
- // 更实用的例子
- type ApiResponse =
- | { status: 'success'; data: any }
- | { status: 'error'; error: string }
- | { status: 'loading' };
- type SuccessResponse = Extract<ApiResponse, { status: 'success' }>;
- // 结果: { status: 'success'; data: any }
- // Exclude<T,U> - 从 T 中排除可赋值给 U 的类型
- type NonMammals = Exclude<Species, Mammals>;
- // 结果: 'bird' | 'fish' | 'reptile'
- // 排除所有错误状态
- type NonErrorResponses = Exclude<ApiResponse, { status: 'error' }>;
- // 结果: { status: 'success'; data: any } | { status: 'loading' }
- // NonNullable<T> - 从 T 中排除 null 和 undefined
- type MaybeString = string | null | undefined;
- type DefinitelyString = NonNullable<MaybeString>;
- // 结果: string
- // 使用场景:过滤数组中的非空值
- function filterNonNullable<T>(array: Array<T | null | undefined>): Array<NonNullable<T>> {
- return array.filter((item): item is NonNullable<T> => item !== null && item !== undefined) as Array<NonNullable<T>>;
- }
- const mixedArray = ['hello', null, 'world', undefined, '!'];
- const filteredArray = filterNonNullable(mixedArray);
- // 结果: ['hello', 'world', '!']
复制代码 3. 条件范例与范例推断 - infer 关键字
条件范例是TypeScript中最强大的范例构造之一,它答应我们基于范例关系创建条件逻辑。
infer 关键字,它答应我们声明一个范例变量,用于捕捉和提取符合特定模式的范例。简单来说,它让我们可以大概从复杂范例中"提取"出我们关心的部门。
根本语法
- type ExtractSomething<T> = T extends Pattern_with_infer_X ? X : Fallback;
复制代码 在这个模式中:
- T是我们要查抄的范例
- Pattern_with_infer_X是包罗infer X声明的模式
- 如果T符合该模式,结果范例就是我们提取出的X
- 否则,结果范例为Fallback
简单示例:提取函数返回范例
- // 定义一个提取函数返回类型的工具类型
- type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : any;
- // 一个简单的函数
- function getUsername(): string {
- return "张三";
- }
- // 提取函数的返回类型
- type Username = ReturnTypeOf<typeof getUsername>; // 结果: string
复制代码 infer的本质是举行模式匹配。就像我们辨认笔墨或图像一样,它根据预设的模式来找到并"捕捉"范例中的特定部门。
这就似乎我们看到"159****1234"如许的号码,立刻就能辨认出这是一个手机号,而且知道中央的星号部门是隐蔽的数字。infer在范例天下做的变乱与此雷同——它根据上下文模式自动推断出被省略或隐蔽的范例部门。
4. 案例:泛型在Redux Toolkit中的应用
Redux Toolkit是Redux的官方保举工具集,它大量使用了TypeScript的泛型来提供范例安全的状态管理。让我们看看它怎样使用泛型:

下面我们将看看Redux Toolkit中的泛型应用,并实现一个简单的TodoList应用:- import {
- createSlice,
- createAsyncThunk,
- PayloadAction,
- configureStore
- } from '@reduxjs/toolkit';
- // 1. 定义类型
- interface Todo {
- id: number;
- text: string;
- completed: boolean;
- }
- interface TodosState {
- items: Todo[];
- status: 'idle' | 'loading' | 'succeeded' | 'failed';
- error: string | null;
- }
- // 2. 使用createAsyncThunk泛型
- // createAsyncThunk<返回值类型, 参数类型, { rejectValue: 错误类型 }>
- export const fetchTodos = createAsyncThunk
- Todo[],
- void,
- { rejectValue: string }
- >('todos/fetchTodos', async (_, { rejectWithValue }) => {
- try {
- const response = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=10');
- if (!response.ok) {
- return rejectWithValue('Failed to fetch todos.');
- }
- return await response.json();
- } catch (error) {
- return rejectWithValue(error instanceof Error ? error.message : 'Unknown error');
- }
- });
- // 3. 使用createSlice泛型来创建切片
- // createSlice<状态类型>
- const todosSlice = createSlice({
- name: 'todos',
- initialState: {
- items: [],
- status: 'idle',
- error: null
- } as TodosState,
- reducers: {
- // PayloadAction<载荷类型> 增强了action的类型安全
- addTodo: (state, action: PayloadAction<string>) => {
- const newTodo: Todo = {
- id: Date.now(),
- text: action.payload,
- completed: false
- };
- state.items.push(newTodo);
- },
- toggleTodo: (state, action: PayloadAction<number>) => {
- const todo = state.items.find(item => item.id === action.payload);
- if (todo) {
- todo.completed = !todo.completed;
- }
- },
- removeTodo: (state, action: PayloadAction<number>) => {
- state.items = state.items.filter(item => item.id !== action.payload);
- }
- },
- extraReducers: (builder) => {
- // 处理异步action状态
- builder
- .addCase(fetchTodos.pending, (state) => {
- state.status = 'loading';
- })
- .addCase(fetchTodos.fulfilled, (state, action: PayloadAction<Todo[]>) => {
- state.status = 'succeeded';
- state.items = action.payload;
- })
- .addCase(fetchTodos.rejected, (state, action) => {
- state.status = 'failed';
- state.error = action.payload || 'Unknown error';
- });
- }
- });
- // 4. 导出actions
- export const { addTodo, toggleTodo, removeTodo } = todosSlice.actions;
- // 5. 配置store
- const store = configureStore({
- reducer: {
- todos: todosSlice.reducer
- }
- });
- // 6. 从store中提取RootState和AppDispatch类型
- export type RootState = ReturnType<typeof store.getState>;
- export type AppDispatch = typeof store.dispatch;
- // 7. 强类型的Hooks
- import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
- // 为useDispatch和useSelector创建强类型的版本
- export const useAppDispatch = () => useDispatch<AppDispatch>();
- export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
- // 8. 在组件中使用
- import React, { useEffect, useState } from 'react';
- import { useAppDispatch, useAppSelector } from './store';
- import { addTodo, toggleTodo, removeTodo, fetchTodos } from './todosSlice';
- const TodoApp: React.FC = () => {
- const [newTodo, setNewTodo] = useState('');
- const dispatch = useAppDispatch();
-
- // 强类型的selector,IDE可以提供自动完成
- const { todos, status, error} = useAppSelector(state => state.todos);
-
- useEffect(() => {
- if (status === 'idle') {
- dispatch(fetchTodos());
- }
- }, [status, dispatch]);
-
- const handleAddTodo = (e: React.FormEvent) => {
- e.preventDefault();
- if (newTodo.trim()) {
- dispatch(addTodo(newTodo));
- setNewTodo('');
- }
- };
-
- if (status === 'loading') {
- return <div>Loading...</div>;
- }
-
- if (status === 'failed') {
- return <div>Error: {error}</div>;
- }
-
- return (
- <div>
- <h1>Todo List</h1>
-
- <form onSubmit={handleAddTodo}>
- <input
- type="text"
- value={newTodo}
- onChange={(e) => setNewTodo(e.target.value)}
- placeholder="Add a new todo"
- />
- <button type="submit">Add</button>
- </form>
-
- <ul>
- {todos.map(todo => (
- <li
- key={todo.id}
- style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
- >
- <span onClick={() => dispatch(toggleTodo(todo.id))}>
- {todo.text}
- </span>
- <button onClick={() => dispatch(removeTodo(todo.id))}>
- Delete
- </button>
- </li>
- ))}
- </ul>
- </div>
- );
- };
- export default TodoApp;
复制代码 Redux Toolkit中的泛型带来的长处:
- 范例安全的Actions:通过PayloadAction<T>泛型,确保了action的载荷范例精确。
- 范例安全的Thunks:createAsyncThunk<返回值范例, 参数范例, 选项>泛型确保了异步操纵的范例安全。
- 范例安全的State访问:通过RootState范例和强范例的selector hooks,确保了状态的范例安全访问。
- 智能的自动完成:由于范例体系的存在,IDE可以提供更好的自动完乐成能。
- 编译时错误查抄:错误在编译时而非运行时被捕捉。
这些高级泛型技能使Redux Toolkit可以大概提供精良的开辟体验,尤其在大型应用中尤为紧张。
总结
泛型是TypeScript最强大的特性之一,把握泛型可以资助我们写出更机动、更可重用、更范例安全的代码。随着TypeScript的不停发展,泛型的应用将变得越来越广泛和紧张。通过深入明确泛型,我们可以充实使用TypeScript的范例体系,进步代码质量和开辟服从。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
|