默认情况下,泛型可以接受任意类型。有时我们需要限制泛型的范围,确保类型参数满足特定条件。泛型约束让我们可以精确控制类型参数的范围。
使用 extends 关键字约束类型参数。
interface Lengthwise {
length: number
}
function logLength<T extends Lengthwise>(arg: T): number {
return arg.length
}
console.log(logLength("hello"))
console.log(logLength([1, 2, 3]))
console.log(logLength({ length: 10 }))
T extends Lengthwise 表示 T 必须有 length 属性。这让我们可以在函数内部安全地访问 length。
约束可以是接口、类型别名或原始类型。
function process<T extends string | number>(value: T): T {
console.log(`处理值: ${value}`)
return value
}
process("hello")
process(123)
T extends string | number 限制 T 只能是字符串或数字。
可以在约束中使用另一个类型参数。
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const user = {
id: 1,
name: "张三",
email: "zhangsan@example.com"
}
console.log(getProperty(user, "name"))
console.log(getProperty(user, "email"))
K extends keyof T 确保 key 是 T 的有效属性名。keyof T 获取 T 的所有键的联合类型。
约束可以与默认类型结合使用。
interface ApiResponse<T extends object = Record<string, any>> {
code: number
message: string
data: T
}
const response1: ApiResponse = {
code: 200,
message: "success",
data: { name: "张三" }
}
const response2: ApiResponse<{ id: number; name: string }> = {
code: 200,
message: "success",
data: { id: 1, name: "李四" }
}
T extends object = Record<string, any> 约束 T 必须是对象类型,默认值是 Record<string, any>。
可以使用交叉类型实现多重约束。
interface Named {
name: string
}
interface Aged {
age: number
}
function greet<T extends Named & Aged>(person: T): string {
return `你好,我是 ${person.name},今年 ${person.age} 岁`
}
const person = {
name: "张三",
age: 25,
email: "zhangsan@example.com"
}
console.log(greet(person))
T extends Named & Aged 要求 T 同时满足 Named 和 Aged 两个接口。
可以使用类类型作为约束。
class Animal {
name: string
constructor(name: string) {
this.name = name
}
}
function createInstance<T extends Animal>(c: new (...args: any[]) => T, name: string): T {
return new c(name)
}
const animal = createInstance(Animal, "小动物")
console.log(animal.name)
new (...args: any[]) => T 表示一个构造函数类型,返回 T 类型的实例。
可以使用条件类型实现更复杂的约束。
type NonNullable<T> = T extends null | undefined ? never : T
function process<T extends NonNullable<T>>(value: T): T {
return value
}
process("hello")
process(123)
process(true)
NonNullable<T> 排除了 null 和 undefined 类型。
可以约束数组元素的类型。
function sum<T extends number>(numbers: T[]): number {
return numbers.reduce((total, n) => total + n, 0)
}
console.log(sum([1, 2, 3]))
console.log(sum([1.5, 2.5, 3.5]))
T extends number 确保数组元素是数字类型。
约束在泛型函数中特别有用。
function merge<T extends object, U extends object>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 }
}
const result = merge(
{ name: "张三" },
{ age: 25 }
)
console.log(result.name)
console.log(result.age)
T extends object 确保参数是对象类型,而不是原始类型。
泛型约束在实际开发中常用于确保类型安全。
interface Entity {
id: number
}
class Repository<T extends Entity> {
private items: T[] = []
add(item: T): void {
this.items.push(item)
}
findById(id: number): T | undefined {
return this.items.find(item => item.id === id)
}
update(item: T): boolean {
const index = this.items.findIndex(i => i.id === item.id)
if (index >= 0) {
this.items[index] = item
return true
}
return false
}
delete(id: number): boolean {
const index = this.items.findIndex(item => item.id === id)
if (index >= 0) {
this.items.splice(index, 1)
return true
}
return false
}
findAll(): T[] {
return [...this.items]
}
}
interface User extends Entity {
name: string
email: string
}
const userRepo = new Repository<User>()
userRepo.add({ id: 1, name: "张三", email: "zhangsan@example.com" })
userRepo.add({ id: 2, name: "李四", email: "lisi@example.com" })
console.log(userRepo.findById(1))
console.log(userRepo.findAll())
T extends Entity 确保 Repository 只能处理有 id 属性的实体类型。
泛型约束是控制类型参数范围的重要工具。使用 extends 关键字可以约束类型参数满足特定条件。约束可以是接口、类型别名或原始类型。可以在约束中使用其他类型参数,实现更精确的类型控制。合理使用泛型约束可以提高代码的类型安全性和可维护性。