目标读者:刚学 TS 的前端开发者,或希望把泛型用到实际工程(请求封装、组件复用)中的同学。


目录

  1. 为什么需要泛型(直观动机)
  2. 基本语法与例子(函数、接口、类)
  3. 泛型约束(extendskeyof
  4. 进阶语法:默认类型、多个类型参数、泛型推断
  5. 实战一:request<T> 网络请求封装(详细讲解)
  6. 实战二:React 通用下拉组件 <Select<T>>(含使用示例)
  7. 常见坑、调试技巧与最佳实践
  8. 练习题与参考资料

1. 为什么需要泛型(直观动机)

在没有泛型的世界里,如果你写一个工具函数或组件只能处理单一类型,就会出现大量重复代码或丧失类型提示。

举例:写一个返回第一个元素的 first 函数,如果不使用泛型,你可能写成 any,失去类型安全:

function firstBad(arr: any[]) { return arr[0]; }
const a = firstBad([1,2,3]); // a 的类型是 any,编辑器不会提示

使用泛型后:

function first<T>(arr: T[]): T | undefined { return arr[0]; }
const a = first([1,2,3]); // a 被推断为 number | undefined

泛型能让工具/组件“对所有类型通用”,同时保留类型信息,这就是它的价值。


2. 基本语法与例子

2.1 泛型函数

// 最基础的泛型函数:identity
function identity<T>(arg: T): T {return arg;
}const s = identity('hello'); // T 被推断为 string
const n = identity<number>(123); // 显示指定泛型

注意:一般情况下不必显式写 <T>,TypeScript 会根据参数自动推断。

2.2 泛型类型别名 / 接口

type Box<T> = { value: T };
const b: Box<number> = { value: 42 };interface ApiResponse<T> {code: number;data: T;
}const r: ApiResponse<string[]> = { code: 0, data: ['a','b'] };

2.3 泛型类

class Stack<T> {private items: T[] = [];push(item: T) { this.items.push(item); }pop(): T | undefined { return this.items.pop(); }
}const s = new Stack<number>();
s.push(1);

2.4 多个类型参数

function mapArray<T, U>(arr: T[], fn: (t: T) => U): U[] {return arr.map(fn);
}const r = mapArray([1,2,3], x => x.toString()); // r: string[]

3. 泛型约束(extendskeyof

有时候我们要限制泛型的“范围”,比如只允许对象类型、必须包含某些属性等。

3.1 extends 限制

function pluck<T extends object, K extends keyof T>(obj: T, key: K) {return obj[key];
}const user = { id: '1', name: 'Alice' };
pluck(user, 'name'); // OK
// pluck(user, 'notExist'); // Error

解释:K extends keyof T 表示 K 必须是 T 的键之一,防止传入不存在的属性名。

3.2 keyof 的常见用法

type KeysOfUser = keyof typeof user; // 'id' | 'name'

4. 进阶语法(默认类型、泛型推断等)

4.1 默认类型

function identityDefault<T = string>(arg: T): T { return arg; }
const a = identityDefault('x'); // T 推断为 string

4.2 泛型推断

TypeScript 会根据函数参数自动推断泛型类型,像 identity([1,2,3]) 会推断 Tnumber[] 的元素类型(… 具体依赖签名)。


5. 实战一:封装 request<T>(网络请求)

目的:写一个简单且实用的 request,在调用处能用泛型指定返回类型,从而获得完整的类型提示。

5.1 需求与设计

  • 希望 request<T>(url) 返回 Promise<T>
  • 在大多数场景后端返回的是一个包裹结构,比如 { code: number, data: T },我们也要支持。
  • 稍微封装错误处理与超时(示例化,不追求复杂性)。

5.2 代码实现(utils/request.ts

// utils/request.ts
export type ApiResponse<T> = { code: number; data: T; message?: string };export async function request<T = any>(url: string, init?: RequestInit): Promise<T> {const controller = new AbortController();const timeout = setTimeout(() => controller.abort(), 10_000);try {const res = await fetch(url, { signal: controller.signal, ...init });if (!res.ok) throw new Error(res.statusText);const data = await res.json();return data as T; // 注意:这是类型断言,运行时不会做检查} finally {clearTimeout(timeout);}
}

5.3 使用示例

// types.ts
type User = { id: string; name: string };// 使用(直接返回数组)
const users = await request<User[]>('/api/users');
users[0].name; // 编辑器会提示 name// 使用(后端返回包裹结构)
const resp = await request<ApiResponse<User[]>>('/api/users-pkg');
const list = resp.data; // 正常使用

5.4 提醒:类型安全与运行时验证

TypeScript 的类型只存在编译阶段。request<T> 中的 return data as T 是“信任后端返回的结构”。如果需要更严格的保证,请在运行时做校验(使用 zodio-ts 等)。


6. 实战二:React 通用下拉组件 <Select<T>>(简单到常用)

目标:实现一个对数据类型“透明”的下拉组件,使用泛型后,父组件拿到 onChange 的回调类型时能直接获得具体类型提示。

6.1 需求与设计

  • 组件接收 options: T[]
  • 需要 getLabel?: (item: T) => string,用于渲染文本。
  • 需要 keyExtractor?: (item: T, idx: number) => string | number,用于 keyvalue(避免假设数据有 id 字段)。
  • onChange?: (item: T | null) => void

6.2 组件代码(简洁、可用)

import React from 'react';export interface SelectProps<T> {options: T[];value?: T | null;onChange?: (item: T | null) => void;placeholder?: string;getLabel?: (item: T) => string;keyExtractor?: (item: T, idx: number) => string | number;
}// 注意箭头函数组件写法:const Select = <T,>(props: SelectProps<T>) => { ... }
export const Select = <T,>({ options, value, onChange, placeholder, getLabel, keyExtractor }: SelectProps<T>) => {const labelOf = getLabel ?? ((it: T) => String((it as any)));const keyOf = keyExtractor ?? ((_: T, idx: number) => idx);return (<selectvalue={options.indexOf(value as T)}onChange={(e) => {const idx = Number(e.target.value);onChange?.(idx >= 0 ? options[idx] : null);}}><option value={-1}>{placeholder ?? '请选择'}</option>{options.map((it, i) => (<option key={String(keyOf(it, i))} value={i}>{labelOf(it)}</option>))}</select>);
};

说明

  • const Select = <T,>(...) 中的 ,(逗号)是一个常用写法,用来避免 TSX 将 <T> 误解析为 JSX;这是声明泛型函数表达式/箭头函数时的语法技巧。
  • 为了让组件与任意数据结构配合,我们没有假定 itemidlabel 字段,而是通过 keyExtractorgetLabel 注入策略。

6.3 使用示例

// App.tsx
import React, { useState, useEffect } from 'react';
import { Select } from './Select';
import { request } from './utils/request';type User = { id: string; name: string };function App() {const [users, setUsers] = useState<User[]>([]);const [sel, setSel] = useState<User | null>(null);useEffect(() => {request<User[]>('/api/users').then(setUsers).catch(console.error);}, []);return (<div><Selectoptions={users}value={sel}onChange={(u) => setSel(u)}getLabel={(u) => u.name}keyExtractor={(u) => u.id}placeholder="选择用户"/><div>当前选中:{sel ? sel.name : '无'}</div></div>);
}

类型体验:当你写 onChange={(u) => setSel(u)} 时,编辑器会推断 u 的类型为 User | null,这给你编辑器级别的保护与提示。

6.4 关于显式泛型(什么时候必须)

通常只要 options 的类型是具体的数组(User[]),TS 能推断出 T,使用时不需要写 <Select<User> />
如果推断失败(例如 options 类型被擦除为 any[]),你可以:

  • 在数据源处把类型写清楚(推荐);
  • 或在组件使用处做类型断言:<Select options={someAny as User[]} ... />

7. 常见坑、调试技巧与最佳实践

  • 不要滥用 any:泛型的一个目标就是替代 any,保留类型信息。
  • 理解类型与运行时的边界:泛型只是编译期工具,运行时没有类型检查。
  • 在库/公共代码中多写泛型,在应用层用具体类型;库需要更强的泛型设计能力。
  • 避免过度复杂的类型:当类型系统变得难以理解时,权衡是否用运行时校验来代替复杂类型。
  • 在 React 中尽量依赖类型推断,不要在 JSX 里频繁显式写 <Component<Type> />(有时会引起解析问题)。

8. 练习题(自测)

  1. 写一个泛型 filterMap<T, U>,它的签名为 (arr: T[], fn: (t: T) => U | null) => U[]
  2. 基于 request<T>,写一个 getJson<T>(url),当后端返回 { code, data } 结构时,自动返回 data
  3. 修改 Select 组件,使它支持 multiple(多选)并确保类型安全。

9. 总结与下一步学习建议

  • 泛型让你的代码既通用类型安全,是编写可复用工具与组件的核心。
  • 推荐掌握:泛型约束(extends)、keyof、条件类型(下一步,可学 infer)、以及常见内置工具类型(Partial/Readonly/Record)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/95534.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/95534.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/95534.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux ARP老化机制/探测机制/ip neigh使用

文章目录1. ARP状态机1.1 ARP状态类型1.2 状态转换图2. 超时时间与参数2.1 主要超时参数2.1.1 基础时间参数2.1.2 探测相关参数2.1.3 垃圾回收参数3. 主机发送ARP报文的时机3.1 发送数据包时发现ARP缓存中没有目标IP的MAC地址3.2 ARP条目进入STALE状态后需要发送数据3.3 定期维…

便携式显示器怎么选?:6大关键指标全解析

地铁通勤路上&#xff0c;程序员小陈掏出背包里的便携屏&#xff0c;连接手机即刻扩展出第二个工作窗口&#xff0c;赶在上班前修复了紧急bug&#xff1b;咖啡厅里&#xff0c;设计师阿琳用笔记本加便携屏的双屏组合&#xff0c;一边参看客户brief一边修改方案&#xff0c;效率…

以太坊网络

以太坊的网络和以太网是两个完全不同的概念&#xff0c;虽然它们的名称听起来相似&#xff0c;但它们属于不同的技术领域。以太坊网络&#xff08;Ethereum Network&#xff09;领域&#xff1a;区块链和加密货币定义&#xff1a;以太坊是一个开源区块链平台&#xff0c;支持智…

nano banana官方最强Prompt模板来了!六大场景模板详解

最近&#xff0c;相信大家的朋友圈和社交媒体都被一个叫nano banana的AI图像生成工具刷屏了。从精致的手办模型到名画人物穿越&#xff0c;再到中土世界场景还原&#xff0c;nano banana已然成为了最火爆的“整活工具之一。刚刚&#xff0c;谷歌为nano banana推出了官方Prompt模…

LeetCode 2825.循环增长使字符串子序列等于另一个字符串

给你一个下标从 0 开始的字符串 str1 和 str2 。 一次操作中&#xff0c;你选择 str1 中的若干下标。对于选中的每一个下标 i &#xff0c;你将 str1[i] 循环 递增&#xff0c;变成下一个字符。也就是说 ‘a’ 变成 ‘b’ &#xff0c;‘b’ 变成 ‘c’ &#xff0c;以此类推&a…

【无人机】1.编译betaflight和cleanflight的固件

在2023年&#xff0c;betaflight腾飞&#xff0c;而cleanflight已经结束更新&#xff0c;但是用cleanflight的原因是因为他最后版本支持stm32f103系列。不用betaflight因为手头还没有f405和f411&#xff0c;只有一个不支持的f407和f401&#xff0c;所以。。接下来开始步骤&…

刻意练习理论

刻意练习理论 一、理论概述 刻意练习&#xff08;Deliberate Practice&#xff09;是由心理学家安德斯艾利克森&#xff08;Anders Ericsson&#xff09;提出的一种系统化学习方法&#xff0c;核心观点是卓越并非源于天赋&#xff0c;而是通过针对性训练获得。其理论基础来自对…

【FastDDS】Layer DDS之Domain ( 04-DomainParticipantFactory)

Fast DDS 域参与者工厂&#xff08;DomainParticipantFactory&#xff09;详解 一、域参与者工厂&#xff08;DomainParticipantFactory&#xff09;基础定义 域参与者工厂&#xff08;DomainParticipantFactory&#xff09;的唯一作用是实现域参与者&#xff08;DomainPartici…

树莓集团建数字产业学院:产教融合强化成渝人才链与产业链衔接

成渝地区双城经济圈建设是国家重大发展战略&#xff0c;而人才链与产业链的有效衔接&#xff0c;是推动成渝地区产业高质量发展的关键。树莓集团顺应时代发展需求&#xff0c;搭建数字产业学院&#xff0c;以产教融合为纽带&#xff0c;不断强化成渝人才链与产业链的衔接&#…

在 ASP.NET 8 WebAPI 中使用不同的提供程序验证多个令牌(Token)及常见问题解答

介绍作为 ASP.NET 框架的最新版本&#xff0c;ASP.NET 8提供了强大的功能&#xff0c;可用于构建安全且可扩展的 Web API。API 开发的一个关键方面是身份验证&#xff0c;它确保只有授权用户或服务才能访问受保护的资源。在本文中&#xff0c;我们将探讨如何在 ASP.NET 8 API 中…

工业相机为啥丢包?黑条 / 撕裂的原因 + 解决办法,一看就懂

工业相机为啥丢包&#xff1f;黑条/撕裂的原因解决办法&#xff0c;一看就懂 工业相机拍图时出现黑条、撕裂、花屏&#xff0c;别急着换设备——大概率是“数据丢包”在搞鬼。尤其是高频率、高分辨率采图时&#xff0c;数据传输稍出问题&#xff0c;图像就会出故障。今天用“快…

【IQA技术专题】NIQE代码讲解

本文是对NIQE图像质量评价指标的代码解读&#xff0c;原文解读请看NIQE文章讲解。 本文的代码来源于IQA-Pytorch工程。 1、原文概要 NIQE实现了无参考的图像质量评价指标&#xff0c;可以有效地对图像的感知&#xff08;Fidelity&#xff09;质量进行评估。本文提出了一种完全…

配置时钟分频与倍频

在STM32微控制器中&#xff0c;“配置时钟分频与倍频”是一个关键步骤&#xff0c;它允许开发者根据应用需求调整系统时钟的频率。以下是对这一概念的详细解释&#xff1a;时钟源与基础频率时钟源&#xff1a;STM32微控制器通常支持多种时钟源&#xff0c;如高速外部时钟&#…

【深度学习新浪潮】视觉大模型在预训练方面有哪些关键进展?

近年来,视觉大模型在预训练领域取得了多项突破性进展,涵盖架构设计、多模态融合、数据利用效率及训练策略等多个维度。以下结合2024-2025年最新研究成果,从技术创新和应用突破两方面展开分析: 一、架构创新:突破分辨率与模态限制 超高分辨率预训练 伯克利与英伟达提出的P…

Elasticsearch原理篇

Elasticsearch原理篇写在前面&#xff1a;用之于手&#xff0c;先明于心一、传统数据库的瓶颈&#xff1a;当数据量成为负担1. 千万级数据下的性能衰减2. 分页查询的“深水陷阱”3. 关联查询的扩展难题4. 全文检索能力薄弱二、Elasticsearch 的优势&#xff1a;为搜索而生的分布…

《我是如何用C语言写工控系统的漏洞和Bug》连载(1)内容大纲

第一部分&#xff1a;导论与基础 第1章 引言 1.1 工控系统的独特性和重要性 实时性、可靠性、长生命周期的要求与IT系统的差异&#xff1a;后果不再是信息泄露&#xff0c;而是物理世界的中断与破坏 1.2 为什么C语言依然是工控领域的主流&#xff1f; 性能、底层硬件操作、历史…

.Net程序员就业现状以及学习路线图(三)

一、.Net程序员就业现状分析 1. 市场需求与薪资水平 ‌市场需求两极分化‌&#xff1a;2025年数据显示&#xff0c;.Net开发岗位全国占比约0.009%&#xff0c;主要集中在深圳、上海等一线城市 2 3。高端岗位&#xff08;云原生/AI集成方向&#xff09;年薪可达36-60万&#xff…

云计算学习100天-第40天 -普罗米修斯1

目录 Prometheus 概述—— 安装prometheus 案例 环境说明 实验步骤 一、prometheus服务器配置时间同步 二、安装Prometheus服务器 配置文件说明 三、编写服务启动文件并启动服务 四、访问web页面 Prometheus 概述—— Prometheus是一个开源系统监控和警报工具包&a…

高效文本处理:cut、sort、uniq 和 tr 命令详解与实战

前言 &#x1f52a; 一、cut —— 按列或字符截取 常用选项&#xff1a; 示例&#xff1a; &#x1f504; 二、sort —— 排序&#xff08;默认按行首字符升序&#xff09; 常用选项&#xff1a; 示例&#xff1a; &#x1f9fc; 三、uniq —— 去除连续重复行 常用选项…

时序数据库选型指南:Apache IoTDB为何成为工业物联网首选?

引言&#xff1a;时序数据管理的时代挑战 随着工业4.0和物联网技术的快速发展&#xff0c;全球时序数据呈现爆炸式增长。据IDC预测&#xff0c;到2025年&#xff0c;全球物联网设备产生的数据量将达到79.4ZB&#xff0c;其中超过60%为时序数据。这类数据具有显著特征&#xff…