泛型编程
2026/7/9大约 6 分钟
泛型编程
概述
Go 1.18 引入了泛型,但其表达能力仍然有限(不支持协变/逆变、条件类型等)。TypeScript 的泛型系统极其强大,支持泛型函数、泛型接口、泛型类、条件类型、映射类型、infer 关键字等高级特性。对于 Go 开发者,TS 泛型的灵活性可能令人惊叹,但也容易过度设计。
Go 开发者已知
// Go 1.18+ 泛型
func Map[T any, U any](s []T, f func(T) U) []U {
result := make([]U, len(s))
for i, v := range s {
result[i] = f(v)
}
return result
}
// 泛型约束
type Number interface {
~int | ~float64
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
// 泛型结构体
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}Go 泛型特点:
- 支持泛型函数和泛型类型
- 通过
interface定义类型约束(union) - 不支持协变/逆变
- 不支持条件类型
- 不支持映射类型
TypeScript 怎么做
泛型函数
// 基本泛型函数
function identity<T>(arg: T): T {
return arg
}
// 调用
const n = identity(42) // 推断为 number
const s = identity('hello') // 推断为 string
const x = identity<number>(42) // 显式指定
// 多个泛型参数
function map<T, U>(items: T[], fn: (item: T) => U): U[] {
return items.map(fn)
}泛型接口
interface Repository<T> {
getById(id: string): Promise<T | null>
getAll(): Promise<T[]>
create(item: T): Promise<T>
update(id: string, item: Partial<T>): Promise<T>
delete(id: string): Promise<void>
}
// 实现
class UserRepository implements Repository<User> {
async getById(id: string): Promise<User | null> {
return db.users.findById(id)
}
// ... 其他方法
}泛型类
class Stack<T> {
private items: T[] = []
push(item: T): void {
this.items.push(item)
}
pop(): T | undefined {
return this.items.pop()
}
peek(): T | undefined {
return this.items[this.items.length - 1]
}
get size(): number {
return this.items.length
}
}
const stack = new Stack<number>()
stack.push(1)
stack.push(2)
const top = stack.pop() // number | undefined泛型约束 extends
interface HasLength {
length: number
}
// 约束 T 必须具有 length 属性
function logLength<T extends HasLength>(arg: T): T {
console.log(arg.length)
return arg
}
logLength('hello') // 5
logLength([1, 2, 3]) // 3
// logLength(123) // Error: number 没有 length
// keyof 约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const user = { name: 'Alice', age: 30 }
getProperty(user, 'name') // string
getProperty(user, 'age') // number
// getProperty(user, 'email') // Error条件类型 Conditional Types
// 条件类型
type IsString<T> = T extends string ? true : false
type A = IsString<'hello'> // true
type B = IsString<42> // false
// 嵌套条件类型
type TypeName<T> =
T extends string ? 'string' :
T extends number ? 'number' :
T extends boolean ? 'boolean' :
T extends undefined ? 'undefined' :
T extends Function ? 'function' :
'object'
type T1 = TypeName<string> // 'string'
type T2 = TypeName<42> // 'number'infer 关键字
// 提取函数返回类型
type ReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never
type Fn = (x: number) => string
type R = ReturnType<Fn> // string
// 提取数组元素类型
type ElementType<T> = T extends (infer U)[] ? U : never
type E = ElementType<number[]> // number
// 提取 Promise 值类型
type Unwrap<T> = T extends Promise<infer U> ? U : T
type P = Unwrap<Promise<string>> // string
type N = Unwrap<number> // number映射类型 Mapped Types
// 将所有属性变为可选
type MyPartial<T> = {
[K in keyof T]?: T[K]
}
// 将所有属性变为只读
type MyReadonly<T> = {
readonly [K in keyof T]: T[K]
}
// 映射 + 条件类型
type Nullable<T> = {
[K in keyof T]: T[K] | null
}
// 应用
interface User {
name: string
age: number
}
type PartialUser = MyPartial<User>
// { name?: string; age?: number }差异分析
| 维度 | Go 1.18+ | TypeScript |
|---|---|---|
| 语法 | [T any] | <T> |
| 约束 | interface { ~int } | extends + 条件类型 |
| 条件类型 | 不支持 | T extends U ? X : Y |
| infer | 不支持 | 支持在条件类型中提取 |
| 映射类型 | 不支持 | [K in keyof T]: T[K] |
| 协变/逆变 | 不变 | 默认协变,可配置逆变 |
| 泛型约束符号 | ~ 近似 | extends 约束 |
| 内置工具类型 | 无 | Partial/Pick/Record 等 |
Go 的 ~ 近似约束 vs TS 的 extends
Go 的 ~int 表示"底层类型为 int 的所有类型"(包括自定义类型),而 TypeScript 的 extends 是结构化子类型检查。
// Go: 接受任何底层类型为 int 的类型
type MyInt int
func Add[T ~int](a, b T) T { return a + b }
// Add(MyInt(1), MyInt(2)) // OK// TS: 接受任何可赋值给 number 的类型
function add<T extends number>(a: T, b: T): T {
return (a + b) as T
}Bad Practice
// Bad: 泛型参数未使用
function process<T>(input: string): string {
return input.toUpperCase()
}
// Bad: 过度设计(过于泛化)
function deepClone<T, U, V>(
obj: T,
options?: U,
callback?: V
): T {
// 三个泛型参数只用了 T
return JSON.parse(JSON.stringify(obj))
}
// Bad: 泛型命名无意义
function f<A, B, C>(a: A, b: B): C {
// 难以理解
return a as unknown as C
}
// Bad: 泛型约束过于宽松
function getLength<T>(arg: T): number {
// return arg.length // Error: T 上不存在 length
return 0
}Best Practice
// Good: 有意义的泛型命名
function createPair<TKey, TValue>(key: TKey, value: TValue): [TKey, TValue] {
return [key, value]
}
// Good: 适当约束泛型参数
function getLength<T extends { length: number }>(arg: T): number {
return arg.length
}
// Good: 使用内置工具类型
interface User {
name: string
age: number
email: string
}
type PartialUser = Partial<User> // 所有属性可选
type UserName = Pick<User, 'name'> // 只取 name
type WithoutEmail = Omit<User, 'email'> // 排除 email
// Good: 条件类型做精确的类型转换
type Arrayify<T> = T extends unknown[] ? T : T[]
type A = Arrayify<string> // string[]
type B = Arrayify<number[]> // number[]
// Good: infer 提取复杂类型
type AsyncReturnType<T> = T extends (...args: unknown[]) => Promise<infer R> ? R : never
// Good: 映射类型保持修饰符
type Writable<T> = {
-readonly [K in keyof T]: T[K] // -readonly 去除只读
}内置工具类型速查
| 工具类型 | 作用 | 示例 |
|---|---|---|
Partial<T> | 所有属性可选 | Partial<User> |
Required<T> | 所有属性必填 | Required<PartialUser> |
Readonly<T> | 所有属性只读 | Readonly<User> |
Pick<T, K> | 选取部分属性 | Pick<User, 'name'> |
Omit<T, K> | 排除部分属性 | Omit<User, 'password'> |
Record<K, V> | 键值对对象 | Record<string, number> |
Exclude<T, U> | 从联合类型排除 | Exclude<'a'|'b', 'a'> |
Extract<T, U> | 从联合类型提取 | Extract<'a'|'b', 'a'> |
NonNullable<T> | 排除 null/undefined | NonNullable<string|null> |
ReturnType<T> | 函数返回值类型 | ReturnType<typeof fn> |
Parameters<T> | 函数参数类型元组 | Parameters<typeof fn> |
Awaited<T> | Promise 展开值类型 | Awaited<Promise<string>> |
code-tabs 对比
总结
| Go 泛型 | TypeScript 泛型 |
|---|---|
[T any] | <T> |
interface { ~int } 约束 | T extends SomeType |
| 仅支持基本泛型 | 支持条件/映射/infer 等高级特性 |
| 不变(invariant) | 协变/逆变可配置 |
| 没有内置工具类型 | 丰富的内置工具类型 |
TypeScript 的泛型系统比 Go 强大得多,但也需要克制。遵循 KISS 原则:能用具体类型解决问题就不要泛型化,能用一个泛型参数解决就不要用两个。
下一章将介绍 枚举与字面量类型。