在編程世界里,我們經(jīng)常會遇到一個情況:閱讀那些充滿了虛構(gòu)示例的枯燥文檔,實在是讓人提不起興趣。因此,在這篇文章中,我想和大家分享一些我在實際開發(fā)過程中遇到的泛型(Generics)使用案例。通過這些真實的例子,相信泛型的概念對你來說會更加具有意義,也更容易理解。
那么,泛型究竟是什么呢?簡而言之,泛型允許我們編寫能夠適用于廣泛的原始類型和對象的類型安全代碼。在聲明新類型、接口、函數(shù)和類時,都可以使用泛型。這聽起來可能有點抽象,那么讓我們直接進入正題,看看泛型的一些實際用例吧。
有時候,在我們開發(fā)的時候會遇到一些重復(fù)性的工作,特別是當我們要處理不同類型的數(shù)據(jù)時。這里有個很好的例子,就是我們的服務(wù)器需要返回用戶和書籍信息。通常情況下,如果沒有泛型(Generics),我們可能需要為每種資源分別定義一個響應(yīng)類型。
舉個例子,你的服務(wù)器需要返回用戶信息和書籍信息。
如果沒有泛型,你可能需要為用戶和書籍分別定義兩個相似的響應(yīng)類型,就像下面這樣:
// 用戶信息類型type User = { name: string };type UsersResponse = { data: User[]; total: number; page: number; limit: number;};// 書籍信息類型type Book = { isbn: string };type BooksResponse = { data: Book[]; total: number; page: number; limit: number;};
但這種方式會產(chǎn)生很多重復(fù)的代碼,不利于后期維護。而泛型,它的妙處就在于可以讓我們定義一個通用的響應(yīng)形狀,然后再根據(jù)需要使用不同的數(shù)據(jù)類型來復(fù)用這個形狀,這樣就能減少重復(fù)的代碼,看看下面這個改進版:
// 分頁響應(yīng)的泛型定義type PaginatedResponse<T> = { data: T[]; total: number; page: number; limit: number;};// 使用泛型定義用戶和書籍的響應(yīng)類型type UsersResponse = PaginatedResponse<User>;type BooksResponse = PaginatedResponse<Book>;
使用了泛型之后,無論是處理用戶列表還是書籍列表,我們只需要寫一次響應(yīng)結(jié)構(gòu),就可以應(yīng)用到各種不同的數(shù)據(jù)類型上了,不是很方便嗎?
泛型就像是一個萬能的模具,你只需要根據(jù)不同的需求,換上不同的"原料",它就能幫你塑形出符合要求的"產(chǎn)品"。這樣我們的代碼就會變得更簡潔、更有可讀性,也更容易維護。
現(xiàn)在來想想,你是否能在你的項目中找到那些可以用泛型來簡化的地方呢?別小看這個小改變,它可能會為你省下不少時間和精力哦!
在軟件開發(fā)中,我們常常需要編寫一些根據(jù)特定屬性篩選數(shù)組元素的函數(shù)。比如我們有一個篩選數(shù)組的函數(shù) filterArrayByValue,它可以基于我們提供的屬性和值來過濾數(shù)組。函數(shù)的參數(shù)和返回值之間的關(guān)系非常緊密。
一開始,我們的函數(shù)可能看起來是這樣的:
function filterArrayByValue(items, propertyName, valueToFilter) { return items.filter((item) => item[propertyName] === valueToFilter);}
這個函數(shù)聲明說,它接受一個項目數(shù)組,并返回一個具有相同類型項目的數(shù)組。目前為止,一切都好。
但是這里有個問題,我們的 propertyName 參數(shù)被定義為字符串類型,這看似沒問題,但它可能會導(dǎo)致我們不小心傳入了不存在于類型 T 的項的屬性名。如果我們定義了一個用戶數(shù)組,它應(yīng)該是這樣的:
type User = { name: string; age: number };const users: User[] = [ { name: 'Vasya', age: 32 }, { name: 'Anna', age: 12 },];
現(xiàn)在,如果我們嘗試傳遞一個錯誤的屬性,在這種情況下它不會破壞應(yīng)用程序,只是返回一個空數(shù)組,但是這并不是我們希望的,我們希望編譯器會提示屬性不匹配的問題。
filterArrayByValue(users, 'notExistField', 'Vasya');
讓我們定義該函數(shù)的第二個參數(shù),它將描述限定為只能為T類型的相關(guān)的屬性
我們定義完后,發(fā)現(xiàn)在運行階段之前提示傳遞了錯誤的屬性。
接下來我們使用 number 類型的age 屬性。正如您可能預(yù)測的那樣,當我們嘗試按此字段過濾項目時,我們會遇到問題:
filterArrayByValue(users, 'age', 12);
接下來我們修改過濾函數(shù),valueToFilter參數(shù)的對應(yīng)關(guān)系,匹配為T類型屬性對應(yīng)的值
修改后,問題已經(jīng)消失了,現(xiàn)在我們無法將除了數(shù)字以外的其他類型的值作為年齡屬性值傳遞,因為用戶類型只允許該屬性為數(shù)字,這正是我們需要的。
在React開發(fā)中,狀態(tài)管理是一個核心概念,尤其是在使用函數(shù)組件和Hooks的時候。給出的代碼段展示了如何在React組件中使用 useState Hook來管理一個用戶對象的狀態(tài),并提供了一個 setUserField 函數(shù)來更新用戶對象的特定字段。原始版本的函數(shù)對于字段名和字段值使用了非常寬泛的類型定義,這可能會導(dǎo)致類型安全問題。
const setUserField = (field: string, value: any) => setUser(prevUser => ({...prevUser, [field]: value}));
這里,field 是任意的字符串,而 value 是任意類型,這意味著我們可以不小心將錯誤的數(shù)據(jù)類型賦值給用戶對象的屬性,TypeScript編譯器也不會提出警告。
為了提高類型安全性,可以使用泛型來約束 field 必須是 User 類型的鍵,value 必須是對應(yīng)于該鍵的 User 類型的值。改進后的 setUserField 函數(shù)如下:
function setUserField<KEY extends keyof User>( field: KEY, value: User[KEY] ) { setUser((prevUser) => ({ ...prevUser, [field]: value }));}
在這個改進的版本中,setUserField 函數(shù)現(xiàn)在接受兩個參數(shù):
這樣一來,如果你嘗試傳遞一個不正確的字段或者錯誤類型的值給 setUserField 函數(shù),TypeScript編譯器會提供類型錯誤的提示,從而減少運行時錯誤的可能性。這種模式特別有用,因為它可以保證我們對狀態(tài)的更新是類型安全的,同時也保持了函數(shù)的靈活性。這是React中使用TypeScript的一個典型例子,展示了如何通過類型系統(tǒng)來增強代碼質(zhì)量。
當我們在設(shè)計高階組件(HOC)時,尤其是在React或React Native的環(huán)境下,我們希望這些HOC只能應(yīng)用于具有某些屬性的組件。在這個例子中,我們想要一個HOC,它僅適用于具有 style 屬性的組件。
function withStyledComponent<StyleProp, Props extends { style?: StyleProp }>( Component: ComponentType<Props>) { return (props: Props) => { const { style } = props; // 實現(xiàn)細節(jié)在此省略 return <Component {...props} />; };}
泛型的 extend 關(guān)鍵字允許我們定義一個類型 T,它必須至少具有類型 K 的所有屬性。這樣,我們就可以確保我們的HOC只會被用在正確的組件上。
在上述的 withStyledComponent HOC中,我們指定了任何使用此HOC的組件都必須有一個 style 屬性。如果我們嘗試將這個HOC應(yīng)用于沒有 style 屬性的組件,TypeScript會拋出一個錯誤。
這種模式非常有用,因為它可以保證我們的HOC在類型安全的同時,也不限制組件的其他屬性。這就意味著,盡管我們對 style 屬性有明確的期望,但我們的組件可以自由地具有其他任何屬性。
此外,由于TypeScript知道我們可能會在具有 style 屬性的組件中使用我們的HOC,我們可以安全地從組件的屬性中提取 style 并在HOC內(nèi)部操作它。
TypeScript有一個令人驚嘆的特性——它會嘗試從上下文中推斷出類型,只要有可能。比如,在代碼中看到這樣的語句時:
const a: number = 12;
這意味著開發(fā)者可能并不知道TypeScript已經(jīng)知道a是一個從值推斷出來的數(shù)字類型。
現(xiàn)在,假設(shè)我們用泛型定義了這樣一個函數(shù):
function identifyType<T>(target: T) { console.log("Type of target is", typeof target);}
如果你是初學(xué)者,你可能會這樣使用它:
identifyType<number>(5);
但是,TypeScript可以從你作為第一個參數(shù)傳遞的值中推斷出泛型的類型,最好是這樣使用:
identifyType(5);
如果你是React開發(fā)者,你可能會經(jīng)常看到像這樣的代碼片段:
const [count, setCount] = useState<number>(5);
但同樣,這里明確定義泛型類型是多余的,因為它會從你作為第一個參數(shù)傳遞的值中被推斷出來。如果你是一位經(jīng)驗豐富的開發(fā)者,你的代碼將看起來像這樣:
const [count, setCount] = useState(5);
還有我遇到過的一個情況,有開發(fā)者害怕在React組件的props中使用泛型。是的,我們在JSX中使用我們的組件,他們不知道這樣的語法是有效的:
function Component() { const data: ItemType[] = [{ value: '1' }]; return ( <RenderList<ItemType> data={data} renderItem={({ item }) => <Text>{item.value}</Text>} /> );}
是的,它看起來有些奇怪,但這里我們可以依靠TypeScript的能力,根據(jù)我們傳遞給組件的props類型來推斷泛型類型:
<RenderList data={data} renderItem={({ item }) => <Text>{item.value}</Text>}/>
認同這樣的代碼看起來更簡潔,你看起來也像一個經(jīng)驗豐富的開發(fā)者。這就是TypeScript和泛型的魅力:它們提供了一種強大的類型系統(tǒng),不僅可以幫助我們減少錯誤,還可以使代碼更加簡潔易讀。通過這些例子,我們可以看到,TypeScript的類型推斷功能可以在不犧牲類型安全的情況下,極大地簡化代碼。而泛型的靈活使用,則讓我們的代碼既嚴謹又富有彈性。
在我們今天的旅程中,我們一起探索了TypeScript中那些令人興奮的泛型知識。從類型推斷的便捷性到泛型在日常編程中的靈活運用,希望這些內(nèi)容能夠幫助你解開圍繞泛型的所有迷霧。記住,泛型不僅僅是類型安全的保障,它還能讓你的代碼更加簡潔、更易于維護。
正如我們所見,合理利用TypeScript的類型推斷,可以讓我們避免冗余的代碼,讓邏輯表達更為直觀。泛型的使用更是讓組件和函數(shù)的復(fù)用性達到了新的高度。所以,當你下次遇到需要類型化處理多樣化數(shù)據(jù)的場景時,別忘了,泛型就是你的得力助手。
本文鏈接:http://www.tebozhan.com/showinfo-26-84716-0.html關(guān)于TypeScript中的泛型,希望這篇文章能讓你徹底理解泛型
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: C++折疊表達式:簡潔高效的編程利器