TypeScript 泛型四大應(yīng)用場景與高級用法
一、TypeScrip寫法
泛型是 TypeScript 中一種「類型化的變量」,它不預(yù)先指定具體的類型,而是在使用時才確定類型。簡單來說,泛型就像一個「類型占位符」,可以在函數(shù)、接口、類、類型別名中被復(fù)用,從而實現(xiàn)一套代碼適配多種類型,同時避免 any 類型帶來的類型信息丟失。
舉個簡單的例子:如果我們需要一個函數(shù),能夠返回數(shù)組的第一個元素,同時支持?jǐn)?shù)字?jǐn)?shù)組、字符串?dāng)?shù)組等多種類型,不用泛型的話,要么寫多個重載函數(shù),要么使用 any 類型:
// 弊端:丟失類型校驗,返回值為 any 類型
function getFirst(arr: any[]): any {
return arr[0];
}
const str = getFirst(["a", "b", "c"]); // 類型為 any,無法獲得自動補(bǔ)全
const num = getFirst([1, 2, 3]); // 類型為 any,存在類型安全風(fēng)險
而使用泛型,我們可以完美解決這個問題:
// 泛型基礎(chǔ)寫法:用 <T> 定義類型參數(shù),T 可看作任意合法的類型變量名
function getFirst<T>(arr: T[]): T {
return arr[0];
}
// 使用時自動推導(dǎo)類型,也可顯式指定類型
const str = getFirst(["a", "b", "c"]); // 自動推導(dǎo) T 為 string,返回值類型為 string
const num = getFirst<number>([1, 2, 3]); // 顯式指定 T 為 number,返回值類型為 number
這里的 <T> 就是泛型的核心標(biāo)識,T(Type 的縮寫,也可以用 U、V、Data 等任意合法名稱)就是我們定義的「類型參數(shù)」,它會在函數(shù)調(diào)用時被具體的類型替代,從而實現(xiàn)「一次定義,多類型復(fù)用」。
二、泛型的四大核心應(yīng)用場景
泛型的應(yīng)用場景主要集中在函數(shù)、接口、類、類型別名這四個方面,下面逐一拆解每種場景的寫法與實戰(zhàn)用法。
場景 1:泛型函數(shù)(最常用)
泛型函數(shù)是日常開發(fā)中使用頻率最高的場景,核心寫法是「在函數(shù)名后添加 <類型參數(shù)>」,類型參數(shù)可以在函數(shù)的參數(shù)、返回值、內(nèi)部變量中使用。
基礎(chǔ)寫法
// 單個類型參數(shù)
function wrapValue<T>(value: T): { value: T } {
return { value };
}
// 多個類型參數(shù)(支持定義多個泛型變量,用逗號分隔)
function mergeObjects<U, V>(obj1: U, obj2: V): U & V {
return { ...obj1, ...obj2 };
}
// 實戰(zhàn)使用
const wrapped = wrapValue<boolean>(true); // 返回 { value: boolean }
const merged = mergeObjects({ name: "張三" }, { age: 28 }); // 返回 { name: string; age: number }
關(guān)鍵特性:類型參數(shù)的默認(rèn)值
和函數(shù)參數(shù)可以設(shè)置默認(rèn)值一樣,泛型的類型參數(shù)也可以設(shè)置默認(rèn)值。當(dāng)使用泛型時沒有顯式指定類型參數(shù),且 TypeScript 無法自動推導(dǎo)時,會使用這個默認(rèn)值。
語法:<T = 默認(rèn)類型>
// 為類型參數(shù) T 設(shè)置默認(rèn)值 string
function getFirst<T = string>(arr: T[]): T {
return arr[0];
}
// 未顯式指定類型,且無法推導(dǎo)具體類型時,使用默認(rèn)值 string
const defaultVal = getFirst([]); // T 為 string,返回值類型為 string
// 自動推導(dǎo)類型,覆蓋默認(rèn)值
const numVal = getFirst([1, 2, 3]); // T 為 number,返回值類型為 number
場景 2:泛型接口
當(dāng)接口需要支持多種類型的屬性或方法時,泛型接口可以讓接口具備更強(qiáng)的復(fù)用性。核心寫法是「在接口名后添加 <類型參數(shù)>」,類型參數(shù)可用于接口的屬性、方法參數(shù)和返回值。
基礎(chǔ)寫法
// 定義泛型接口
interface Result<T> {
code: number;
message: string;
data: T; // 用泛型 T 定義 data 屬性的類型
}
// 使用時指定具體類型
type UserResult = Result<{ id: number; name: string }>;
type ArticleResult = Result<{ title: string; content: string }>;
// 實戰(zhàn)應(yīng)用:接口返回值類型定義
const userRes: UserResult = {
code: 200,
message: "請求成功",
data: { id: 1, name: "張三" }
};
const articleRes: ArticleResult = {
code: 200,
message: "請求成功",
data: { title: "TypeScript 泛型指南", content: "泛型的四大應(yīng)用場景..." }
};
泛型接口與函數(shù)結(jié)合(常用實戰(zhàn)場景)
// 定義泛型接口(描述函數(shù)類型)
interface RequestFunction<T> {
(url: string): Promise<Result<T>>;
}
// 實現(xiàn)該接口的函數(shù)
const getUserInfo: RequestFunction<{ id: number; name: string }> = async (url) => {
const res = await fetch(url);
return res.json();
};
帶默認(rèn)值的泛型接口
// 為泛型接口設(shè)置默認(rèn)值
interface Pagination<T = unknown> {
page: number;
size: number;
total: number;
list: T[];
}
// 未指定類型,使用默認(rèn)值 unknown
const defaultPagination: Pagination = {
page: 1,
size: 10,
total: 100,
list: []
};
// 指定具體類型,覆蓋默認(rèn)值
const userPagination: Pagination<{ id: number; name: string }> = {
page: 1,
size: 10,
total: 100,
list: [{ id: 1, name: "張三" }]
};
場景 3:泛型類
泛型類適用于「類的屬性、方法需要支持多種類型」的場景,比如數(shù)據(jù)容器、隊列、棧等。核心寫法是「在類名后添加 <類型參數(shù)>」,類型參數(shù)可用于類的屬性、構(gòu)造函數(shù)、實例方法。
基礎(chǔ)寫法
// 定義泛型類
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];
}
}
// 使用時指定具體類型
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // 2(類型為 number | undefined)
const stringStack = new Stack<string>();
stringStack.push("a");
stringStack.push("b");
console.log(stringStack.peek()); // "b"(類型為 string | undefined)
帶默認(rèn)值的泛型類
// 為泛型類設(shè)置默認(rèn)值
class Container<T = string> {
private data: T;
constructor(data: T) {
this.data = data;
}
getData(): T {
return this.data;
}
}
// 未指定類型,使用默認(rèn)值 string
const defaultContainer = new Container("hello");
console.log(defaultContainer.getData()); // "hello"(類型為 string)
// 指定具體類型,覆蓋默認(rèn)值
const numberContainer = new Container<number>(123);
console.log(numberContainer.getData()); // 123(類型為 number)
注意:泛型類只作用于實例部分,靜態(tài)屬性和靜態(tài)方法無法使用類的泛型參數(shù)(因為靜態(tài)成員屬于類本身,而泛型參數(shù)是在實例化時確定的)。
場景 4:泛型類型別名
類型別名(type)是 TypeScript 中定義自定義類型的重要方式,泛型類型別名可以讓自定義類型具備更強(qiáng)的靈活性,核心寫法是「在類型別名后添加 <類型參數(shù)>」。
基礎(chǔ)寫法
// 定義泛型類型別名
type Pair<T> = [T, T]; // 元組類型,兩個元素類型相同
type Optional<T> = T | undefined; // 可選類型,T 或 undefined
type RecordMap<K extends string | number, V> = { [key in K]: V }; // 映射類型
// 使用時指定具體類型
type NumberPair = Pair<number>; // [number, number]
type OptionalString = Optional<string>; // string | undefined
type UserMap = RecordMap<number, { name: string; age: number }>; // { [key: number]: { name: string; age: number } }
// 實戰(zhàn)應(yīng)用
const pair: NumberPair = [1, 2];
const optionalStr: OptionalString = "hello";
const userMap: UserMap = {
1: { name: "張三", age: 28 },
2: { name: "李四", age: 30 }
};
帶默認(rèn)值的泛型類型別名
// 為泛型類型別名設(shè)置默認(rèn)值
type ResponseData<T = { [key: string]: unknown }> = {
success: boolean;
data: T;
};
// 未指定類型,使用默認(rèn)值
const defaultResponse: ResponseData = {
success: true,
data: { id: 1, name: "張三" }
};
// 指定具體類型,覆蓋默認(rèn)值
const userResponse: ResponseData<{ id: number; name: string }> = {
success: true,
data: { id: 1, name: "張三" }
};
三、高級用法:類型參數(shù)的約束條件
在某些場景下,我們不希望泛型參數(shù)是「任意類型」,而是希望它滿足一定的約束條件(比如擁有某個屬性、實現(xiàn)某個接口)。這時可以使用 extends 關(guān)鍵字來約束泛型參數(shù),這就是「泛型約束」。
基礎(chǔ)約束:extends 關(guān)鍵字
// 定義一個接口,作為約束條件
interface HasLength {
length: number;
}
// 約束 T 必須實現(xiàn) HasLength 接口(即 T 必須擁有 length 屬性)
function getLength<T extends HasLength>(value: T): number {
return value.length;
}
// 合法:string 擁有 length 屬性
console.log(getLength("hello")); // 5
// 合法:數(shù)組擁有 length 屬性
console.log(getLength([1, 2, 3])); // 3
// 合法:自定義對象擁有 length 屬性
console.log(getLength({ length: 10, name: "測試" })); // 10
// 不合法:number 沒有 length 屬性,編譯報錯
console.log(getLength(123)); // Error: Argument of type 'number' is not assignable to parameter of type 'HasLength'
泛型約束與默認(rèn)值結(jié)合
泛型約束和默認(rèn)值可以同時使用,語法順序為「<T extends 約束類型 = 默認(rèn)類型>」:
interface HasId {
id: number;
}
// 約束 T 必須實現(xiàn) HasId 接口,同時設(shè)置默認(rèn)值
function getId<T extends HasId = { id: number; name: string }>(obj: T): number {
return obj.id;
}
// 未指定類型,使用默認(rèn)值(滿足 HasId 約束)
const defaultId = getId({ id: 1, name: "張三" }); // 1
// 指定具體類型(滿足 HasId 約束)
const customId = getId({ id: 2, age: 28 }); // 2
常用約束:約束為對象、字符串/數(shù)字字面量
// 約束 T 為對象類型
function getProperty<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "張三", age: 28 };
console.log(getProperty(user, "name")); // "張三"(類型為 string)
console.log(getProperty(user, "age")); // 28(類型為 number)
// 約束 K 為 "success" | "error" 字面量類型
function createMessage<K extends "success" | "error" = "success">(type: K): { type: K; content: string } {
return {
type,
content: type === "success" ? "操作成功" : "操作失敗"
};
}
const successMsg = createMessage(); // { type: "success"; content: string }
const errorMsg = createMessage("error"); // { type: "error"; content: string }
四、泛型使用總結(jié)與最佳實踐
- 泛型的核心價值:復(fù)用代碼、保留類型信息、避免
any類型,實現(xiàn)「靈活與安全并存」。 - 四大應(yīng)用場景優(yōu)先級:泛型函數(shù) > 泛型接口 > 泛型類型別名 > 泛型類(類的泛型使用場景相對有限)。
- 最佳實踐:
- 類型參數(shù)命名盡量語義化(如
T表示通用類型,U/V表示輔助類型,K表示鍵類型,V表示值類型)。 - 對于有常用類型的場景,設(shè)置泛型默認(rèn)值,提升代碼易用性。
- 當(dāng)需要限制泛型范圍時,使用
extends進(jìn)行約束,避免非法類型傳入。 - 避免過度泛型化:如果一個函數(shù)只需要支持一種或兩種固定類型,無需使用泛型,直接定義具體類型即可。
- 類型參數(shù)命名盡量語義化(如
到此這篇關(guān)于TypeScript 泛型四大應(yīng)用場景與高級用法的文章就介紹到這了,更多相關(guān)TypeScript 泛型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
layer的prompt彈出框,點擊回車,觸發(fā)確定事件的方法
今天小編就為大家分享一篇layer的prompt彈出框,點擊回車,觸發(fā)確定事件的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09
JS中關(guān)于ES6?Module模塊化的跨域報錯問題解決
這篇文章主要介紹了JS中關(guān)于ES6?Module模塊化的跨域報錯,ES6模塊化提供了export命令和import?命令,對于模塊的導(dǎo)出和引入,本文結(jié)合實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07
JavaScript實現(xiàn)JSON合并操作示例【遞歸深度合并】
這篇文章主要介紹了JavaScript實現(xiàn)JSON合并操作,結(jié)合實例形式分析了javascript基于遞歸深度實現(xiàn)json合并操作相關(guān)實現(xiàn)技巧與注意事項,需要的朋友可以參考下2018-09-09
深入理解JavaScript系列(1) 編寫高質(zhì)量JavaScript代碼的基本要點
才華橫溢的Stoyan Stefanov,在他寫的由O’Reilly初版的新書《JavaScript Patterns》(JavaScript模式)中,我想要是為我們的讀者貢獻(xiàn)其摘要,那會是件很美妙的事情2012-01-01
JS格式化數(shù)字(每三位加逗號)的方法總結(jié)
這篇文章總結(jié)了JS格式化數(shù)字(每三位加逗號)的幾種方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06

