泛型是 TypeScript 中实现代码复用的重要工具。它允许我们编写可以处理多种类型的代码,同时保持类型安全。泛型在函数、接口、类中都有广泛应用。
泛型是一种类型参数化机制,让类型像函数参数一样可以传递。通过泛型,我们可以创建可重用的组件,它们可以工作于多种类型而不是单一类型。
function identity<T>(arg: T): T {
return arg
}
const str = identity<string>("hello")
const num = identity(123)
console.log(str)
console.log(num)
identity 函数使用了泛型 T,可以接受任意类型的参数并返回相同类型的值。<T> 是类型参数,调用时可以显式指定或让 TypeScript 自动推断。
泛型的主要优势是代码复用和类型安全。
function getFirstElement<T>(arr: T[]): T | undefined {
return arr[0]
}
const firstNumber = getFirstElement([1, 2, 3])
const firstString = getFirstElement(["a", "b", "c"])
console.log(firstNumber)
console.log(firstString)
不使用泛型的话,我们需要为每种类型写一个函数,或者使用 any 类型。泛型让我们可以写一个函数处理所有类型,同时保持类型推断。
在泛型函数内部,类型参数可以被当作类型使用。
function loggingIdentity<T>(arg: T[]): T[] {
console.log(`数组长度: ${arg.length}`)
return arg
}
const result = loggingIdentity([1, 2, 3])
console.log(result)
T[] 表示 T 类型的数组,我们可以访问数组的 length 属性。类型参数 T 在函数内部可以用于类型注解。
接口可以使用泛型,定义可复用的类型结构。
interface Container<T> {
value: T
getValue(): T
setValue(value: T): void
}
class Box<T> implements Container<T> {
value: T
constructor(value: T) {
this.value = value
}
getValue(): T {
return this.value
}
setValue(value: T): void {
this.value = value
}
}
const stringBox = new Box("hello")
const numberBox = new Box(123)
console.log(stringBox.getValue())
console.log(numberBox.getValue())
Container 接口使用泛型 T,Box 类实现这个接口时指定了具体的类型。
类可以使用泛型,创建可复用的类定义。
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]
}
isEmpty(): boolean {
return this.items.length === 0
}
}
const numberStack = new Stack<number>()
numberStack.push(1)
numberStack.push(2)
console.log(numberStack.pop())
const stringStack = new Stack<string>()
stringStack.push("a")
stringStack.push("b")
console.log(stringStack.pop())
Stack 类使用泛型 T,可以创建存储任意类型数据的栈。实例化时指定具体类型。
默认情况下,泛型可以接受任意类型。有时我们需要限制泛型的范围,这时可以使用泛型约束。
interface Lengthwise {
length: number
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log(`长度: ${arg.length}`)
return arg
}
logLength("hello")
logLength([1, 2, 3])
logLength({ length: 10 })
T extends Lengthwise 表示 T 必须有 length 属性。这让我们可以在函数内部安全地访问 length。
可以为泛型参数提供默认类型,简化使用。
interface Response<T = any> {
code: number
message: string
data: T
}
const response1: Response = {
code: 200,
message: "success",
data: { name: "张三" }
}
const response2: Response<string> = {
code: 200,
message: "success",
data: "hello"
}
Response 的泛型 T 默认是 any,使用时可以省略类型参数。
泛型在实际开发中有很多应用场景,比如定义通用的 API 响应类型:
interface ApiResponse<T> {
code: number
message: string
data: T
}
interface User {
id: number
name: string
}
interface Product {
id: number
name: string
price: number
}
async function fetchUser(id: number): Promise<ApiResponse<User>> {
return {
code: 200,
message: "success",
data: { id, name: "张三" }
}
}
async function fetchProducts(): Promise<ApiResponse<Product[]>> {
return {
code: 200,
message: "success",
data: [
{ id: 1, name: "商品1", price: 100 },
{ id: 2, name: "商品2", price: 200 }
]
}
}
async function main() {
const userResponse = await fetchUser(1)
console.log(userResponse.data.name)
const productsResponse = await fetchProducts()
console.log(productsResponse.data.length)
}
main()
泛型让 API 响应类型可以复用,同时保持类型安全。
泛型是 TypeScript 的核心特性之一,它让代码更加灵活和可复用。通过泛型,我们可以编写处理多种类型的代码,同时保持完整的类型检查。泛型在函数、接口、类中都有广泛应用,是构建类型安全可复用代码的重要工具。